diff options
Diffstat (limited to 'plugin/server_audit')
-rw-r--r-- | plugin/server_audit/CMakeLists.txt | 18 | ||||
-rw-r--r-- | plugin/server_audit/COPYING | 339 | ||||
-rw-r--r-- | plugin/server_audit/plugin_audit_v4.h | 561 | ||||
-rw-r--r-- | plugin/server_audit/server_audit.c | 3104 | ||||
-rw-r--r-- | plugin/server_audit/test_audit_v4.c | 163 |
5 files changed, 4185 insertions, 0 deletions
diff --git a/plugin/server_audit/CMakeLists.txt b/plugin/server_audit/CMakeLists.txt new file mode 100644 index 00000000..8a5333cf --- /dev/null +++ b/plugin/server_audit/CMakeLists.txt @@ -0,0 +1,18 @@ +# Copyright (C) 2013 Alexey Botchkov and SkySQL Ab +# +# 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 Street, Fifth Floor, Boston, MA 02110-1335 USA + +SET(SOURCES server_audit.c test_audit_v4.c plugin_audit_v4.h) + +MYSQL_ADD_PLUGIN(server_audit ${SOURCES} MODULE_ONLY RECOMPILE_FOR_EMBEDDED) diff --git a/plugin/server_audit/COPYING b/plugin/server_audit/COPYING new file mode 100644 index 00000000..6e475df5 --- /dev/null +++ b/plugin/server_audit/COPYING @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + 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; either version 2 of the License, or + (at your option) any later version. + + 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 Street, Fifth Floor, Boston, MA 02110-1335 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/plugin/server_audit/plugin_audit_v4.h b/plugin/server_audit/plugin_audit_v4.h new file mode 100644 index 00000000..a2a38806 --- /dev/null +++ b/plugin/server_audit/plugin_audit_v4.h @@ -0,0 +1,561 @@ +/* Copyright (c) 2007, 2015, 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-1335 USA */ + +#ifndef _my_audit_h +#define _my_audit_h + +#ifndef PLUGIN_CONTEXT +#include "plugin.h" +#include "mysql/mysql_lex_string.h" +#ifndef MYSQL_ABI_CHECK +#include "m_string.h" +#endif +#include "my_command.h" +#include "my_sqlcommand.h" +#endif /*PLUGIN_CONTEXT*/ + +#define MYSQL_AUDIT_INTERFACE_VERSION 0x0401 + +/** + @enum mysql_event_class_t + + Audit event classes. +*/ +typedef enum +{ + MYSQL_AUDIT_GENERAL_CLASS = 0, + MYSQL_AUDIT_CONNECTION_CLASS = 1, + MYSQL_AUDIT_PARSE_CLASS = 2, + MYSQL_AUDIT_AUTHORIZATION_CLASS = 3, + MYSQL_AUDIT_TABLE_ACCESS_CLASS = 4, + MYSQL_AUDIT_GLOBAL_VARIABLE_CLASS = 5, + MYSQL_AUDIT_SERVER_STARTUP_CLASS = 6, + MYSQL_AUDIT_SERVER_SHUTDOWN_CLASS = 7, + MYSQL_AUDIT_COMMAND_CLASS = 8, + MYSQL_AUDIT_QUERY_CLASS = 9, + MYSQL_AUDIT_STORED_PROGRAM_CLASS = 10, + /* This item must be last in the list. */ + MYSQL_AUDIT_CLASS_MASK_SIZE +} mysql_event_class_t; + +/** + @struct st_mysql_audit + + The descriptor structure that is referred from st_mysql_plugin. +*/ +struct st_mysql_audit +{ + /** + Interface version. + */ + int interface_version; + + /** + Event occurs when the event class consumer is to be + disassociated from the specified THD.This would typically occur + before some operation which may require sleeping - such as when + waiting for the next query from the client. + */ + void (*release_thd)(MYSQL_THD); + + /** + Invoked whenever an event occurs which is of any + class for which the plugin has interest.The second argument + indicates the specific event class and the third argument is data + as required for that class. + */ + int (*event_notify)(MYSQL_THD, mysql_event_class_t, const void *); + + /** + An array of bits used to indicate what event classes + that this plugin wants to receive. + */ + unsigned long class_mask[MYSQL_AUDIT_CLASS_MASK_SIZE]; +}; + +/** + @typedef enum_sql_command_t + + SQL command type definition. +*/ +typedef enum enum_sql_command enum_sql_command_t; + +/** + @enum mysql_event_general_subclass_t + + Events for the MYSQL_AUDIT_GENERAL_CLASS event class. +*/ +typedef enum +{ + /** occurs before emitting to the general query log. */ + MYSQL_AUDIT_GENERAL_LOG = 1 << 0, + /** occurs before transmitting errors to the user. */ + MYSQL_AUDIT_GENERAL_ERROR = 1 << 1, + /** occurs after transmitting a resultset to the user. */ + MYSQL_AUDIT_GENERAL_RESULT = 1 << 2, + /** occurs after transmitting a resultset or errors */ + MYSQL_AUDIT_GENERAL_STATUS = 1 << 3 +} mysql_event_general_subclass_t; + +#define MYSQL_AUDIT_GENERAL_ALL (MYSQL_AUDIT_GENERAL_LOG | \ + MYSQL_AUDIT_GENERAL_ERROR | \ + MYSQL_AUDIT_GENERAL_RESULT | \ + MYSQL_AUDIT_GENERAL_STATUS) +/** + @struct mysql_event_general + + Structure for the MYSQL_AUDIT_GENERAL_CLASS event class. +*/ +struct mysql_event_general +{ + mysql_event_general_subclass_t event_subclass; + int general_error_code; + unsigned long general_thread_id; + MYSQL_LEX_CSTRING general_user; + MYSQL_LEX_CSTRING general_command; + MYSQL_LEX_CSTRING general_query; + struct charset_info_st *general_charset; + unsigned long long general_time; + unsigned long long general_rows; + MYSQL_LEX_CSTRING general_host; + MYSQL_LEX_CSTRING general_sql_command; + MYSQL_LEX_CSTRING general_external_user; + MYSQL_LEX_CSTRING general_ip; +}; + +/** + @enum mysql_event_connection_subclass_t + + Events for MYSQL_AUDIT_CONNECTION_CLASS event class. +*/ +typedef enum +{ + /** occurs after authentication phase is completed. */ + MYSQL_AUDIT_CONNECTION_CONNECT = 1 << 0, + /** occurs after connection is terminated. */ + MYSQL_AUDIT_CONNECTION_DISCONNECT = 1 << 1, + /** occurs after COM_CHANGE_USER RPC is completed. */ + MYSQL_AUDIT_CONNECTION_CHANGE_USER = 1 << 2, + /** occurs before authentication. */ + MYSQL_AUDIT_CONNECTION_PRE_AUTHENTICATE = 1 << 3 +} mysql_event_connection_subclass_t; + +#define MYSQL_AUDIT_CONNECTION_ALL (MYSQL_AUDIT_CONNECTION_CONNECT | \ + MYSQL_AUDIT_CONNECTION_DISCONNECT | \ + MYSQL_AUDIT_CONNECTION_CHANGE_USER | \ + MYSQL_AUDIT_CONNECTION_PRE_AUTHENTICATE) +/** + @struct mysql_event_connection + + Structure for the MYSQL_AUDIT_CONNECTION_CLASS event class. +*/ +struct mysql_event_connection +{ + /** Event subclass. */ + mysql_event_connection_subclass_t event_subclass; + /** Current status of the connection. */ + int status; + /** Connection id. */ + unsigned long connection_id; + /** User name of this connection. */ + MYSQL_LEX_CSTRING user; + /** Priv user name. */ + MYSQL_LEX_CSTRING priv_user; + /** External user name. */ + MYSQL_LEX_CSTRING external_user; + /** Proxy user used for this connection. */ + MYSQL_LEX_CSTRING proxy_user; + /** Connection host. */ + MYSQL_LEX_CSTRING host; + /** IP of the connection. */ + MYSQL_LEX_CSTRING ip; + /** Database name specified at connection time. */ + MYSQL_LEX_CSTRING database; + /** Connection type: + - 0 Undefined + - 1 TCP/IP + - 2 Socket + - 3 Named pipe + - 4 SSL + - 5 Shared memory + */ + int connection_type; +}; + +/** +@enum mysql_event_parse_subclass_t + +Events for MYSQL_AUDIT_PARSE_CLASS event class. +*/ +typedef enum +{ + /** occurs before the query parsing. */ + MYSQL_AUDIT_PARSE_PREPARSE = 1 << 0, + /** occurs after the query parsing. */ + MYSQL_AUDIT_PARSE_POSTPARSE = 1 << 1 +} mysql_event_parse_subclass_t; + +#define MYSQL_AUDIT_PARSE_ALL (MYSQL_AUDIT_PARSE_PREPARSE | \ + MYSQL_AUDIT_PARSE_POSTPARSE) + +typedef enum +{ + MYSQL_AUDIT_PARSE_REWRITE_PLUGIN_NONE = 0, + /// mysql_event_parse::flags Must be set by a plugin if the query is rewritten. + MYSQL_AUDIT_PARSE_REWRITE_PLUGIN_QUERY_REWRITTEN = 1 << 0, + /// mysql_event_parse::flags Is set by the server if the query is prepared statement. + MYSQL_AUDIT_PARSE_REWRITE_PLUGIN_IS_PREPARED_STATEMENT = 1 << 1 +} mysql_event_parse_rewrite_plugin_flag; + +/** Data for the MYSQL_AUDIT_PARSE events */ +struct mysql_event_parse +{ + /** MYSQL_AUDIT_[PRE|POST]_PARSE event id */ + mysql_event_parse_subclass_t event_subclass; + + /** one of FLAG_REWRITE_PLUGIN_* */ + mysql_event_parse_rewrite_plugin_flag *flags; + + /** input: the original query text */ + MYSQL_LEX_CSTRING query; + + /** output: returns the null-terminated rewritten query allocated by my_malloc() */ + MYSQL_LEX_CSTRING *rewritten_query; +}; + +/** + @enum mysql_event_authorization_subclass_t + + Events for MYSQL_AUDIT_AUTHORIZATION_CLASS event class. +*/ +typedef enum +{ + MYSQL_AUDIT_AUTHORIZATION_USER = 1 << 0, + /** Occurs when database privilege is checked. */ + MYSQL_AUDIT_AUTHORIZATION_DB = 1 << 1, + /** Occurs when table privilege is checked. */ + MYSQL_AUDIT_AUTHORIZATION_TABLE = 1 << 2, + /** Occurs when column privilege is checked. */ + MYSQL_AUDIT_AUTHORIZATION_COLUMN = 1 << 3, + /** Occurs when procedure privilege is checked. */ + MYSQL_AUDIT_AUTHORIZATION_PROCEDURE = 1 << 4, + /** Occurs when proxy privilege is checked. */ + MYSQL_AUDIT_AUTHORIZATION_PROXY = 1 << 5 +} mysql_event_authorization_subclass_t; + +#define MYSQL_AUDIT_AUTHORIZATION_ALL (MYSQL_AUDIT_AUTHORIZATION_USER | \ + MYSQL_AUDIT_AUTHORIZATION_DB | \ + MYSQL_AUDIT_AUTHORIZATION_TABLE | \ + MYSQL_AUDIT_AUTHORIZATION_COLUMN | \ + MYSQL_AUDIT_AUTHORIZATION_PROCEDURE | \ + MYSQL_AUDIT_AUTHORIZATION_PROXY) +/** + @struct mysql_event_authorization + + Structure for MYSQL_AUDIT_AUTHORIZATION_CLASS event class. +*/ +struct mysql_event_authorization +{ + /** Event subclass. */ + mysql_event_authorization_subclass_t event_subclass; + /** Event status. */ + int status; + /** Connection id. */ + unsigned int connection_id; + /** SQL command id. */ + enum_sql_command_t sql_command_id; + /** SQL query text. */ + MYSQL_LEX_CSTRING query; + /** SQL query charset. */ + const struct charset_info_st *query_charset; + /** Database name. */ + MYSQL_LEX_CSTRING database; + /** Table name. */ + MYSQL_LEX_CSTRING table; + /** Other name associated with the event. */ + MYSQL_LEX_CSTRING object; + /** Requested authorization privileges. */ + unsigned long requested_privilege; + /** Currently granted authorization privileges. */ + unsigned long granted_privilege; +}; + +/** + @enum mysql_event_table_row_access_subclass_t + + Events for MYSQL_AUDIT_TABLE_ACCES_CLASS event class. +*/ +typedef enum +{ + /** Occurs when table data are read. */ + MYSQL_AUDIT_TABLE_ACCESS_READ = 1 << 0, + /** Occurs when table data are inserted. */ + MYSQL_AUDIT_TABLE_ACCESS_INSERT = 1 << 1, + /** Occurs when table data are updated. */ + MYSQL_AUDIT_TABLE_ACCESS_UPDATE = 1 << 2, + /** Occurs when table data are deleted. */ + MYSQL_AUDIT_TABLE_ACCESS_DELETE = 1 << 3 +} mysql_event_table_access_subclass_t; + +#define MYSQL_AUDIT_TABLE_ACCESS_ALL (MYSQL_AUDIT_TABLE_ACCESS_READ | \ + MYSQL_AUDIT_TABLE_ACCESS_INSERT | \ + MYSQL_AUDIT_TABLE_ACCESS_UPDATE | \ + MYSQL_AUDIT_TABLE_ACCESS_DELETE) + +/** + @struct mysql_event_table_row_access + + Structure for MYSQL_AUDIT_TABLE_ACCES_CLASS event class. +*/ +struct mysql_event_table_access +{ + /** Event subclass. */ + mysql_event_table_access_subclass_t event_subclass; + /** Connection id. */ + unsigned long connection_id; + /** SQL command id. */ + enum_sql_command_t sql_command_id; + /** SQL query. */ + MYSQL_LEX_CSTRING query; + /** SQL query charset. */ + const struct charset_info_st *query_charset; + /** Database name. */ + MYSQL_LEX_CSTRING table_database; + /** Table name. */ + MYSQL_LEX_CSTRING table_name; +}; + +/** + @enum mysql_event_global_variable_subclass_t + + Events for MYSQL_AUDIT_GLOBAL_VARIABLE_CLASS event class. +*/ +typedef enum +{ + /** Occurs when global variable is retrieved. */ + MYSQL_AUDIT_GLOBAL_VARIABLE_GET = 1 << 0, + /** Occurs when global variable is set. */ + MYSQL_AUDIT_GLOBAL_VARIABLE_SET = 1 << 1 +} mysql_event_global_variable_subclass_t; + +#define MYSQL_AUDIT_GLOBAL_VARIABLE_ALL (MYSQL_AUDIT_GLOBAL_VARIABLE_GET | \ + MYSQL_AUDIT_GLOBAL_VARIABLE_SET) + +/** Events for MYSQL_AUDIT_GLOBAL_VARIABLE_CLASS event class. */ +struct mysql_event_global_variable +{ + /** Event subclass. */ + mysql_event_global_variable_subclass_t event_subclass; + /** Connection id. */ + unsigned long connection_id; + /** SQL command id. */ + enum_sql_command_t sql_command_id; + /** Variable name. */ + MYSQL_LEX_CSTRING variable_name; + /** Variable value. */ + MYSQL_LEX_CSTRING variable_value; +}; + +/** + @enum mysql_event_server_startup_subclass_t + + Events for MYSQL_AUDIT_SERVER_STARTUP_CLASS event class. +*/ +typedef enum +{ + /** Occurs after all subsystem are initialized during system start. */ + MYSQL_AUDIT_SERVER_STARTUP_STARTUP = 1 << 0 +} mysql_event_server_startup_subclass_t; + +#define MYSQL_AUDIT_SERVER_STARTUP_ALL (MYSQL_AUDIT_SERVER_STARTUP_STARTUP) + +/** + @struct mysql_event_server_startup + + Structure for MYSQL_AUDIT_SERVER_STARTUP_CLASS event class. +*/ +struct mysql_event_server_startup +{ + /** Event subclass. */ + mysql_event_server_startup_subclass_t event_subclass; + /** Command line arguments. */ + const char **argv; + /** Command line arguments count. */ + unsigned int argc; +}; + +/** + @enum mysql_event_server_shutdown_subclass_t + + Events for MYSQL_AUDIT_SERVER_SHUTDOWN_CLASS event class. +*/ +typedef enum +{ + /** Occurs when global variable is set. */ + MYSQL_AUDIT_SERVER_SHUTDOWN_SHUTDOWN = 1 << 0 +} mysql_event_server_shutdown_subclass_t; + +#define MYSQL_AUDIT_SERVER_SHUTDOWN_ALL (MYSQL_AUDIT_SERVER_SHUTDOWN_SHUTDOWN) + +/** + @enum mysql_server_shutdown_reason_t + + Server shutdown reason. +*/ +typedef enum +{ + /** User requested shut down. */ + MYSQL_AUDIT_SERVER_SHUTDOWN_REASON_SHUTDOWN, + /** The server aborts. */ + MYSQL_AUDIT_SERVER_SHUTDOWN_REASON_ABORT +} mysql_server_shutdown_reason_t; + +/** + @struct mysql_event_server_shutdown + + Structure for MYSQL_AUDIT_SERVER_SHUTDOWN_CLASS event class. +*/ +struct mysql_event_server_shutdown +{ + /** Shutdown event. */ + mysql_event_server_shutdown_subclass_t event_subclass; + /** Exit code associated with the shutdown event. */ + int exit_code; + /** Shutdown reason. */ + mysql_server_shutdown_reason_t reason; +}; + +/** + @enum mysql_event_command_subclass_t + + Events for MYSQL_AUDIT_COMMAND_CLASS event class. +*/ +typedef enum +{ + /** Command start event. */ + MYSQL_AUDIT_COMMAND_START = 1 << 0, + /** Command end event. */ + MYSQL_AUDIT_COMMAND_END = 1 << 1 +} mysql_event_command_subclass_t; + +#define MYSQL_AUDIT_COMMAND_ALL (MYSQL_AUDIT_COMMAND_START | \ + MYSQL_AUDIT_COMMAND_END) +/** + @typedef enum_server_command_t + + Server command type definition. +*/ +typedef enum enum_server_command enum_server_command_t; + +/** + @struct mysql_event_command + + Event for MYSQL_AUDIT_COMMAND_CLASS event class. + Events generated as a result of RPC command requests. +*/ +struct mysql_event_command +{ + /** Command event subclass. */ + mysql_event_command_subclass_t event_subclass; + /** Command event status. */ + int status; + /** Connection id. */ + unsigned long connection_id; + /** Command id. */ + enum_server_command_t command_id; +}; + +/** + @enum mysql_event_query_subclass_t + + Events for MYSQL_AUDIT_QUERY_CLASS event class. +*/ +typedef enum +{ + /** Query start event. */ + MYSQL_AUDIT_QUERY_START = 1 << 0, + /** Nested query start event. */ + MYSQL_AUDIT_QUERY_NESTED_START = 1 << 1, + /** Query post parse event. */ + MYSQL_AUDIT_QUERY_STATUS_END = 1 << 2, + /** Nested query status end event. */ + MYSQL_AUDIT_QUERY_NESTED_STATUS_END = 1 << 3 +} mysql_event_query_subclass_t; + +#define MYSQL_AUDIT_QUERY_ALL (MYSQL_AUDIT_QUERY_START | \ + MYSQL_AUDIT_QUERY_NESTED_START | \ + MYSQL_AUDIT_QUERY_STATUS_END | \ + MYSQL_AUDIT_QUERY_NESTED_STATUS_END) +/** + @struct mysql_event_command + + Event for MYSQL_AUDIT_COMMAND_CLASS event class. +*/ +struct mysql_event_query +{ + /** Event subclass. */ + mysql_event_query_subclass_t event_subclass; + /** Event status. */ + int status; + /** Connection id. */ + unsigned long connection_id; + /** SQL command id. */ + enum_sql_command_t sql_command_id; + /** SQL query. */ + MYSQL_LEX_CSTRING query; + /** SQL query charset. */ + const struct charset_info_st *query_charset; +}; + +/** + @enum mysql_event_stored_program_subclass_t + + Events for MYSQL_AUDIT_STORED_PROGRAM_CLASS event class. +*/ +typedef enum +{ + /** Stored program execution event. */ + MYSQL_AUDIT_STORED_PROGRAM_EXECUTE = 1 << 0 +} mysql_event_stored_program_subclass_t; + +#define MYSQL_AUDIT_STORED_PROGRAM_ALL (MYSQL_AUDIT_STORED_PROGRAM_EXECUTE) + +/** + @struct mysql_event_command + +Event for MYSQL_AUDIT_COMMAND_CLASS event class. +*/ +struct mysql_event_stored_program +{ + /** Event subclass. */ + mysql_event_stored_program_subclass_t event_subclass; + /** Connection id. */ + unsigned long connection_id; + /** SQL command id. */ + enum_sql_command_t sql_command_id; + /** SQL query text. */ + MYSQL_LEX_CSTRING query; + /** SQL query charset. */ + const struct charset_info_st *query_charset; + /** The Database the procedure is defined in. */ + MYSQL_LEX_CSTRING database; + /** Name of the stored program. */ + MYSQL_LEX_CSTRING name; + /** Stored program parameters. */ + void *parameters; +}; + +#endif diff --git a/plugin/server_audit/server_audit.c b/plugin/server_audit/server_audit.c new file mode 100644 index 00000000..e94361de --- /dev/null +++ b/plugin/server_audit/server_audit.c @@ -0,0 +1,3104 @@ +/* Copyright (C) 2013, 2015, Alexey Botchkov and SkySQL Ab + Copyright (c) 2019, 2020, MariaDB Corporation. + + 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 Street, Fifth Floor, Boston, MA 02110-1335 USA */ + + +#define PLUGIN_VERSION 0x104 +#define PLUGIN_STR_VERSION "1.4.13" + +#define _my_thread_var loc_thread_var + +#include <my_config.h> +#include <assert.h> + +#ifndef _WIN32 +#define DO_SYSLOG +#include <syslog.h> +static const char out_type_desc[]= "Desired output type. Possible values - 'syslog', 'file'" + " or 'null' as no output."; +#else +static const char out_type_desc[]= "Desired output type. Possible values - 'file'" + " or 'null' as no output."; +#define syslog(PRIORITY, FORMAT, INFO, MESSAGE_LEN, MESSAGE) do {}while(0) +static void closelog() {} +#define openlog(IDENT, LOG_NOWAIT, LOG_USER) do {}while(0) + +/* priorities */ +#define LOG_EMERG 0 /* system is unusable */ +#define LOG_ALERT 1 /* action must be taken immediately */ +#define LOG_CRIT 2 /* critical conditions */ +#define LOG_ERR 3 /* error conditions */ +#define LOG_WARNING 4 /* warning conditions */ +#define LOG_NOTICE 5 /* normal but significant condition */ +#define LOG_INFO 6 /* informational */ +#define LOG_DEBUG 7 /* debug-level messages */ + +#define LOG_MAKEPRI(fac, pri) (((fac) << 3) | (pri)) + +/* facility codes */ +#define LOG_KERN (0<<3) /* kernel messages */ +#define LOG_USER (1<<3) /* random user-level messages */ +#define LOG_MAIL (2<<3) /* mail system */ +#define LOG_DAEMON (3<<3) /* system daemons */ +#define LOG_AUTH (4<<3) /* security/authorization messages */ +#define LOG_SYSLOG (5<<3) /* messages generated internally by syslogd */ +#define LOG_LPR (6<<3) /* line printer subsystem */ +#define LOG_NEWS (7<<3) /* network news subsystem */ +#define LOG_UUCP (8<<3) /* UUCP subsystem */ +#define LOG_CRON (9<<3) /* clock daemon */ +#define LOG_AUTHPRIV (10<<3) /* security/authorization messages (private) */ +#define LOG_FTP (11<<3) /* ftp daemon */ +#define LOG_LOCAL0 (16<<3) /* reserved for local use */ +#define LOG_LOCAL1 (17<<3) /* reserved for local use */ +#define LOG_LOCAL2 (18<<3) /* reserved for local use */ +#define LOG_LOCAL3 (19<<3) /* reserved for local use */ +#define LOG_LOCAL4 (20<<3) /* reserved for local use */ +#define LOG_LOCAL5 (21<<3) /* reserved for local use */ +#define LOG_LOCAL6 (22<<3) /* reserved for local use */ +#define LOG_LOCAL7 (23<<3) /* reserved for local use */ + +#endif /*!_WIN32*/ + +/* + Defines that can be used to reshape the pluging: + #define MARIADB_ONLY + #define USE_MARIA_PLUGIN_INTERFACE +*/ + +#if !defined(MYSQL_DYNAMIC_PLUGIN) && !defined(MARIADB_ONLY) +#include <typelib.h> +#define MARIADB_ONLY +#endif /*MYSQL_PLUGIN_DYNAMIC*/ + +#ifndef MARIADB_ONLY +#define MYSQL_SERVICE_LOGGER_INCLUDED +#endif /*MARIADB_ONLY*/ + +#include <my_global.h> +#include <my_base.h> +#include <typelib.h> +#include <mysql/plugin.h> +#include <mysql/plugin_audit.h> +#include <string.h> +#include "../../mysys/mysys_priv.h" +#ifndef RTLD_DEFAULT +#define RTLD_DEFAULT NULL +#endif + +#ifndef MARIADB_ONLY +#undef MYSQL_SERVICE_LOGGER_INCLUDED +#undef MYSQL_DYNAMIC_PLUGIN +#define FLOGGER_NO_PSI + +/* How to access the pthread_mutex in mysql_mutex_t */ +#ifdef SAFE_MUTEX +#define mysql_mutex_real_mutex(A) &(A)->m_mutex.mutex +#else +#define mysql_mutex_real_mutex(A) &(A)->m_mutex +#endif + +#define flogger_mutex_init(A,B,C) do{}while(0) +#define flogger_mutex_destroy(A) do{}while(0) +#define flogger_mutex_lock(A) do{}while(0) +#define flogger_mutex_unlock(A) do{}while(0) + +static char **int_mysql_data_home; +static char *default_home= (char *)"."; +#define mysql_data_home (*int_mysql_data_home) + +#define FLOGGER_SKIP_INCLUDES +#define my_open(A, B, C) loc_open(A, B) +#define my_close(A, B) loc_close(A) +#define my_rename(A, B, C) loc_rename(A, B) +#define my_tell(A, B) loc_tell(A) +#define my_write(A, B, C, D) loc_write(A, B, C) +#define my_malloc(A, B, C) malloc(B) +#define my_free(A) free(A) +#ifdef my_errno + #undef my_errno +#endif +static int loc_file_errno; +#define my_errno loc_file_errno +#ifdef my_vsnprintf + #undef my_vsnprintf +#endif +#define my_vsnprintf vsnprintf +#define logger_open loc_logger_open +#define logger_close loc_logger_close +#define logger_write loc_logger_write +#define logger_rotate loc_logger_rotate +#define logger_init_mutexts loc_logger_init_mutexts +#define logger_time_to_rotate loc_logger_time_to_rotate + + +static size_t loc_write(File Filedes, const uchar *Buffer, size_t Count) +{ + size_t writtenbytes; +#ifdef _WIN32 + writtenbytes= (size_t)_write(Filedes, Buffer, (unsigned int)Count); +#else + writtenbytes= write(Filedes, Buffer, Count); +#endif + return writtenbytes; +} + + +static File loc_open(const char *FileName, int Flags) + /* Path-name of file */ + /* Read | write .. */ + /* Special flags */ +{ + File fd; +#ifdef _WIN32 + HANDLE h; + /* + We could just use _open() here. but prefer to open in unix-similar way + just like my_open() does it on Windows. + This gives atomic multiprocess-safe appends, and possibility to rename + or even delete file while it is open, and CRT lacks this features. + */ + assert(Flags == (O_APPEND | O_CREAT | O_WRONLY)); + h= CreateFile(FileName, FILE_APPEND_DATA, + FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, NULL, + OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (h == INVALID_HANDLE_VALUE) + { + fd= -1; + my_osmaperr(GetLastError()); + } + else + { + fd= _open_osfhandle((intptr)h, O_WRONLY|O_BINARY); + } +#else + fd= open(FileName, Flags, my_umask); +#endif + my_errno= errno; + return fd; +} + + +static int loc_close(File fd) +{ + int err; +#ifndef _WIN32 + do + { + err= close(fd); + } while (err == -1 && errno == EINTR); +#else + err= close(fd); +#endif + my_errno=errno; + return err; +} + + +static int loc_rename(const char *from, const char *to) +{ + int error = 0; + +#if defined(__WIN__) + if (!MoveFileEx(from, to, MOVEFILE_COPY_ALLOWED | + MOVEFILE_REPLACE_EXISTING)) + { + my_osmaperr(GetLastError()); +#elif defined(HAVE_RENAME) + if (rename(from,to)) + { +#else + if (link(from, to) || unlink(from)) + { +#endif + my_errno=errno; + error = -1; + } + return error; +} + + +static my_off_t loc_tell(File fd) +{ + os_off_t pos= IF_WIN(_telli64(fd),lseek(fd, 0, SEEK_CUR)); + if (pos == (os_off_t) -1) + { + my_errno= errno; + } + return (my_off_t) pos; +} + +#ifdef HAVE_PSI_INTERFACE +#undef HAVE_PSI_INTERFACE +#include <mysql/service_logger.h> +#include "../../mysys/file_logger.c" +#define HAVE_PSI_INTERFACE +#else +#include <mysql/service_logger.h> +#include "../../mysys/file_logger.c" +#endif +#endif /*!MARIADB_ONLY*/ + +#undef flogger_mutex_init +#undef flogger_mutex_destroy +#undef flogger_mutex_lock +#undef flogger_mutex_unlock + +#define flogger_mutex_init(A,B,C) pthread_mutex_init(mysql_mutex_real_mutex(B), C) +#define flogger_mutex_destroy(A) pthread_mutex_destroy(mysql_mutex_real_mutex(A)) +#define flogger_mutex_lock(A) pthread_mutex_lock(mysql_mutex_real_mutex(A)) +#define flogger_mutex_unlock(A) pthread_mutex_unlock(mysql_mutex_real_mutex(A)) + +#ifndef DBUG_OFF +#define PLUGIN_DEBUG_VERSION "-debug" +#else +#define PLUGIN_DEBUG_VERSION "" +#endif /*DBUG_OFF*/ +/* + Disable __attribute__() on non-gcc compilers. +*/ +#if !defined(__attribute__) && !defined(__GNUC__) +#define __attribute__(A) +#endif + +#ifdef _WIN32 +#define localtime_r(a, b) localtime_s(b, a) +#endif /*WIN32*/ + + +extern MYSQL_PLUGIN_IMPORT char server_version[]; +static const char *serv_ver= NULL; +static int started_mysql= 0; +static int mysql_57_started= 0; +static int debug_server_started= 0; +static int use_event_data_for_disconnect= 0; +static int started_mariadb= 0; +static int maria_55_started= 0; +static int maria_above_5= 0; +static char *incl_users, *excl_users, + *file_path, *syslog_info; +static char path_buffer[FN_REFLEN]; +static unsigned int mode, mode_readonly= 0; +static ulong output_type; +static ulong syslog_facility, syslog_priority; + +static ulonglong events; /* mask for events to log */ +static unsigned long long file_rotate_size; +static unsigned int rotations; +static my_bool rotate= TRUE; +static char logging; +static volatile int internal_stop_logging= 0; +static char incl_user_buffer[1024]; +static char excl_user_buffer[1024]; +static char *big_buffer= NULL; +static size_t big_buffer_alloced= 0; +static unsigned int query_log_limit= 0; + +static char servhost[256]; +static uint servhost_len; +static char *syslog_ident; +static char syslog_ident_buffer[128]= "mysql-server_auditing"; + +struct connection_info +{ + int header; + unsigned long thread_id; + unsigned long long query_id; + char db[256]; + int db_length; + char user[64]; + int user_length; + char host[64]; + int host_length; + char ip[64]; + int ip_length; + const char *query; + int query_length; + char query_buffer[1024]; + time_t query_time; + int log_always; + char proxy[64]; + int proxy_length; + char proxy_host[64]; + int proxy_host_length; +}; + +#define DEFAULT_FILENAME_LEN 16 +static char default_file_name[DEFAULT_FILENAME_LEN+1]= "server_audit.log"; + +static void update_file_path(MYSQL_THD thd, struct st_mysql_sys_var *var, + void *var_ptr, const void *save); +static void update_file_rotate_size(MYSQL_THD thd, struct st_mysql_sys_var *var, + void *var_ptr, const void *save); +static void update_file_rotations(MYSQL_THD thd, struct st_mysql_sys_var *var, + void *var_ptr, const void *save); +static void update_incl_users(MYSQL_THD thd, struct st_mysql_sys_var *var, + void *var_ptr, const void *save); +static int check_incl_users(MYSQL_THD thd, struct st_mysql_sys_var *var, void *save, + struct st_mysql_value *value); +static int check_excl_users(MYSQL_THD thd, struct st_mysql_sys_var *var, void *save, + struct st_mysql_value *value); +static void update_excl_users(MYSQL_THD thd, struct st_mysql_sys_var *var, + void *var_ptr, const void *save); +static void update_output_type(MYSQL_THD thd, struct st_mysql_sys_var *var, + void *var_ptr, const void *save); +static void update_syslog_facility(MYSQL_THD thd, struct st_mysql_sys_var *var, + void *var_ptr, const void *save); +static void update_syslog_priority(MYSQL_THD thd, struct st_mysql_sys_var *var, + void *var_ptr, const void *save); +static void update_mode(MYSQL_THD thd, struct st_mysql_sys_var *var, + void *var_ptr, const void *save); +static void update_logging(MYSQL_THD thd, struct st_mysql_sys_var *var, + void *var_ptr, const void *save); +static void update_syslog_ident(MYSQL_THD thd, struct st_mysql_sys_var *var, + void *var_ptr, const void *save); +static void rotate_log(MYSQL_THD thd, struct st_mysql_sys_var *var, + void *var_ptr, const void *save); + +static MYSQL_SYSVAR_STR(incl_users, incl_users, PLUGIN_VAR_RQCMDARG, + "Comma separated list of users to monitor.", + check_incl_users, update_incl_users, NULL); +static MYSQL_SYSVAR_STR(excl_users, excl_users, PLUGIN_VAR_RQCMDARG, + "Comma separated list of users to exclude from auditing.", + check_excl_users, update_excl_users, NULL); +/* bits in the event filter. */ +#define EVENT_CONNECT 1 +#define EVENT_QUERY_ALL 2 +#define EVENT_QUERY 122 +#define EVENT_TABLE 4 +#define EVENT_QUERY_DDL 8 +#define EVENT_QUERY_DML 16 +#define EVENT_QUERY_DCL 32 +#define EVENT_QUERY_DML_NO_SELECT 64 + +static const char *event_names[]= +{ + "CONNECT", "QUERY", "TABLE", "QUERY_DDL", "QUERY_DML", "QUERY_DCL", + "QUERY_DML_NO_SELECT", NULL +}; +static TYPELIB events_typelib= +{ + array_elements(event_names) - 1, "", event_names, NULL +}; +static MYSQL_SYSVAR_SET(events, events, PLUGIN_VAR_RQCMDARG, + "Specifies the set of events to monitor. Can be CONNECT, QUERY, TABLE," + " QUERY_DDL, QUERY_DML, QUERY_DML_NO_SELECT, QUERY_DCL.", + NULL, NULL, 0, &events_typelib); +#ifdef DO_SYSLOG +#define OUTPUT_SYSLOG 0 +#define OUTPUT_FILE 1 +#else +#define OUTPUT_SYSLOG 0xFFFF +#define OUTPUT_FILE 0 +#endif /*DO_SYSLOG*/ + +#define OUTPUT_NO 0xFFFF +static const char *output_type_names[]= { +#ifdef DO_SYSLOG + "syslog", +#endif + "file", 0 }; +static TYPELIB output_typelib= +{ + array_elements(output_type_names) - 1, "output_typelib", + output_type_names, NULL +}; +static MYSQL_SYSVAR_ENUM(output_type, output_type, PLUGIN_VAR_RQCMDARG, + out_type_desc, + 0, update_output_type, OUTPUT_FILE, + &output_typelib); +static MYSQL_SYSVAR_STR(file_path, file_path, PLUGIN_VAR_RQCMDARG, + "Path to the log file.", NULL, update_file_path, default_file_name); +static MYSQL_SYSVAR_ULONGLONG(file_rotate_size, file_rotate_size, + PLUGIN_VAR_RQCMDARG, "Maximum size of the log to start the rotation.", + NULL, update_file_rotate_size, + 1000000, 100, ((long long) 0x7FFFFFFFFFFFFFFFLL), 1); +static MYSQL_SYSVAR_UINT(file_rotations, rotations, + PLUGIN_VAR_RQCMDARG, "Number of rotations before log is removed.", + NULL, update_file_rotations, 9, 0, 999, 1); +static MYSQL_SYSVAR_BOOL(file_rotate_now, rotate, PLUGIN_VAR_OPCMDARG, + "Force log rotation now.", NULL, rotate_log, FALSE); +static MYSQL_SYSVAR_BOOL(logging, logging, + PLUGIN_VAR_OPCMDARG, "Turn on/off the logging.", NULL, + update_logging, 0); +static MYSQL_SYSVAR_UINT(mode, mode, + PLUGIN_VAR_OPCMDARG, "Auditing mode.", NULL, update_mode, 0, 0, 1, 1); +static MYSQL_SYSVAR_STR(syslog_ident, syslog_ident, PLUGIN_VAR_RQCMDARG, + "The SYSLOG identifier - the beginning of each SYSLOG record.", + NULL, update_syslog_ident, syslog_ident_buffer); +static MYSQL_SYSVAR_STR(syslog_info, syslog_info, + PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_MEMALLOC, + "The <info> string to be added to the SYSLOG record.", NULL, NULL, ""); +static MYSQL_SYSVAR_UINT(query_log_limit, query_log_limit, + PLUGIN_VAR_OPCMDARG, "Limit on the length of the query string in a record.", + NULL, NULL, 1024, 0, 0x7FFFFFFF, 1); + +char locinfo_ini_value[sizeof(struct connection_info)+4]; + +static MYSQL_THDVAR_STR(loc_info, + PLUGIN_VAR_NOSYSVAR | PLUGIN_VAR_NOCMDOPT | PLUGIN_VAR_MEMALLOC, + "Internal info", NULL, NULL, locinfo_ini_value); + +static const char *syslog_facility_names[]= +{ + "LOG_USER", "LOG_MAIL", "LOG_DAEMON", "LOG_AUTH", + "LOG_SYSLOG", "LOG_LPR", "LOG_NEWS", "LOG_UUCP", + "LOG_CRON", +#ifdef LOG_AUTHPRIV + "LOG_AUTHPRIV", +#endif +#ifdef LOG_FTP + "LOG_FTP", +#endif + "LOG_LOCAL0", "LOG_LOCAL1", "LOG_LOCAL2", "LOG_LOCAL3", + "LOG_LOCAL4", "LOG_LOCAL5", "LOG_LOCAL6", "LOG_LOCAL7", + 0 +}; +#ifndef _WIN32 +static unsigned int syslog_facility_codes[]= +{ + LOG_USER, LOG_MAIL, LOG_DAEMON, LOG_AUTH, + LOG_SYSLOG, LOG_LPR, LOG_NEWS, LOG_UUCP, + LOG_CRON, +#ifdef LOG_AUTHPRIV + LOG_AUTHPRIV, +#endif +#ifdef LOG_FTP + LOG_FTP, +#endif + LOG_LOCAL0, LOG_LOCAL1, LOG_LOCAL2, LOG_LOCAL3, + LOG_LOCAL4, LOG_LOCAL5, LOG_LOCAL6, LOG_LOCAL7, +}; +#endif +static TYPELIB syslog_facility_typelib= +{ + array_elements(syslog_facility_names) - 1, "syslog_facility_typelib", + syslog_facility_names, NULL +}; +static MYSQL_SYSVAR_ENUM(syslog_facility, syslog_facility, PLUGIN_VAR_RQCMDARG, + "The 'facility' parameter of the SYSLOG record." + " The default is LOG_USER.", 0, update_syslog_facility, 0/*LOG_USER*/, + &syslog_facility_typelib); + +static const char *syslog_priority_names[]= +{ + "LOG_EMERG", "LOG_ALERT", "LOG_CRIT", "LOG_ERR", + "LOG_WARNING", "LOG_NOTICE", "LOG_INFO", "LOG_DEBUG", + 0 +}; + +#ifndef _WIN32 +static unsigned int syslog_priority_codes[]= +{ + LOG_EMERG, LOG_ALERT, LOG_CRIT, LOG_ERR, + LOG_WARNING, LOG_NOTICE, LOG_INFO, LOG_DEBUG, +}; +#endif + +static TYPELIB syslog_priority_typelib= +{ + array_elements(syslog_priority_names) - 1, "syslog_priority_typelib", + syslog_priority_names, NULL +}; +static MYSQL_SYSVAR_ENUM(syslog_priority, syslog_priority, PLUGIN_VAR_RQCMDARG, + "The 'priority' parameter of the SYSLOG record." + " The default is LOG_INFO.", 0, update_syslog_priority, 6/*LOG_INFO*/, + &syslog_priority_typelib); + + +static struct st_mysql_sys_var* vars[] = { + MYSQL_SYSVAR(incl_users), + MYSQL_SYSVAR(excl_users), + MYSQL_SYSVAR(events), + MYSQL_SYSVAR(output_type), + MYSQL_SYSVAR(file_path), + MYSQL_SYSVAR(file_rotate_size), + MYSQL_SYSVAR(file_rotations), + MYSQL_SYSVAR(file_rotate_now), + MYSQL_SYSVAR(logging), + MYSQL_SYSVAR(mode), + MYSQL_SYSVAR(syslog_info), + MYSQL_SYSVAR(syslog_ident), + MYSQL_SYSVAR(syslog_facility), + MYSQL_SYSVAR(syslog_priority), + MYSQL_SYSVAR(query_log_limit), + MYSQL_SYSVAR(loc_info), + NULL +}; + + +/* Status variables for SHOW STATUS */ +static int is_active= 0; +static long log_write_failures= 0; +static char current_log_buf[FN_REFLEN]= ""; +static char last_error_buf[512]= ""; + +extern void *mysql_v4_descriptor; + +static struct st_mysql_show_var audit_status[]= +{ + {"server_audit_active", (char *)&is_active, SHOW_BOOL}, + {"server_audit_current_log", current_log_buf, SHOW_CHAR}, + {"server_audit_writes_failed", (char *)&log_write_failures, SHOW_LONG}, + {"server_audit_last_error", last_error_buf, SHOW_CHAR}, + {0,0,0} +}; + +#ifdef HAVE_PSI_INTERFACE +static PSI_mutex_key key_LOCK_operations; +static PSI_mutex_info mutex_key_list[]= +{ + { &key_LOCK_operations, "SERVER_AUDIT_plugin::lock_operations", + PSI_FLAG_GLOBAL} +#ifndef FLOGGER_NO_PSI + , + { &key_LOCK_atomic, "SERVER_AUDIT_plugin::lock_atomic", + PSI_FLAG_GLOBAL}, + { &key_LOCK_bigbuffer, "SERVER_AUDIT_plugin::lock_bigbuffer", + PSI_FLAG_GLOBAL} +#endif /*FLOGGER_NO_PSI*/ +}; +#endif /*HAVE_PSI_INTERFACE*/ +static mysql_prlock_t lock_operations; +static mysql_mutex_t lock_atomic; +static mysql_mutex_t lock_bigbuffer; + +/* The Percona server and partly MySQL don't support */ +/* launching client errors in the 'update_variable' methods. */ +/* So the client errors just disabled for them. */ +/* The possible solution is to implement the 'check_variable'*/ +/* methods there properly, but at the moment i'm not sure it */ +/* worths doing. */ +#define CLIENT_ERROR if (!started_mysql) my_printf_error + +#define ADD_ATOMIC(x, a) \ + do { \ + flogger_mutex_lock(&lock_atomic); \ + x+= a; \ + flogger_mutex_unlock(&lock_atomic); \ + } while (0) + + +static uchar *getkey_user(const char *entry, size_t *length, + my_bool nu __attribute__((unused)) ) +{ + const char *e= entry; + while (*e && *e != ' ' && *e != ',') + ++e; + *length= e - entry; + return (uchar *) entry; +} + + +static void blank_user(char *user) +{ + for (; *user && *user != ','; user++) + *user= ' '; +} + + +static void remove_user(char *user) +{ + char *start_user= user; + while (*user != ',') + { + if (*user == 0) + { + *start_user= 0; + return; + } + user++; + } + user++; + while (*user == ' ') + user++; + + do { + *(start_user++)= *user; + } while (*(user++)); +} + + +static void remove_blanks(char *user) +{ + char *user_orig= user; + char *user_to= user; + char *start_tok; + int blank_name; + + while (*user != 0) + { + start_tok= user; + blank_name= 1; + while (*user !=0 && *user != ',') + { + if (*user != ' ') + blank_name= 0; + user++; + } + if (!blank_name) + { + while (start_tok <= user) + *(user_to++)= *(start_tok++); + } + if (*user == ',') + user++; + } + if (user_to > user_orig && user_to[-1] == ',') + user_to--; + *user_to= 0; +} + + +struct user_name +{ + size_t name_len; + char *name; +}; + + +struct user_coll +{ + int n_users; + struct user_name *users; + int n_alloced; +}; + + +static void coll_init(struct user_coll *c) +{ + c->n_users= 0; + c->users= 0; + c->n_alloced= 0; +} + + +static void coll_free(struct user_coll *c) +{ + if (c->users) + { + free(c->users); + coll_init(c); + } +} + + +static int cmp_users(const void *ia, const void *ib) +{ + const struct user_name *a= (const struct user_name *) ia; + const struct user_name *b= (const struct user_name *) ib; + int dl= (int)(a->name_len - b->name_len); + if (dl != 0) + return dl; + + return strncmp(a->name, b->name, a->name_len); +} + + +static char *coll_search(struct user_coll *c, const char *n, size_t len) +{ + struct user_name un; + struct user_name *found; + if (!c->n_users) + return 0; + un.name_len= len; + un.name= (char *) n; + found= (struct user_name*) bsearch(&un, c->users, c->n_users, + sizeof(c->users[0]), cmp_users); + return found ? found->name : 0; +} + + +static int coll_insert(struct user_coll *c, char *n, size_t len) +{ + if (c->n_users >= c->n_alloced) + { + c->n_alloced+= 128; + if (c->users == NULL) + c->users= malloc(c->n_alloced * sizeof(c->users[0])); + else + c->users= realloc(c->users, c->n_alloced * sizeof(c->users[0])); + + if (c->users == NULL) + return 1; + } + c->users[c->n_users].name= n; + c->users[c->n_users].name_len= len; + c->n_users++; + return 0; +} + + +static void coll_sort(struct user_coll *c) +{ + if (c->n_users) + qsort(c->users, c->n_users, sizeof(c->users[0]), cmp_users); +} + + +static int user_coll_fill(struct user_coll *c, char *users, + struct user_coll *cmp_c, int take_over_cmp) +{ + char *orig_users= users; + char *cmp_user= 0; + size_t cmp_length; + int refill_cmp_coll= 0; + + c->n_users= 0; + + while (*users) + { + while (*users == ' ') + users++; + if (!*users) + return 0; + + (void) getkey_user(users, &cmp_length, FALSE); + if (cmp_c) + { + cmp_user= coll_search(cmp_c, users, cmp_length); + + if (cmp_user && take_over_cmp) + { + ADD_ATOMIC(internal_stop_logging, 1); + CLIENT_ERROR(1, "User '%.*b' was removed from the" + " server_audit_excl_users.", + MYF(ME_WARNING), (int) cmp_length, users); + ADD_ATOMIC(internal_stop_logging, -1); + blank_user(cmp_user); + refill_cmp_coll= 1; + } + else if (cmp_user) + { + ADD_ATOMIC(internal_stop_logging, 1); + CLIENT_ERROR(1, "User '%.*b' is in the server_audit_incl_users, " + "so wasn't added.", MYF(ME_WARNING), (int) cmp_length, users); + ADD_ATOMIC(internal_stop_logging, -1); + remove_user(users); + continue; + } + } + if (coll_insert(c, users, cmp_length)) + return 1; + while (*users && *users != ',') + users++; + if (!*users) + break; + users++; + } + + if (refill_cmp_coll) + { + remove_blanks(excl_users); + return user_coll_fill(cmp_c, excl_users, 0, 0); + } + + if (users > orig_users && users[-1] == ',') + users[-1]= 0; + + coll_sort(c); + + return 0; +} + + +enum sa_keywords +{ + SQLCOM_NOTHING=0, + SQLCOM_DDL, + SQLCOM_DML, + SQLCOM_GRANT, + SQLCOM_CREATE_USER, + SQLCOM_ALTER_USER, + SQLCOM_CHANGE_MASTER, + SQLCOM_CREATE_SERVER, + SQLCOM_SET_OPTION, + SQLCOM_ALTER_SERVER, + SQLCOM_TRUNCATE, + SQLCOM_QUERY_ADMIN, + SQLCOM_DCL, +}; + +struct sa_keyword +{ + int length; + const char *wd; + struct sa_keyword *next; + enum sa_keywords type; +}; + + +struct sa_keyword xml_word= {3, "XML", 0, SQLCOM_NOTHING}; +struct sa_keyword user_word= {4, "USER", 0, SQLCOM_NOTHING}; +struct sa_keyword data_word= {4, "DATA", 0, SQLCOM_NOTHING}; +struct sa_keyword server_word= {6, "SERVER", 0, SQLCOM_NOTHING}; +struct sa_keyword master_word= {6, "MASTER", 0, SQLCOM_NOTHING}; +struct sa_keyword password_word= {8, "PASSWORD", 0, SQLCOM_NOTHING}; +struct sa_keyword function_word= {8, "FUNCTION", 0, SQLCOM_NOTHING}; +struct sa_keyword statement_word= {9, "STATEMENT", 0, SQLCOM_NOTHING}; +struct sa_keyword procedure_word= {9, "PROCEDURE", 0, SQLCOM_NOTHING}; + + +struct sa_keyword keywords_to_skip[]= +{ + {3, "SET", &statement_word, SQLCOM_QUERY_ADMIN}, + {0, NULL, 0, SQLCOM_DDL} +}; + + +struct sa_keyword not_ddl_keywords[]= +{ + {4, "DROP", &user_word, SQLCOM_DCL}, + {6, "CREATE", &user_word, SQLCOM_DCL}, + {6, "RENAME", &user_word, SQLCOM_DCL}, + {0, NULL, 0, SQLCOM_DDL} +}; + + +struct sa_keyword ddl_keywords[]= +{ + {4, "DROP", 0, SQLCOM_DDL}, + {5, "ALTER", 0, SQLCOM_DDL}, + {6, "CREATE", 0, SQLCOM_DDL}, + {6, "RENAME", 0, SQLCOM_DDL}, + {8, "TRUNCATE", 0, SQLCOM_DDL}, + {0, NULL, 0, SQLCOM_DDL} +}; + + +struct sa_keyword dml_keywords[]= +{ + {2, "DO", 0, SQLCOM_DML}, + {4, "CALL", 0, SQLCOM_DML}, + {4, "LOAD", &data_word, SQLCOM_DML}, + {4, "LOAD", &xml_word, SQLCOM_DML}, + {6, "DELETE", 0, SQLCOM_DML}, + {6, "INSERT", 0, SQLCOM_DML}, + {6, "SELECT", 0, SQLCOM_DML}, + {6, "UPDATE", 0, SQLCOM_DML}, + {7, "HANDLER", 0, SQLCOM_DML}, + {7, "REPLACE", 0, SQLCOM_DML}, + {0, NULL, 0, SQLCOM_DML} +}; + + +struct sa_keyword dml_no_select_keywords[]= +{ + {2, "DO", 0, SQLCOM_DML}, + {4, "CALL", 0, SQLCOM_DML}, + {4, "LOAD", &data_word, SQLCOM_DML}, + {4, "LOAD", &xml_word, SQLCOM_DML}, + {6, "DELETE", 0, SQLCOM_DML}, + {6, "INSERT", 0, SQLCOM_DML}, + {6, "UPDATE", 0, SQLCOM_DML}, + {7, "HANDLER", 0, SQLCOM_DML}, + {7, "REPLACE", 0, SQLCOM_DML}, + {0, NULL, 0, SQLCOM_DML} +}; + + +struct sa_keyword dcl_keywords[]= +{ + {6, "CREATE", &user_word, SQLCOM_DCL}, + {4, "DROP", &user_word, SQLCOM_DCL}, + {6, "RENAME", &user_word, SQLCOM_DCL}, + {5, "GRANT", 0, SQLCOM_DCL}, + {6, "REVOKE", 0, SQLCOM_DCL}, + {3, "SET", &password_word, SQLCOM_DCL}, + {0, NULL, 0, SQLCOM_DDL} +}; + + +struct sa_keyword passwd_keywords[]= +{ + {3, "SET", &password_word, SQLCOM_SET_OPTION}, + {5, "ALTER", &server_word, SQLCOM_ALTER_SERVER}, + {5, "ALTER", &user_word, SQLCOM_ALTER_USER}, + {5, "GRANT", 0, SQLCOM_GRANT}, + {6, "CREATE", &user_word, SQLCOM_CREATE_USER}, + {6, "CREATE", &server_word, SQLCOM_CREATE_SERVER}, + {6, "CHANGE", &master_word, SQLCOM_CHANGE_MASTER}, + {0, NULL, 0, SQLCOM_NOTHING} +}; + +#define MAX_KEYWORD 9 + + +static void error_header() +{ + struct tm tm_time; + time_t curtime; + + (void) time(&curtime); + (void) localtime_r(&curtime, &tm_time); + + (void) fprintf(stderr,"%02d%02d%02d %2d:%02d:%02d server_audit: ", + tm_time.tm_year % 100, tm_time.tm_mon + 1, + tm_time.tm_mday, tm_time.tm_hour, tm_time.tm_min, tm_time.tm_sec); +} + + +static LOGGER_HANDLE *logfile; +static struct user_coll incl_user_coll, excl_user_coll; +static unsigned long long query_counter= 1; + + +static struct connection_info *get_loc_info(MYSQL_THD thd) +{ + return (struct connection_info *) THDVAR(thd, loc_info); +} + + +static int ci_needs_setup(const struct connection_info *ci) +{ + return ci->header != 0; +} + + +static void get_str_n(char *dest, int *dest_len, size_t dest_size, + const char *src, size_t src_len) +{ + if (src_len >= dest_size) + src_len= dest_size - 1; + + if (src_len) + memcpy(dest, src, src_len); + dest[src_len]= 0; + *dest_len= (int)src_len; +} + + +static int get_user_host(const char *uh_line, unsigned int uh_len, + char *buffer, size_t buf_len, + size_t *user_len, size_t *host_len, size_t *ip_len) +{ + const char *buf_end= buffer + buf_len - 1; + const char *buf_start; + const char *uh_end= uh_line + uh_len; + + while (uh_line < uh_end && *uh_line != '[') + ++uh_line; + + if (uh_line == uh_end) + return 1; + ++uh_line; + + buf_start= buffer; + while (uh_line < uh_end && *uh_line != ']') + { + if (buffer == buf_end) + return 1; + *(buffer++)= *(uh_line++); + } + if (uh_line == uh_end) + return 1; + *user_len= buffer - buf_start; + *(buffer++)= 0; + + while (uh_line < uh_end && *uh_line != '@') + ++uh_line; + if (uh_line == uh_end || *(++uh_line) == 0) + return 1; + ++uh_line; + + buf_start= buffer; + while (uh_line < uh_end && *uh_line != ' ' && *uh_line != '[') + { + if (buffer == buf_end) + break; + *(buffer++)= *(uh_line++); + } + *host_len= buffer - buf_start; + *(buffer++)= 0; + + while (uh_line < uh_end && *uh_line != '[') + ++uh_line; + + buf_start= buffer; + if (*uh_line == '[') + { + ++uh_line; + while (uh_line < uh_end && *uh_line != ']') + *(buffer++)= *(uh_line++); + } + *ip_len= buffer - buf_start; + return 0; +} + +#if defined(__WIN__) && !defined(S_ISDIR) +#define S_ISDIR(x) ((x) & _S_IFDIR) +#endif /*__WIN__ && !S_ISDIR*/ + +static int start_logging() +{ + last_error_buf[0]= 0; + log_write_failures= 0; + if (output_type == OUTPUT_FILE) + { + char alt_path_buffer[FN_REFLEN+1+DEFAULT_FILENAME_LEN]; + struct stat *f_stat= (struct stat *)alt_path_buffer; + const char *alt_fname= file_path; + + while (*alt_fname == ' ') + alt_fname++; + + if (*alt_fname == 0) + { + /* Empty string means the default file name. */ + alt_fname= default_file_name; + } + else + { + /* See if the directory exists with the name of file_path. */ + /* Log file name should be [file_path]/server_audit.log then. */ + if (stat(file_path, (struct stat *)alt_path_buffer) == 0 && + S_ISDIR(f_stat->st_mode)) + { + size_t p_len= strlen(file_path); + memcpy(alt_path_buffer, file_path, p_len); + if (alt_path_buffer[p_len-1] != FN_LIBCHAR) + { + alt_path_buffer[p_len]= FN_LIBCHAR; + p_len++; + } + memcpy(alt_path_buffer+p_len, default_file_name, DEFAULT_FILENAME_LEN); + alt_path_buffer[p_len+DEFAULT_FILENAME_LEN]= 0; + alt_fname= alt_path_buffer; + } + } + + logfile= logger_open(alt_fname, file_rotate_size, rotations); + + if (logfile == NULL) + { + error_header(); + fprintf(stderr, "Could not create file '%s'.\n", + alt_fname); + logging= 0; + my_snprintf(last_error_buf, sizeof(last_error_buf), + "Could not create file '%s'.", alt_fname); + is_active= 0; + CLIENT_ERROR(1, "SERVER AUDIT plugin can't create file '%s'.", + MYF(ME_WARNING), alt_fname); + return 1; + } + error_header(); + fprintf(stderr, "logging started to the file %s.\n", alt_fname); + strncpy(current_log_buf, alt_fname, sizeof(current_log_buf)-1); + current_log_buf[sizeof(current_log_buf)-1]= 0; + } + else if (output_type == OUTPUT_SYSLOG) + { + openlog(syslog_ident, LOG_NOWAIT, syslog_facility_codes[syslog_facility]); + error_header(); + fprintf(stderr, "logging started to the syslog.\n"); + strncpy(current_log_buf, "[SYSLOG]", sizeof(current_log_buf)-1); + compile_time_assert(sizeof current_log_buf > sizeof "[SYSLOG]"); + } + is_active= 1; + return 0; +} + + +static int stop_logging() +{ + last_error_buf[0]= 0; + if (output_type == OUTPUT_FILE && logfile) + { + logger_close(logfile); + logfile= NULL; + } + else if (output_type == OUTPUT_SYSLOG) + { + closelog(); + } + error_header(); + fprintf(stderr, "logging was stopped.\n"); + is_active= 0; + return 0; +} + + +static void setup_connection_simple(struct connection_info *ci) +{ + ci->db_length= 0; + ci->user_length= 0; + ci->host_length= 0; + ci->ip_length= 0; + ci->query_length= 0; + ci->header= 0; + ci->proxy_length= 0; +} + + +#define MAX_HOSTNAME 61 +#define USERNAME_LENGTH 384 + +static void setup_connection_connect(struct connection_info *cn, + const struct mysql_event_connection *event) +{ + cn->query_id= 0; + cn->query_length= 0; + cn->log_always= 0; + cn->thread_id= event->thread_id; + get_str_n(cn->db, &cn->db_length, sizeof(cn->db), + event->database.str, event->database.length); + get_str_n(cn->user, &cn->user_length, sizeof(cn->db), + event->user, event->user_length); + get_str_n(cn->host, &cn->host_length, sizeof(cn->host), + event->host, event->host_length); + get_str_n(cn->ip, &cn->ip_length, sizeof(cn->ip), + event->ip, event->ip_length); + cn->header= 0; + if (event->proxy_user && event->proxy_user[0]) + { + const char *priv_host= event->proxy_user + + sizeof(char[MAX_HOSTNAME+USERNAME_LENGTH+5]); + size_t priv_host_length; + + if (mysql_57_started) + { + priv_host+= sizeof(size_t); + priv_host_length= *(size_t *) (priv_host + MAX_HOSTNAME); + } + else + priv_host_length= strlen(priv_host); + + + get_str_n(cn->proxy, &cn->proxy_length, sizeof(cn->proxy), + event->priv_user, event->priv_user_length); + get_str_n(cn->proxy_host, &cn->proxy_host_length, + sizeof(cn->proxy_host), + priv_host, priv_host_length); + } + else + cn->proxy_length= 0; +} + + +#define SAFE_STRLEN(s) (s ? strlen(s) : 0) +#define SAFE_STRLEN_UI(s) ((unsigned int) (s ? strlen(s) : 0)) +static char empty_str[1]= { 0 }; + + +static int is_space(char c) +{ + return c == ' ' || c == '\r' || c == '\n' || c == '\t'; +} + + +#define SKIP_SPACES(str) \ +do { \ + while (is_space(*str)) \ + ++str; \ +} while(0) + + +#define ESC_MAP_SIZE 0x60 +static const char esc_map[ESC_MAP_SIZE]= +{ + 0, 0, 0, 0, 0, 0, 0, 0, 'b', 't', 'n', 0, 'f', 'r', 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, '\'', 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '\\', 0, 0, 0 +}; + +static char escaped_char(char c) +{ + return ((unsigned char ) c) >= ESC_MAP_SIZE ? 0 : esc_map[(unsigned char) c]; +} + + +static void setup_connection_initdb(struct connection_info *cn, + const struct mysql_event_general *event) +{ + size_t user_len, host_len, ip_len; + char uh_buffer[512]; + + cn->thread_id= event->general_thread_id; + cn->query_id= 0; + cn->query_length= 0; + cn->log_always= 0; + get_str_n(cn->db, &cn->db_length, sizeof(cn->db), + event->general_query, event->general_query_length); + + if (get_user_host(event->general_user, event->general_user_length, + uh_buffer, sizeof(uh_buffer), + &user_len, &host_len, &ip_len)) + { + /* The user@host line is incorrect. */ + cn->user_length= 0; + cn->host_length= 0; + cn->ip_length= 0; + } + else + { + get_str_n(cn->user, &cn->user_length, sizeof(cn->user), + uh_buffer, user_len); + get_str_n(cn->host, &cn->host_length, sizeof(cn->host), + uh_buffer+user_len+1, host_len); + get_str_n(cn->ip, &cn->ip_length, sizeof(cn->ip), + uh_buffer+user_len+1+host_len+1, ip_len); + } + cn->header= 0; +} + + +static void setup_connection_table(struct connection_info *cn, + const struct mysql_event_table *event) +{ + cn->thread_id= event->thread_id; + cn->query_id= query_counter++; + cn->log_always= 0; + cn->query_length= 0; + get_str_n(cn->db, &cn->db_length, sizeof(cn->db), + event->database.str, event->database.length); + get_str_n(cn->user, &cn->user_length, sizeof(cn->db), + event->user, SAFE_STRLEN(event->user)); + get_str_n(cn->host, &cn->host_length, sizeof(cn->host), + event->host, SAFE_STRLEN(event->host)); + get_str_n(cn->ip, &cn->ip_length, sizeof(cn->ip), + event->ip, SAFE_STRLEN(event->ip)); + cn->header= 0; +} + + +static void setup_connection_query(struct connection_info *cn, + const struct mysql_event_general *event) +{ + size_t user_len, host_len, ip_len; + char uh_buffer[512]; + + cn->thread_id= event->general_thread_id; + cn->query_id= query_counter++; + cn->log_always= 0; + cn->query_length= 0; + get_str_n(cn->db, &cn->db_length, sizeof(cn->db), "", 0); + + if (get_user_host(event->general_user, event->general_user_length, + uh_buffer, sizeof(uh_buffer), + &user_len, &host_len, &ip_len)) + { + /* The user@host line is incorrect. */ + cn->user_length= 0; + cn->host_length= 0; + cn->ip_length= 0; + } + else + { + get_str_n(cn->user, &cn->user_length, sizeof(cn->user), + uh_buffer, user_len); + get_str_n(cn->host, &cn->host_length, sizeof(cn->host), + uh_buffer+user_len+1, host_len); + get_str_n(cn->ip, &cn->ip_length, sizeof(cn->ip), + uh_buffer+user_len+1+host_len+1, ip_len); + } + cn->header= 0; +} + + +static void change_connection(struct connection_info *cn, + const struct mysql_event_connection *event) +{ + get_str_n(cn->user, &cn->user_length, sizeof(cn->user), + event->user, event->user_length); + get_str_n(cn->ip, &cn->ip_length, sizeof(cn->ip), + event->ip, event->ip_length); +} + +/* + Write to the log + + @param take_lock If set, take a read lock (or write lock on rotate). + If not set, the caller has a already taken a write lock +*/ + +static int write_log(const char *message, size_t len, int take_lock) +{ + int result= 0; + if (take_lock) + { + /* Start by taking a read lock */ + mysql_prlock_rdlock(&lock_operations); + } + + if (output_type == OUTPUT_FILE) + { + if (logfile) + { + my_bool allow_rotate= !take_lock; /* Allow rotate if caller write lock */ + if (take_lock && logger_time_to_rotate(logfile)) + { + /* We have to rotate the log, change above read lock to write lock */ + mysql_prlock_unlock(&lock_operations); + mysql_prlock_wrlock(&lock_operations); + allow_rotate= 1; + } + if (!(is_active= (logger_write_r(logfile, allow_rotate, message, len) == + (int) len))) + { + ++log_write_failures; + result= 1; + } + } + } + else if (output_type == OUTPUT_SYSLOG) + { + syslog(syslog_facility_codes[syslog_facility] | + syslog_priority_codes[syslog_priority], + "%s %.*s", syslog_info, (int) len, message); + } + if (take_lock) + mysql_prlock_unlock(&lock_operations); + return result; +} + + +static size_t log_header(char *message, size_t message_len, + time_t *ts, + const char *serverhost, size_t serverhost_len, + const char *username, unsigned int username_len, + const char *host, unsigned int host_len, + const char *userip, unsigned int userip_len, + unsigned int connection_id, long long query_id, + const char *operation) +{ + struct tm tm_time; + + if (host_len == 0 && userip_len != 0) + { + host_len= userip_len; + host= userip; + } + + if (output_type == OUTPUT_SYSLOG) + return my_snprintf(message, message_len, + "%.*s,%.*s,%.*s,%d,%lld,%s", + (unsigned int) serverhost_len, serverhost, + username_len, username, + host_len, host, + connection_id, query_id, operation); + + (void) localtime_r(ts, &tm_time); + return my_snprintf(message, message_len, + "%04d%02d%02d %02d:%02d:%02d,%.*s,%.*s,%.*s,%d,%lld,%s", + tm_time.tm_year+1900, tm_time.tm_mon+1, tm_time.tm_mday, + tm_time.tm_hour, tm_time.tm_min, tm_time.tm_sec, + serverhost_len, serverhost, + username_len, username, + host_len, host, + connection_id, query_id, operation); +} + + +static int log_proxy(const struct connection_info *cn, + const struct mysql_event_connection *event) + +{ + time_t ctime; + size_t csize; + char message[1024]; + + (void) time(&ctime); + csize= log_header(message, sizeof(message)-1, &ctime, + servhost, servhost_len, + cn->user, cn->user_length, + cn->host, cn->host_length, + cn->ip, cn->ip_length, + event->thread_id, 0, "PROXY_CONNECT"); + csize+= my_snprintf(message+csize, sizeof(message) - 1 - csize, + ",%.*s,`%.*s`@`%.*s`,%d", cn->db_length, cn->db, + cn->proxy_length, cn->proxy, + cn->proxy_host_length, cn->proxy_host, + event->status); + message[csize]= '\n'; + return write_log(message, csize + 1, 1); +} + + +static int log_connection(const struct connection_info *cn, + const struct mysql_event_connection *event, + const char *type) +{ + time_t ctime; + size_t csize; + char message[1024]; + + (void) time(&ctime); + csize= log_header(message, sizeof(message)-1, &ctime, + servhost, servhost_len, + cn->user, cn->user_length, + cn->host, cn->host_length, + cn->ip, cn->ip_length, + event->thread_id, 0, type); + csize+= my_snprintf(message+csize, sizeof(message) - 1 - csize, + ",%.*s,,%d", cn->db_length, cn->db, event->status); + message[csize]= '\n'; + return write_log(message, csize + 1, 1); +} + + +static int log_connection_event(const struct mysql_event_connection *event, + const char *type) +{ + time_t ctime; + size_t csize; + char message[1024]; + + (void) time(&ctime); + csize= log_header(message, sizeof(message)-1, &ctime, + servhost, servhost_len, + event->user, event->user_length, + event->host, event->host_length, + event->ip, event->ip_length, + event->thread_id, 0, type); + csize+= my_snprintf(message+csize, sizeof(message) - 1 - csize, + ",%.*s,,%d", event->database.length, event->database.str, event->status); + message[csize]= '\n'; + return write_log(message, csize + 1, 1); +} + + +static size_t escape_string(const char *str, unsigned int len, + char *result, size_t result_len) +{ + const char *res_start= result; + const char *res_end= result + result_len - 2; + while (len) + { + char esc_c; + + if (result >= res_end) + break; + if ((esc_c= escaped_char(*str))) + { + if (result+1 >= res_end) + break; + *(result++)= '\\'; + *(result++)= esc_c; + } + else if (is_space(*str)) + *(result++)= ' '; + else + *(result++)= *str; + str++; + len--; + } + *result= 0; + return result - res_start; +} + + +static size_t escape_string_hide_passwords(const char *str, unsigned int len, + char *result, size_t result_len, + const char *word1, size_t word1_len, + const char *word2, size_t word2_len, + int next_text_string) +{ + const char *res_start= result; + const char *res_end= result + result_len - 2; + size_t d_len; + + while (len) + { + if (len > word1_len + 1 && strncasecmp(str, word1, word1_len) == 0) + { + const char *next_s= str + word1_len; + size_t c; + + if (next_text_string) + { + while (*next_s && *next_s != '\'' && *next_s != '"') + ++next_s; + } + else + { + if (word2) + { + SKIP_SPACES(next_s); + if (len < (next_s - str) + word2_len + 1 || + strncasecmp(next_s, word2, word2_len) != 0) + goto no_password; + next_s+= word2_len; + } + + while (*next_s && *next_s != '\'' && *next_s != '"') + ++next_s; + } + + d_len= next_s - str; + if (result + d_len + 5 > res_end) + break; + + for (c=0; c<d_len; c++) + result[c]= is_space(str[c]) ? ' ' : str[c]; + + if (*next_s) + { + const char b_char= *next_s++; + memset(result + d_len, '*', 5); + result+= d_len + 5; + + while (*next_s) + { + if (*next_s == b_char) + { + ++next_s; + break; + } + if (*next_s == '\\') + { + if (next_s[1]) + next_s++; + } + next_s++; + } + } + else + result+= d_len; + + len-= (uint)(next_s - str); + str= next_s; + continue; + } +no_password: + if (result >= res_end) + break; + else + { + const char b_char= escaped_char(*str); + if (b_char) + { + if (result+1 >= res_end) + break; + *(result++)= '\\'; + *(result++)= b_char; + } + else if (is_space(*str)) + *(result++)= ' '; + else + *(result++)= *str; + str++; + len--; + } + } + *result= 0; + return result - res_start; +} + + + +static int do_log_user(const char *name, int len, + const char *proxy, int proxy_len, int take_lock) +{ + int result; + + if (!name) + return 0; + + if (take_lock) + mysql_prlock_rdlock(&lock_operations); + + if (incl_user_coll.n_users) + { + result= coll_search(&incl_user_coll, name, len) != 0 || + (proxy && coll_search(&incl_user_coll, proxy, proxy_len) != 0); + } + else if (excl_user_coll.n_users) + { + result= coll_search(&excl_user_coll, name, len) == 0 && + (proxy && coll_search(&excl_user_coll, proxy, proxy_len) == 0); + } + else + result= 1; + + if (take_lock) + mysql_prlock_unlock(&lock_operations); + return result; +} + + +static int get_next_word(const char *query, char *word) +{ + int len= 0; + char c; + while ((c= query[len])) + { + if (c >= 'a' && c <= 'z') + word[len]= 'A' + (c-'a'); + else if (c >= 'A' && c <= 'Z') + word[len]= c; + else + break; + + if (len++ == MAX_KEYWORD) + return 0; + } + word[len]= 0; + return len; +} + + +static int filter_query_type(const char *query, struct sa_keyword *kwd) +{ + int qwe_in_list; + char fword[MAX_KEYWORD + 1], nword[MAX_KEYWORD + 1]; + int len, nlen= 0; + const struct sa_keyword *l_keywords; + + while (*query && (is_space(*query) || *query == '(' || *query == '/')) + { + /* comment handling */ + if (*query == '/' && query[1] == '*') + { + if (query[2] == '!') + { + query+= 3; + while (*query >= '0' && *query <= '9') + query++; + continue; + } + query+= 2; + while (*query) + { + if (*query=='*' && query[1] == '/') + { + query+= 2; + break; + } + query++; + } + continue; + } + query++; + } + + qwe_in_list= 0; + if (!(len= get_next_word(query, fword))) + goto not_in_list; + query+= len+1; + + l_keywords= kwd; + while (l_keywords->length) + { + if (l_keywords->length == len && strncmp(l_keywords->wd, fword, len) == 0) + { + if (l_keywords->next) + { + if (nlen == 0) + { + while (*query && is_space(*query)) + query++; + nlen= get_next_word(query, nword); + } + if (l_keywords->next->length != nlen || + strncmp(l_keywords->next->wd, nword, nlen) != 0) + goto do_loop; + } + + qwe_in_list= l_keywords->type; + break; + }; +do_loop: + l_keywords++; + } + +not_in_list: + return qwe_in_list; +} + + +static int log_statement_ex(const struct connection_info *cn, + time_t ev_time, unsigned long thd_id, + const char *query, unsigned int query_len, + int error_code, const char *type, int take_lock) +{ + size_t csize; + char message_loc[1024]; + char *message= message_loc; + size_t message_size= sizeof(message_loc); + char *uh_buffer; + size_t uh_buffer_size; + const char *db; + unsigned int db_length; + long long query_id; + int result; + + if ((db= cn->db)) + db_length= cn->db_length; + else + { + db= ""; + db_length= 0; + } + + if (!(query_id= cn->query_id)) + query_id= query_counter++; + + if (query == 0) + { + /* Can happen after the error in mysqld_prepare_stmt() */ + query= cn->query; + query_len= cn->query_length; + if (query == 0 || query_len == 0) + return 0; + } + + if (query && !(events & EVENT_QUERY_ALL) && + (events & EVENT_QUERY && !cn->log_always)) + { + const char *orig_query= query; + + if (filter_query_type(query, keywords_to_skip)) + { + char fword[MAX_KEYWORD + 1]; + int len; + do + { + len= get_next_word(query, fword); + query+= len ? len : 1; + if (len == 3 && strncmp(fword, "FOR", 3) == 0) + break; + } while (*query); + + if (*query == 0) + return 0; + } + + if (events & EVENT_QUERY_DDL) + { + if (!filter_query_type(query, not_ddl_keywords) && + filter_query_type(query, ddl_keywords)) + goto do_log_query; + } + if (events & EVENT_QUERY_DML) + { + if (filter_query_type(query, dml_keywords)) + goto do_log_query; + } + if (events & EVENT_QUERY_DML_NO_SELECT) + { + if (filter_query_type(query, dml_no_select_keywords)) + goto do_log_query; + } + if (events & EVENT_QUERY_DCL) + { + if (filter_query_type(query, dcl_keywords)) + goto do_log_query; + } + + return 0; +do_log_query: + query= orig_query; + } + + csize= log_header(message, message_size-1, &ev_time, + servhost, servhost_len, + cn->user, cn->user_length,cn->host, cn->host_length, + cn->ip, cn->ip_length, thd_id, query_id, type); + + csize+= my_snprintf(message+csize, message_size - 1 - csize, + ",%.*s,\'", db_length, db); + + if (query_log_limit > 0 && query_len > query_log_limit) + query_len= query_log_limit; + + if (query_len > (message_size - csize)/2) + { + flogger_mutex_lock(&lock_bigbuffer); + if (big_buffer_alloced < (query_len * 2 + csize)) + { + big_buffer_alloced= (query_len * 2 + csize + 4095) & ~4095L; + big_buffer= realloc(big_buffer, big_buffer_alloced); + if (big_buffer == NULL) + { + big_buffer_alloced= 0; + return 0; + } + } + + memcpy(big_buffer, message, csize); + message= big_buffer; + message_size= big_buffer_alloced; + } + + uh_buffer= message + csize; + uh_buffer_size= message_size - csize; + if (query_log_limit > 0 && uh_buffer_size > query_log_limit+2) + uh_buffer_size= query_log_limit+2; + + switch (filter_query_type(query, passwd_keywords)) + { + case SQLCOM_GRANT: + case SQLCOM_CREATE_USER: + case SQLCOM_ALTER_USER: + csize+= escape_string_hide_passwords(query, query_len, + uh_buffer, uh_buffer_size, + "IDENTIFIED", 10, "BY", 2, 0); + break; + case SQLCOM_CHANGE_MASTER: + csize+= escape_string_hide_passwords(query, query_len, + uh_buffer, uh_buffer_size, + "MASTER_PASSWORD", 15, "=", 1, 0); + break; + case SQLCOM_CREATE_SERVER: + case SQLCOM_ALTER_SERVER: + csize+= escape_string_hide_passwords(query, query_len, + uh_buffer, uh_buffer_size, + "PASSWORD", 8, NULL, 0, 0); + break; + case SQLCOM_SET_OPTION: + csize+= escape_string_hide_passwords(query, query_len, + uh_buffer, uh_buffer_size, + "=", 1, NULL, 0, 1); + break; + default: + csize+= escape_string(query, query_len, + uh_buffer, uh_buffer_size); + break; + } + csize+= my_snprintf(message+csize, message_size - 1 - csize, + "\',%d", error_code); + message[csize]= '\n'; + result= write_log(message, csize + 1, take_lock); + if (message == big_buffer) + flogger_mutex_unlock(&lock_bigbuffer); + + return result; +} + + +static int log_statement(const struct connection_info *cn, + const struct mysql_event_general *event, + const char *type) +{ + return log_statement_ex(cn, event->general_time, event->general_thread_id, + event->general_query, event->general_query_length, + event->general_error_code, type, 1); +} + + +static int log_table(const struct connection_info *cn, + const struct mysql_event_table *event, const char *type) +{ + size_t csize; + char message[1024]; + time_t ctime; + + (void) time(&ctime); + csize= log_header(message, sizeof(message)-1, &ctime, + servhost, servhost_len, + event->user, SAFE_STRLEN_UI(event->user), + event->host, SAFE_STRLEN_UI(event->host), + event->ip, SAFE_STRLEN_UI(event->ip), + event->thread_id, cn->query_id, type); + csize+= my_snprintf(message+csize, sizeof(message) - 1 - csize, + ",%.*s,%.*s,",event->database.length, event->database.str, + event->table.length, event->table.str); + message[csize]= '\n'; + return write_log(message, csize + 1, 1); +} + + +static int log_rename(const struct connection_info *cn, + const struct mysql_event_table *event) +{ + size_t csize; + char message[1024]; + time_t ctime; + + (void) time(&ctime); + csize= log_header(message, sizeof(message)-1, &ctime, + servhost, servhost_len, + event->user, SAFE_STRLEN_UI(event->user), + event->host, SAFE_STRLEN_UI(event->host), + event->ip, SAFE_STRLEN_UI(event->ip), + event->thread_id, cn->query_id, "RENAME"); + csize+= my_snprintf(message+csize, sizeof(message) - 1 - csize, + ",%.*s,%.*s|%.*s.%.*s,",event->database.length, event->database.str, + event->table.length, event->table.str, + event->new_database.length, event->new_database.str, + event->new_table.length, event->new_table.str); + message[csize]= '\n'; + return write_log(message, csize + 1, 1); +} + + +static int event_query_command(const struct mysql_event_general *event) +{ + return (event->general_command_length == 5 && + strncmp(event->general_command, "Query", 5) == 0) || + (event->general_command_length == 7 && + (strncmp(event->general_command, "Execute", 7) == 0 || + (event->general_error_code != 0 && + strncmp(event->general_command, "Prepare", 7) == 0))); +} + + +static void update_general_user(struct connection_info *cn, + const struct mysql_event_general *event) +{ + char uh_buffer[768]; + size_t user_len, host_len, ip_len; + if (cn->user_length == 0 && cn->host_length == 0 && cn->ip_length == 0 && + get_user_host(event->general_user, event->general_user_length, + uh_buffer, sizeof(uh_buffer), + &user_len, &host_len, &ip_len) == 0) + { + get_str_n(cn->user, &cn->user_length, sizeof(cn->user), + uh_buffer, user_len); + get_str_n(cn->host, &cn->host_length, sizeof(cn->host), + uh_buffer+user_len+1, host_len); + get_str_n(cn->ip, &cn->ip_length, sizeof(cn->ip), + uh_buffer+user_len+1+host_len+1, ip_len); + } + +} + + +static struct connection_info ci_disconnect_buffer; + +#define AA_FREE_CONNECTION 1 +#define AA_CHANGE_USER 2 + +static void update_connection_info(struct connection_info *cn, + unsigned int event_class, const void *ev, int *after_action) +{ + *after_action= 0; + + switch (event_class) { + case MYSQL_AUDIT_GENERAL_CLASS: + { + const struct mysql_event_general *event = + (const struct mysql_event_general *) ev; + switch (event->event_subclass) { + case MYSQL_AUDIT_GENERAL_LOG: + { + int init_db_command= event->general_command_length == 7 && + strncmp(event->general_command, "Init DB", 7) == 0; + if (!ci_needs_setup(cn)) + { + if (init_db_command) + { + /* Change DB */ + if (mysql_57_started) + get_str_n(cn->db, &cn->db_length, sizeof(cn->db), + event->database.str, event->database.length); + else + get_str_n(cn->db, &cn->db_length, sizeof(cn->db), + event->general_query, event->general_query_length); + } + cn->query_id= mode ? query_counter++ : event->query_id; + cn->query= event->general_query; + cn->query_length= event->general_query_length; + cn->query_time= (time_t) event->general_time; + update_general_user(cn, event); + } + else if (init_db_command) + setup_connection_initdb(cn, event); + else if (event_query_command(event)) + setup_connection_query(cn, event); + else + setup_connection_simple(cn); + break; + } + + case MYSQL_AUDIT_GENERAL_STATUS: + if (event_query_command(event)) + { + if (ci_needs_setup(cn)) + setup_connection_query(cn, event); + + if (mode == 0 && cn->db_length == 0 && event->database.length > 0) + get_str_n(cn->db, &cn->db_length, sizeof(cn->db), + event->database.str, event->database.length); + + if (event->general_error_code == 0) + { + /* We need to check if it's the USE command to change the DB */ + int use_command= event->general_query_length > 4 && + strncasecmp(event->general_query, "use ", 4) == 0; + if (use_command) + { + /* Change DB */ + if (mode) + get_str_n(cn->db, &cn->db_length, sizeof(cn->db), + event->general_query + 4, event->general_query_length - 4); + else + get_str_n(cn->db, &cn->db_length, sizeof(cn->db), + event->database.str, event->database.length); + } + } + update_general_user(cn, event); + } + break; + case MYSQL_AUDIT_GENERAL_ERROR: + /* + We need this because the MariaDB returns NULL query field for the + MYSQL_AUDIT_GENERAL_STATUS in the mysqld_stmt_prepare. + As a result we get empty QUERY field for errors. + */ + if (ci_needs_setup(cn)) + setup_connection_query(cn, event); + cn->query_id= mode ? query_counter++ : event->query_id; + get_str_n(cn->query_buffer, &cn->query_length, sizeof(cn->query_buffer), + event->general_query, event->general_query_length); + cn->query= cn->query_buffer; + cn->query_time= (time_t) event->general_time; + break; + default:; + } + break; + } + case MYSQL_AUDIT_TABLE_CLASS: + { + const struct mysql_event_table *event = + (const struct mysql_event_table *) ev; + if (ci_needs_setup(cn)) + setup_connection_table(cn, event); + + if (cn->user_length == 0 && cn->host_length == 0 && cn->ip_length == 0) + { + get_str_n(cn->user, &cn->user_length, sizeof(cn->user), + event->user, SAFE_STRLEN(event->user)); + get_str_n(cn->host, &cn->host_length, sizeof(cn->host), + event->host, SAFE_STRLEN(event->host)); + get_str_n(cn->ip, &cn->ip_length, sizeof(cn->ip), + event->ip, SAFE_STRLEN(event->ip)); + } + + if (cn->db_length == 0 && event->database.length != 0) + get_str_n(cn->db, &cn->db_length, sizeof(cn->db), + event->database.str, event->database.length); + + if (mode == 0) + cn->query_id= event->query_id; + break; + } + case MYSQL_AUDIT_CONNECTION_CLASS: + { + const struct mysql_event_connection *event = + (const struct mysql_event_connection *) ev; + switch (event->event_subclass) + { + case MYSQL_AUDIT_CONNECTION_CONNECT: + setup_connection_connect(cn, event); + break; + case MYSQL_AUDIT_CONNECTION_CHANGE_USER: + *after_action= AA_CHANGE_USER; + break; + default:; + } + break; + } + default: + break; + } +} + + +struct connection_info cn_error_buffer; + + +#define FILTER(MASK) (events == 0 || (events & MASK)) +void auditing(MYSQL_THD thd, unsigned int event_class, const void *ev) +{ + struct connection_info *cn= 0; + int after_action= 0; + + /* That one is important as this function can be called with */ + /* &lock_operations locked when the server logs an error reported */ + /* by this plugin. */ + if (!thd || internal_stop_logging) + return; + + if (maria_55_started && debug_server_started && + event_class == MYSQL_AUDIT_GENERAL_CLASS) + { + /* + There's a bug in MariaDB 5.5 that prevents using thread local + variables in some cases. + The 'select * from notexisting_table;' query produces such case. + So just use the static buffer in this case. + */ + const struct mysql_event_general *event = + (const struct mysql_event_general *) ev; + + if (event->event_subclass == MYSQL_AUDIT_GENERAL_ERROR || + (event->event_subclass == MYSQL_AUDIT_GENERAL_STATUS && + event->general_query_length == 0 && + cn_error_buffer.query_id == event->query_id)) + { + cn= &cn_error_buffer; + cn->header= 1; + } + else + cn= get_loc_info(thd); + } + else + { + cn= get_loc_info(thd); + } + + update_connection_info(cn, event_class, ev, &after_action); + + if (!logging) + { + if (cn) + cn->log_always= 0; + goto exit_func; + } + + if (event_class == MYSQL_AUDIT_GENERAL_CLASS && FILTER(EVENT_QUERY) && + cn && (cn->log_always || do_log_user(cn->user, cn->user_length, + cn->proxy, cn->proxy_length, + 1))) + { + const struct mysql_event_general *event = + (const struct mysql_event_general *) ev; + + /* + Only one subclass is logged. + */ + if (event->event_subclass == MYSQL_AUDIT_GENERAL_STATUS && + event_query_command(event)) + { + log_statement(cn, event, "QUERY"); + cn->query_length= 0; /* So the log_current_query() won't log this again. */ + cn->log_always= 0; + } + } + else if (event_class == MYSQL_AUDIT_TABLE_CLASS && FILTER(EVENT_TABLE) && cn) + { + const struct mysql_event_table *event = + (const struct mysql_event_table *) ev; + if (do_log_user(event->user, (int) SAFE_STRLEN(event->user), + cn->proxy, cn->proxy_length, 1)) + { + switch (event->event_subclass) + { + case MYSQL_AUDIT_TABLE_LOCK: + log_table(cn, event, event->read_only ? "READ" : "WRITE"); + break; + case MYSQL_AUDIT_TABLE_CREATE: + log_table(cn, event, "CREATE"); + break; + case MYSQL_AUDIT_TABLE_DROP: + log_table(cn, event, "DROP"); + break; + case MYSQL_AUDIT_TABLE_RENAME: + log_rename(cn, event); + break; + case MYSQL_AUDIT_TABLE_ALTER: + log_table(cn, event, "ALTER"); + break; + default: + break; + } + } + } + else if (event_class == MYSQL_AUDIT_CONNECTION_CLASS && + FILTER(EVENT_CONNECT) && cn) + { + const struct mysql_event_connection *event = + (const struct mysql_event_connection *) ev; + switch (event->event_subclass) + { + case MYSQL_AUDIT_CONNECTION_CONNECT: + log_connection(cn, event, event->status ? "FAILED_CONNECT": "CONNECT"); + if (event->status == 0 && event->proxy_user && event->proxy_user[0]) + log_proxy(cn, event); + break; + case MYSQL_AUDIT_CONNECTION_DISCONNECT: + if (use_event_data_for_disconnect) + log_connection_event(event, "DISCONNECT"); + else + log_connection(&ci_disconnect_buffer, event, "DISCONNECT"); + break; + case MYSQL_AUDIT_CONNECTION_CHANGE_USER: + log_connection(cn, event, "CHANGEUSER"); + if (event->proxy_user && event->proxy_user[0]) + log_proxy(cn, event); + break; + default:; + } + } +exit_func: + /* + This must work always, whether logging is ON or not. + */ + if (after_action) + { + switch (after_action) { + case AA_CHANGE_USER: + { + const struct mysql_event_connection *event = + (const struct mysql_event_connection *) ev; + change_connection(cn, event); + break; + } + default: + break; + } + } +} + + +struct mysql_event_general_v8 +{ + unsigned int event_class; + unsigned int event_subclass; + int general_error_code; + unsigned long general_thread_id; + const char *general_user; + unsigned int general_user_length; + const char *general_command; + unsigned int general_command_length; + const char *general_query; + unsigned int general_query_length; + struct charset_info_st *general_charset; + unsigned long long general_time; + unsigned long long general_rows; +}; + + +static void auditing_v8(MYSQL_THD thd, struct mysql_event_general_v8 *ev_v8) +{ +#ifdef __linux__ +#ifdef DBUG_OFF + #ifdef __x86_64__ + static const int cmd_off= 4200; + static const int db_off= 120; + static const int db_len_off= 128; + #else + static const int cmd_off= 2668; + static const int db_off= 60; + static const int db_len_off= 64; + #endif /*x86_64*/ +#else + #ifdef __x86_64__ + static const int cmd_off= 4432; + static const int db_off= 120; + static const int db_len_off= 128; + #else + static const int cmd_off= 2808; + static const int db_off= 64; + static const int db_len_off= 68; + #endif /*x86_64*/ +#endif /*DBUG_OFF*/ +#endif /* __linux__ */ + + struct mysql_event_general event; + + if (ev_v8->event_class != MYSQL_AUDIT_GENERAL_CLASS) + return; + + event.event_subclass= ev_v8->event_subclass; + event.general_error_code= ev_v8->general_error_code; + event.general_thread_id= ev_v8->general_thread_id; + event.general_user= ev_v8->general_user; + event.general_user_length= ev_v8->general_user_length; + event.general_command= ev_v8->general_command; + event.general_command_length= ev_v8->general_command_length; + event.general_query= ev_v8->general_query; + event.general_query_length= ev_v8->general_query_length; + event.general_charset= ev_v8->general_charset; + event.general_time= ev_v8->general_time; + event.general_rows= ev_v8->general_rows; + event.database.str= 0; + event.database.length= 0; + + if (event.general_query_length > 0) + { + event.event_subclass= MYSQL_AUDIT_GENERAL_STATUS; + event.general_command= "Query"; + event.general_command_length= 5; +#ifdef __linux__ + event.database.str= *(char **) (((char *) thd) + db_off); + event.database.length= *(size_t *) (((char *) thd) + db_len_off); +#endif /*__linux*/ + } +#ifdef __linux__ + else if (*((int *) (((char *)thd) + cmd_off)) == 2) + { + event.event_subclass= MYSQL_AUDIT_GENERAL_LOG; + event.general_command= "Init DB"; + event.general_command_length= 7; + event.general_query= *(char **) (((char *) thd) + db_off); + event.general_query_length= *(size_t *) (((char *) thd) + db_len_off); + } +#endif /*__linux*/ + auditing(thd, ev_v8->event_class, &event); +} + + +static void auditing_v13(MYSQL_THD thd, unsigned int *ev_v0) +{ + struct mysql_event_general event= *(const struct mysql_event_general *) (ev_v0+1); + + if (event.general_query_length > 0) + { + event.event_subclass= MYSQL_AUDIT_GENERAL_STATUS; + event.general_command= "Query"; + event.general_command_length= 5; + } + auditing(thd, ev_v0[0], &event); +} + + +int get_db_mysql57(MYSQL_THD thd, char **name, size_t *len) +{ +#ifdef __linux__ + int db_off; + int db_len_off; + if (debug_server_started) + { +#ifdef __x86_64__ + db_off= 608; + db_len_off= 616; +#else + db_off= 0; + db_len_off= 0; +#endif /*x86_64*/ + } + else + { +#ifdef __x86_64__ + db_off= 536; + db_len_off= 544; +#else + db_off= 0; + db_len_off= 0; +#endif /*x86_64*/ + } + + *name= *(char **) (((char *) thd) + db_off); + *len= *((size_t *) (((char*) thd) + db_len_off)); + if (*name && (*name)[*len] != 0) + return 1; + return 0; +#else + return 1; +#endif +} +/* + As it's just too difficult to #include "sql_class.h", + let's just copy the necessary part of the system_variables + structure here. +*/ +typedef struct loc_system_variables +{ + ulong dynamic_variables_version; + char* dynamic_variables_ptr; + uint dynamic_variables_head; /* largest valid variable offset */ + uint dynamic_variables_size; /* how many bytes are in use */ + + ulonglong max_heap_table_size; + ulonglong tmp_table_size; + ulonglong long_query_time; + ulonglong optimizer_switch; + ulonglong sql_mode; ///< which non-standard SQL behaviour should be enabled + ulonglong option_bits; ///< OPTION_xxx constants, e.g. OPTION_PROFILING + ulonglong join_buff_space_limit; + ulonglong log_slow_filter; + ulonglong log_slow_verbosity; + ulonglong bulk_insert_buff_size; + ulonglong join_buff_size; + ulonglong sortbuff_size; + ulonglong group_concat_max_len; + ha_rows select_limit; + ha_rows max_join_size; + ha_rows expensive_subquery_limit; + ulong auto_increment_increment, auto_increment_offset; + ulong lock_wait_timeout; + ulong join_cache_level; + ulong max_allowed_packet; + ulong max_error_count; + ulong max_length_for_sort_data; + ulong max_sort_length; + ulong max_tmp_tables; + ulong max_insert_delayed_threads; + ulong min_examined_row_limit; + ulong net_buffer_length; + ulong net_interactive_timeout; + ulong net_read_timeout; + ulong net_retry_count; + ulong net_wait_timeout; + ulong net_write_timeout; + ulong optimizer_prune_level; + ulong optimizer_search_depth; + ulong preload_buff_size; + ulong profiling_history_size; + ulong read_buff_size; + ulong read_rnd_buff_size; + ulong mrr_buff_size; + ulong div_precincrement; + /* Total size of all buffers used by the subselect_rowid_merge_engine. */ + ulong rowid_merge_buff_size; + ulong max_sp_recursion_depth; + ulong default_week_format; + ulong max_seeks_for_key; + ulong range_alloc_block_size; + ulong query_alloc_block_size; + ulong query_prealloc_size; + ulong trans_alloc_block_size; + ulong trans_prealloc_size; + ulong log_warnings; + /* Flags for slow log filtering */ + ulong log_slow_rate_limit; + ulong binlog_format; ///< binlog format for this thd (see enum_binlog_format) + ulong progress_report_time; + my_bool binlog_annotate_row_events; + my_bool binlog_direct_non_trans_update; + my_bool sql_log_bin; + ulong completion_type; + ulong query_cache_type; +} LOC_SV; + + +static int init_done= 0; + +static void* find_sym(const char *sym) +{ +#ifdef _WIN32 + return GetProcAddress(GetModuleHandle("server.dll"),sym); +#else + return dlsym(RTLD_DEFAULT, sym); +#endif +} + +static int server_audit_init(void *p __attribute__((unused))) +{ + if (!serv_ver) + { + serv_ver= find_sym("server_version"); + } + + if (!mysql_57_started) + { + const void *my_hash_init_ptr= find_sym("_my_hash_init"); + if (!my_hash_init_ptr) + { + maria_above_5= 1; + my_hash_init_ptr= find_sym("my_hash_init2"); + } + if (!my_hash_init_ptr) + return 1; + } + + if(!(int_mysql_data_home= find_sym("mysql_data_home"))) + { + if(!(int_mysql_data_home= find_sym("?mysql_data_home@@3PADA"))) + int_mysql_data_home= &default_home; + } + + if (!serv_ver) + return 1; + + if (!started_mysql) + { + if (!maria_above_5 && serv_ver[4]=='3' && serv_ver[5]<'3') + { + mode= 1; + mode_readonly= 1; + } + } + + if (gethostname(servhost, sizeof(servhost))) + strcpy(servhost, "unknown"); + + servhost_len= (uint)strlen(servhost); + + logger_init_mutexes(); +#ifdef HAVE_PSI_INTERFACE + if (PSI_server) + PSI_server->register_mutex("server_audit", mutex_key_list, 1); +#endif + mysql_prlock_init(key_LOCK_operations, &lock_operations); + flogger_mutex_init(key_LOCK_operations, &lock_atomic, MY_MUTEX_INIT_FAST); + flogger_mutex_init(key_LOCK_operations, &lock_bigbuffer, MY_MUTEX_INIT_FAST); + + coll_init(&incl_user_coll); + coll_init(&excl_user_coll); + + if (incl_users) + { + if (excl_users) + { + incl_users= excl_users= NULL; + error_header(); + fprintf(stderr, "INCL_DML_USERS and EXCL_DML_USERS specified" + " simultaneously - both set to empty\n"); + } + update_incl_users(NULL, NULL, NULL, &incl_users); + } + else if (excl_users) + { + update_excl_users(NULL, NULL, NULL, &excl_users); + } + + error_header(); + fprintf(stderr, "MariaDB Audit Plugin version %s%s STARTED.\n", + PLUGIN_STR_VERSION, PLUGIN_DEBUG_VERSION); + + /* The Query Cache shadows TABLE events if the result is taken from it */ + /* so we warn users if both Query Cashe and TABLE events enabled. */ + if (!started_mysql && FILTER(EVENT_TABLE)) + { + ulonglong *qc_size= (ulonglong *) dlsym(RTLD_DEFAULT, "query_cache_size"); + if (qc_size == NULL || *qc_size != 0) + { + struct loc_system_variables *g_sys_var= + (struct loc_system_variables *) dlsym(RTLD_DEFAULT, + "global_system_variables"); + if (g_sys_var && g_sys_var->query_cache_type != 0) + { + error_header(); + fprintf(stderr, "Query cache is enabled with the TABLE events." + " Some table reads can be veiled."); + } + } + } + + ci_disconnect_buffer.header= 10; + ci_disconnect_buffer.thread_id= 0; + ci_disconnect_buffer.query_id= 0; + ci_disconnect_buffer.db_length= 0; + ci_disconnect_buffer.user_length= 0; + ci_disconnect_buffer.host_length= 0; + ci_disconnect_buffer.ip_length= 0; + ci_disconnect_buffer.query= empty_str; + ci_disconnect_buffer.query_length= 0; + + if (logging) + start_logging(); + + init_done= 1; + return 0; +} + + +static int server_audit_init_mysql(void *p) +{ + started_mysql= 1; + mode= 1; + mode_readonly= 1; + return server_audit_init(p); +} + + +static int server_audit_deinit(void *p __attribute__((unused))) +{ + if (!init_done) + return 0; + + init_done= 0; + coll_free(&incl_user_coll); + coll_free(&excl_user_coll); + + if (output_type == OUTPUT_FILE && logfile) + logger_close(logfile); + else if (output_type == OUTPUT_SYSLOG) + closelog(); + + (void) free(big_buffer); + mysql_prlock_destroy(&lock_operations); + flogger_mutex_destroy(&lock_atomic); + flogger_mutex_destroy(&lock_bigbuffer); + + error_header(); + fprintf(stderr, "STOPPED\n"); + return 0; +} + + +static void rotate_log(MYSQL_THD thd __attribute__((unused)), + struct st_mysql_sys_var *var __attribute__((unused)), + void *var_ptr __attribute__((unused)), + const void *save __attribute__((unused))) +{ + if (output_type == OUTPUT_FILE && logfile && *(my_bool*) save) + (void) logger_rotate(logfile); +} + + +static struct st_mysql_audit mysql_descriptor = +{ + MYSQL_AUDIT_INTERFACE_VERSION, + NULL, + auditing, + { MYSQL_AUDIT_GENERAL_CLASSMASK | MYSQL_AUDIT_CONNECTION_CLASSMASK } +}; + + +mysql_declare_plugin(server_audit) +{ + MYSQL_AUDIT_PLUGIN, + &mysql_descriptor, + "SERVER_AUDIT", + " Alexey Botchkov (MariaDB Corporation)", + "Audit the server activity", + PLUGIN_LICENSE_GPL, + server_audit_init_mysql, + server_audit_deinit, + PLUGIN_VERSION, + audit_status, + vars, + NULL, + 0 +} +mysql_declare_plugin_end; + + +static struct st_mysql_audit maria_descriptor = +{ + MYSQL_AUDIT_INTERFACE_VERSION, + NULL, + auditing, + { MYSQL_AUDIT_GENERAL_CLASSMASK | + MYSQL_AUDIT_TABLE_CLASSMASK | + MYSQL_AUDIT_CONNECTION_CLASSMASK } +}; +maria_declare_plugin(server_audit) +{ + MYSQL_AUDIT_PLUGIN, + &maria_descriptor, + "SERVER_AUDIT", + "Alexey Botchkov (MariaDB Corporation)", + "Audit the server activity", + PLUGIN_LICENSE_GPL, + server_audit_init, + server_audit_deinit, + PLUGIN_VERSION, + audit_status, + vars, + PLUGIN_STR_VERSION, + MariaDB_PLUGIN_MATURITY_STABLE +} +maria_declare_plugin_end; + + +static void mark_always_logged(MYSQL_THD thd) +{ + struct connection_info *cn; + if (thd && (cn= get_loc_info(thd))) + cn->log_always= 1; +} + + +static void log_current_query(MYSQL_THD thd) +{ + struct connection_info *cn; + if (!thd) + return; + cn= get_loc_info(thd); + if (!ci_needs_setup(cn) && cn->query_length) + { + cn->log_always= 1; + log_statement_ex(cn, cn->query_time, thd_get_thread_id(thd), + cn->query, cn->query_length, 0, "QUERY", 0); + cn->log_always= 0; + } +} + + +static void update_file_path(MYSQL_THD thd, + struct st_mysql_sys_var *var __attribute__((unused)), + void *var_ptr __attribute__((unused)), const void *save) +{ + char *new_name= (*(char **) save) ? *(char **) save : empty_str; + + ADD_ATOMIC(internal_stop_logging, 1); + error_header(); + fprintf(stderr, "Log file name was changed to '%s'.\n", new_name); + + if (!maria_55_started || !debug_server_started) + mysql_prlock_wrlock(&lock_operations); + + if (logging) + log_current_query(thd); + + if (logging && output_type == OUTPUT_FILE) + { + char *sav_path= file_path; + + file_path= new_name; + stop_logging(); + if (start_logging()) + { + file_path= sav_path; + error_header(); + fprintf(stderr, "Reverting log filename back to '%s'.\n", file_path); + logging= (start_logging() == 0); + if (!logging) + { + error_header(); + fprintf(stderr, "Logging was disabled..\n"); + CLIENT_ERROR(1, "Logging was disabled.", MYF(ME_WARNING)); + } + goto exit_func; + } + } + + strncpy(path_buffer, new_name, sizeof(path_buffer)-1); + path_buffer[sizeof(path_buffer)-1]= 0; + file_path= path_buffer; +exit_func: + if (!maria_55_started || !debug_server_started) + mysql_prlock_unlock(&lock_operations); + ADD_ATOMIC(internal_stop_logging, -1); +} + + +static void update_file_rotations(MYSQL_THD thd __attribute__((unused)), + struct st_mysql_sys_var *var __attribute__((unused)), + void *var_ptr __attribute__((unused)), const void *save) +{ + rotations= *(unsigned int *) save; + error_header(); + fprintf(stderr, "Log file rotations was changed to '%d'.\n", rotations); + + if (!logging || output_type != OUTPUT_FILE) + return; + + mysql_prlock_wrlock(&lock_operations); + logfile->rotations= rotations; + mysql_prlock_unlock(&lock_operations); +} + + +static void update_file_rotate_size(MYSQL_THD thd __attribute__((unused)), + struct st_mysql_sys_var *var __attribute__((unused)), + void *var_ptr __attribute__((unused)), const void *save) +{ + file_rotate_size= *(unsigned long long *) save; + error_header(); + fprintf(stderr, "Log file rotate size was changed to '%lld'.\n", + file_rotate_size); + + if (!logging || output_type != OUTPUT_FILE) + return; + + mysql_prlock_wrlock(&lock_operations); + logfile->size_limit= file_rotate_size; + mysql_prlock_unlock(&lock_operations); +} + + +static int check_users(void *save, struct st_mysql_value *value, + size_t s, const char *name) +{ + const char *users; + int len= 0; + + users= value->val_str(value, NULL, &len); + if ((size_t) len > s) + { + error_header(); + fprintf(stderr, + "server_audit_%s_users value can't be longer than %zu characters.\n", + name, s); + return 1; + } + *((const char**)save)= users; + return 0; +} + +static int check_incl_users(MYSQL_THD thd __attribute__((unused)), + struct st_mysql_sys_var *var __attribute__((unused)), + void *save, struct st_mysql_value *value) +{ + return check_users(save, value, sizeof(incl_user_buffer), "incl"); +} + +static int check_excl_users(MYSQL_THD thd __attribute__((unused)), + struct st_mysql_sys_var *var __attribute__((unused)), + void *save, struct st_mysql_value *value) +{ + return check_users(save, value, sizeof(excl_user_buffer), "excl"); +} + + +static void update_incl_users(MYSQL_THD thd, + struct st_mysql_sys_var *var __attribute__((unused)), + void *var_ptr __attribute__((unused)), const void *save) +{ + char *new_users= (*(char **) save) ? *(char **) save : empty_str; + size_t new_len= strlen(new_users) + 1; + if (!maria_55_started || !debug_server_started) + mysql_prlock_wrlock(&lock_operations); + mark_always_logged(thd); + + if (new_len > sizeof(incl_user_buffer)) + new_len= sizeof(incl_user_buffer); + + memcpy(incl_user_buffer, new_users, new_len - 1); + incl_user_buffer[new_len - 1]= 0; + + incl_users= incl_user_buffer; + user_coll_fill(&incl_user_coll, incl_users, &excl_user_coll, 1); + error_header(); + fprintf(stderr, "server_audit_incl_users set to '%s'.\n", incl_users); + if (!maria_55_started || !debug_server_started) + mysql_prlock_unlock(&lock_operations); +} + + +static void update_excl_users(MYSQL_THD thd __attribute__((unused)), + struct st_mysql_sys_var *var __attribute__((unused)), + void *var_ptr __attribute__((unused)), const void *save) +{ + char *new_users= (*(char **) save) ? *(char **) save : empty_str; + size_t new_len= strlen(new_users) + 1; + if (!maria_55_started || !debug_server_started) + mysql_prlock_wrlock(&lock_operations); + mark_always_logged(thd); + + if (new_len > sizeof(excl_user_buffer)) + new_len= sizeof(excl_user_buffer); + + memcpy(excl_user_buffer, new_users, new_len - 1); + excl_user_buffer[new_len - 1]= 0; + + excl_users= excl_user_buffer; + user_coll_fill(&excl_user_coll, excl_users, &incl_user_coll, 0); + error_header(); + fprintf(stderr, "server_audit_excl_users set to '%s'.\n", excl_users); + if (!maria_55_started || !debug_server_started) + mysql_prlock_unlock(&lock_operations); +} + + +static void update_output_type(MYSQL_THD thd, + struct st_mysql_sys_var *var __attribute__((unused)), + void *var_ptr __attribute__((unused)), const void *save) +{ + ulong new_output_type= *((ulong *) save); + if (output_type == new_output_type) + return; + + ADD_ATOMIC(internal_stop_logging, 1); + mysql_prlock_wrlock(&lock_operations); + if (logging) + { + log_current_query(thd); + stop_logging(); + } + + output_type= new_output_type; + error_header(); + fprintf(stderr, "Output was redirected to '%s'\n", + output_type_names[output_type]); + + if (logging) + start_logging(); + mysql_prlock_unlock(&lock_operations); + ADD_ATOMIC(internal_stop_logging, -1); +} + + +static void update_syslog_facility(MYSQL_THD thd __attribute__((unused)), + struct st_mysql_sys_var *var __attribute__((unused)), + void *var_ptr __attribute__((unused)), const void *save) +{ + ulong new_facility= *((ulong *) save); + if (syslog_facility == new_facility) + return; + + mark_always_logged(thd); + error_header(); + fprintf(stderr, "SysLog facility was changed from '%s' to '%s'.\n", + syslog_facility_names[syslog_facility], + syslog_facility_names[new_facility]); + syslog_facility= new_facility; +} + + +static void update_syslog_priority(MYSQL_THD thd __attribute__((unused)), + struct st_mysql_sys_var *var __attribute__((unused)), + void *var_ptr __attribute__((unused)), const void *save) +{ + ulong new_priority= *((ulong *) save); + if (syslog_priority == new_priority) + return; + + mysql_prlock_wrlock(&lock_operations); + mark_always_logged(thd); + mysql_prlock_unlock(&lock_operations); + error_header(); + fprintf(stderr, "SysLog priority was changed from '%s' to '%s'.\n", + syslog_priority_names[syslog_priority], + syslog_priority_names[new_priority]); + syslog_priority= new_priority; +} + + +static void update_logging(MYSQL_THD thd, + struct st_mysql_sys_var *var __attribute__((unused)), + void *var_ptr __attribute__((unused)), const void *save) +{ + char new_logging= *(char *) save; + if (new_logging == logging) + return; + + ADD_ATOMIC(internal_stop_logging, 1); + if (!maria_55_started || !debug_server_started) + mysql_prlock_wrlock(&lock_operations); + if ((logging= new_logging)) + { + start_logging(); + if (!logging) + { + CLIENT_ERROR(1, "Logging was disabled.", MYF(ME_WARNING)); + } + mark_always_logged(thd); + } + else + { + log_current_query(thd); + stop_logging(); + } + + if (!maria_55_started || !debug_server_started) + mysql_prlock_unlock(&lock_operations); + ADD_ATOMIC(internal_stop_logging, -1); +} + + +static void update_mode(MYSQL_THD thd __attribute__((unused)), + struct st_mysql_sys_var *var __attribute__((unused)), + void *var_ptr __attribute__((unused)), const void *save) +{ + unsigned int new_mode= *(unsigned int *) save; + if (mode_readonly || new_mode == mode) + return; + + ADD_ATOMIC(internal_stop_logging, 1); + if (!maria_55_started || !debug_server_started) + mysql_prlock_wrlock(&lock_operations); + mark_always_logged(thd); + error_header(); + fprintf(stderr, "Logging mode was changed from %d to %d.\n", mode, new_mode); + mode= new_mode; + if (!maria_55_started || !debug_server_started) + mysql_prlock_unlock(&lock_operations); + ADD_ATOMIC(internal_stop_logging, -1); +} + + +static void update_syslog_ident(MYSQL_THD thd __attribute__((unused)), + struct st_mysql_sys_var *var __attribute__((unused)), + void *var_ptr __attribute__((unused)), const void *save) +{ + char *new_ident= (*(char **) save) ? *(char **) save : empty_str; + strncpy(syslog_ident_buffer, new_ident, sizeof(syslog_ident_buffer)-1); + syslog_ident_buffer[sizeof(syslog_ident_buffer)-1]= 0; + syslog_ident= syslog_ident_buffer; + error_header(); + fprintf(stderr, "SYSYLOG ident was changed to '%s'\n", syslog_ident); + mysql_prlock_wrlock(&lock_operations); + mark_always_logged(thd); + if (logging && output_type == OUTPUT_SYSLOG) + { + stop_logging(); + start_logging(); + } + mysql_prlock_unlock(&lock_operations); +} + + +struct st_my_thread_var *loc_thread_var(void) +{ + return 0; +} + + + +#ifdef _WIN32 +BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) +{ + if (fdwReason != DLL_PROCESS_ATTACH) + return 1; + + serv_ver= server_version; +#else +void __attribute__ ((constructor)) audit_plugin_so_init(void) +{ + serv_ver= server_version; +#endif /*_WIN32*/ + + if (!serv_ver) + goto exit; + + started_mariadb= strstr(serv_ver, "MariaDB") != 0; + debug_server_started= strstr(serv_ver, "debug") != 0; + + if (started_mariadb) + { + if (serv_ver[0] == '1') + use_event_data_for_disconnect= 1; + else + maria_55_started= 1; + } + else + { + /* Started MySQL. */ + if (serv_ver[0] == '5' && serv_ver[2] == '5') + { + int sc= serv_ver[4] - '0'; + if (serv_ver[5] >= '0' && serv_ver[5] <= '9') + sc= sc * 10 + serv_ver[5] - '0'; + if (sc <= 10) + { + mysql_descriptor.interface_version= 0x0200; + mysql_descriptor.event_notify= (void *) auditing_v8; + } + else if (sc < 14) + { + mysql_descriptor.interface_version= 0x0200; + mysql_descriptor.event_notify= (void *) auditing_v13; + } + } + else if (serv_ver[0] == '5' && serv_ver[2] == '6') + { + int sc= serv_ver[4] - '0'; + if (serv_ver[5] >= '0' && serv_ver[5] <= '9') + sc= sc * 10 + serv_ver[5] - '0'; + if (sc >= 24) + use_event_data_for_disconnect= 1; + } + else if ((serv_ver[0] == '5' && serv_ver[2] == '7') || + (serv_ver[0] == '8' && serv_ver[2] == '0')) + { + mysql_57_started= 1; + _mysql_plugin_declarations_[0].info= mysql_v4_descriptor; + use_event_data_for_disconnect= 1; + } + MYSQL_SYSVAR_NAME(loc_info).flags= PLUGIN_VAR_STR | PLUGIN_VAR_THDLOCAL | + PLUGIN_VAR_READONLY | PLUGIN_VAR_MEMALLOC; + } + + memset(locinfo_ini_value, 'O', sizeof(locinfo_ini_value)-1); + locinfo_ini_value[sizeof(locinfo_ini_value)-1]= 0; + +exit: +#ifdef _WIN32 + return 1; +#else + return; +#endif +} + diff --git a/plugin/server_audit/test_audit_v4.c b/plugin/server_audit/test_audit_v4.c new file mode 100644 index 00000000..f37d8c7c --- /dev/null +++ b/plugin/server_audit/test_audit_v4.c @@ -0,0 +1,163 @@ +#define PLUGIN_CONTEXT + +/* Can't use <my_global.h> as this includes plugin.h */ +#include <stdio.h> + +typedef void *MYSQL_THD; +struct st_mysql_const_lex_string +{ + const char *str; + size_t length; +}; +typedef struct st_mysql_const_lex_string MYSQL_LEX_CSTRING; +enum enum_sql_command{ SQLCOM_A, SQLCOM_B }; +enum enum_server_command{ SERVCOM_A, SERVCOM_B }; + +#include "plugin_audit_v4.h" + +extern void auditing(MYSQL_THD thd, unsigned int event_class, const void *ev); +extern int get_db_mysql57(MYSQL_THD thd, char **name, size_t *len); + + +struct mysql_event_general_302 +{ + unsigned int event_subclass; + int general_error_code; + unsigned long general_thread_id; + const char *general_user; + unsigned int general_user_length; + const char *general_command; + unsigned int general_command_length; + const char *general_query; + unsigned int general_query_length; + struct charset_info_st *general_charset; + unsigned long long general_time; + unsigned long long general_rows; + unsigned long long query_id; + char *database; + size_t database_length; +}; + + +static int auditing_v4(MYSQL_THD thd, mysql_event_class_t class, const void *ev) +{ + int *subclass= (int *)ev; + struct mysql_event_general_302 ev_302; + int subclass_v3, subclass_orig; + + if (class != MYSQL_AUDIT_GENERAL_CLASS && + class != MYSQL_AUDIT_CONNECTION_CLASS) + return 0; + + subclass_orig= *subclass; + + if (class == MYSQL_AUDIT_GENERAL_CLASS) + { + struct mysql_event_general *event= (struct mysql_event_general *) ev; + ev_302.general_error_code= event->general_error_code; + ev_302.general_thread_id= event->general_thread_id; + ev_302.general_user= event->general_user.str; + ev_302.general_user_length= (unsigned int)event->general_user.length; + ev_302.general_command= event->general_command.str; + ev_302.general_command_length= (unsigned int)event->general_command.length; + ev_302.general_query= event->general_query.str; + ev_302.general_query_length= (unsigned int)event->general_query.length; + ev_302.general_charset= event->general_charset; + ev_302.general_time= event->general_time; + ev_302.general_rows= event->general_rows; + if (get_db_mysql57(thd, &ev_302.database, &ev_302.database_length)) + { + ev_302.database= 0; + ev_302.database_length= 0; + } + ev= &ev_302; + switch (subclass_orig) + { + case MYSQL_AUDIT_GENERAL_LOG: + subclass_v3= 0; + ev_302.event_subclass= 0; + break; + case MYSQL_AUDIT_GENERAL_ERROR: + subclass_v3= 1; + ev_302.event_subclass= 1; + break; + case MYSQL_AUDIT_GENERAL_RESULT: + subclass_v3= 2; + ev_302.event_subclass= 2; + break; + case MYSQL_AUDIT_GENERAL_STATUS: + { + subclass_v3= 3; + ev_302.event_subclass= 3; + break; + } + default: + return 0; + } + } + else /* if (class == MYSQL_AUDIT_CONNECTION_CLASS) */ + { + switch (subclass_orig) + { + case MYSQL_AUDIT_CONNECTION_CONNECT: + subclass_v3= 0; + break; + case MYSQL_AUDIT_CONNECTION_DISCONNECT: + subclass_v3= 1; + break; + default: + return 0; + } + } + + *subclass= subclass_v3; + + auditing(thd, (int) class, ev); + + *subclass= subclass_orig; + return 0; +} + + +static struct st_mysql_audit mysql_descriptor = +{ + MYSQL_AUDIT_INTERFACE_VERSION, + NULL, + auditing_v4, + { (unsigned long) MYSQL_AUDIT_GENERAL_ALL, + (unsigned long) MYSQL_AUDIT_CONNECTION_ALL, + (unsigned long) MYSQL_AUDIT_PARSE_ALL, + 0, /* This event class is currently not supported. */ + 0, /* This event class is currently not supported. */ + (unsigned long) MYSQL_AUDIT_GLOBAL_VARIABLE_ALL, + (unsigned long) MYSQL_AUDIT_SERVER_STARTUP_ALL, + (unsigned long) MYSQL_AUDIT_SERVER_SHUTDOWN_ALL, + (unsigned long) MYSQL_AUDIT_COMMAND_ALL, + (unsigned long) MYSQL_AUDIT_QUERY_ALL, + (unsigned long) MYSQL_AUDIT_STORED_PROGRAM_ALL } +#ifdef WHEN_MYSQL_BUG_FIXED + /* + By this moment MySQL just sends no notifications at all + when we request only those we actually need. + So we have to request everything and filter them inside the + handling function. + */ + { (unsigned long) MYSQL_AUDIT_GENERAL_ALL, + (unsigned long) (MYSQL_AUDIT_CONNECTION_CONNECT | + MYSQL_AUDIT_CONNECTION_DISCONNECT), + 0, + 0, /* This event class is currently not supported. */ + 0, /* This event class is currently not supported. */ + 0, + 0, + 0, + 0, + 0, + 0 + } +#endif /*WHEN_MYSQL_BUG_FIXED*/ +}; + + +void *mysql_v4_descriptor= &mysql_descriptor; + |