1 /* Copyright (c) 2014-2016 Percona LLC and/or its affiliates. All rights reserved.
2 
3    This program is free software; you can redistribute it and/or
4    modify it under the terms of the GNU General Public License
5    as published by the Free Software Foundation; version 2 of
6    the License.
7 
8    This program is distributed in the hope that it will be useful,
9    but WITHOUT ANY WARRANTY; without even the implied warranty of
10    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11    GNU General Public License for more details.
12 
13    You should have received a copy of the GNU General Public License
14    along with this program; if not, write to the Free Software
15    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA */
16 
17 #include <time.h>
18 #include <string.h>
19 #include <stdio.h>
20 
21 #include <my_global.h>
22 #include <my_sys.h>
23 #include <m_ctype.h>
24 #include <mysql/plugin.h>
25 #include <mysql/plugin_audit.h>
26 #include <mysql/service_security_context.h>
27 #include <mysqld_error.h>
28 #include <typelib.h>
29 #include <mysql_version.h>
30 #include <mysql_com.h>
31 #include <syslog.h>
32 
33 #include "audit_log.h"
34 #include "logger.h"
35 #include "buffer.h"
36 #include "audit_handler.h"
37 #include "filter.h"
38 
39 #define PLUGIN_VERSION 0x0002
40 
41 
42 enum audit_log_policy_t { ALL, NONE, LOGINS, QUERIES };
43 enum audit_log_strategy_t
44   { ASYNCHRONOUS, PERFORMANCE, SEMISYNCHRONOUS, SYNCHRONOUS };
45 enum audit_log_format_t { OLD, NEW, JSON, CSV };
46 enum audit_log_handler_t { HANDLER_FILE, HANDLER_SYSLOG };
47 
48 typedef void (*escape_buf_func_t)(const char *, size_t *, char *, size_t *);
49 
50 static audit_handler_t *log_handler= NULL;
51 static ulonglong record_id= 0;
52 static time_t log_file_time= 0;
53 static char *audit_log_file;
54 static const char default_audit_log_file[]= "audit.log";
55 static ulong audit_log_policy= ALL;
56 static ulong audit_log_strategy= ASYNCHRONOUS;
57 static ulonglong audit_log_buffer_size= 1048576;
58 static ulonglong audit_log_rotate_on_size= 0;
59 static ulonglong audit_log_rotations= 0;
60 static char audit_log_flush= FALSE;
61 static ulong audit_log_format= OLD;
62 static ulong audit_log_handler= HANDLER_FILE;
63 static char *audit_log_syslog_ident;
64 static const char default_audit_log_syslog_ident[] = "percona-audit";
65 static ulong audit_log_syslog_facility= 0;
66 static ulong audit_log_syslog_priority= 0;
67 static char *audit_log_exclude_accounts= NULL;
68 static char *audit_log_include_accounts= NULL;
69 static char *audit_log_exclude_databases= NULL;
70 static char *audit_log_include_databases= NULL;
71 static char *audit_log_exclude_commands= NULL;
72 static char *audit_log_include_commands= NULL;
73 int64 audit_log_buffer_size_overflow = 0;
74 
75 PSI_memory_key key_memory_audit_log_logger_handle;
76 PSI_memory_key key_memory_audit_log_handler;
77 PSI_memory_key key_memory_audit_log_buffer;
78 PSI_memory_key key_memory_audit_log_accounts;
79 PSI_memory_key key_memory_audit_log_databases;
80 PSI_memory_key key_memory_audit_log_commands;
81 
82 static PSI_memory_info all_audit_log_memory[]=
83 {
84   {&key_memory_audit_log_logger_handle, "audit_log_logger_handle", 0},
85   {&key_memory_audit_log_handler, "audit_log_handler", 0},
86   {&key_memory_audit_log_buffer, "audit_log_buffer", 0},
87   {&key_memory_audit_log_accounts, "audit_log_accounts", 0},
88   {&key_memory_audit_log_databases, "audit_log_databases", 0},
89   {&key_memory_audit_log_commands, "audit_log_commands", 0},
90 };
91 
92 static int audit_log_syslog_facility_codes[]=
93   { LOG_USER,   LOG_AUTHPRIV, LOG_CRON,   LOG_DAEMON, LOG_FTP,
94     LOG_KERN,   LOG_LPR,      LOG_MAIL,   LOG_NEWS,
95 #if (defined LOG_SECURITY)
96     LOG_SECURITY,
97 #endif
98     LOG_SYSLOG, LOG_AUTH,     LOG_UUCP,   LOG_LOCAL0, LOG_LOCAL1,
99     LOG_LOCAL2, LOG_LOCAL3,   LOG_LOCAL4, LOG_LOCAL5, LOG_LOCAL6,
100     LOG_LOCAL7, 0};
101 
102 
103 static const char *audit_log_syslog_facility_names[]=
104   { "LOG_USER",   "LOG_AUTHPRIV", "LOG_CRON",   "LOG_DAEMON", "LOG_FTP",
105     "LOG_KERN",   "LOG_LPR",      "LOG_MAIL",   "LOG_NEWS",
106 #if (defined LOG_SECURITY)
107     "LOG_SECURITY",
108 #endif
109     "LOG_SYSLOG", "LOG_AUTH",     "LOG_UUCP",   "LOG_LOCAL0", "LOG_LOCAL1",
110     "LOG_LOCAL2", "LOG_LOCAL3",   "LOG_LOCAL4", "LOG_LOCAL5", "LOG_LOCAL6",
111     "LOG_LOCAL7", 0 };
112 
113 
114 static const int audit_log_syslog_priority_codes[]=
115   { LOG_INFO,   LOG_ALERT, LOG_CRIT, LOG_ERR, LOG_WARNING,
116     LOG_NOTICE, LOG_EMERG,  LOG_DEBUG, 0 };
117 
118 
119 static const char *audit_log_syslog_priority_names[]=
120   { "LOG_INFO",   "LOG_ALERT", "LOG_CRIT", "LOG_ERR", "LOG_WARNING",
121     "LOG_NOTICE", "LOG_EMERG", "LOG_DEBUG", 0 };
122 
123 static MYSQL_PLUGIN plugin_ptr;
124 
125 static
init_record_id(off_t size)126 void init_record_id(off_t size)
127 {
128   record_id= size;
129 }
130 
131 
132 static
next_record_id()133 ulonglong next_record_id()
134 {
135   return __sync_add_and_fetch(&record_id, 1);
136 }
137 
138 
139 #define MAX_RECORD_ID_SIZE  50
140 #define MAX_TIMESTAMP_SIZE  25
141 
142 void plugin_thdvar_safe_update(MYSQL_THD thd, struct st_mysql_sys_var *var,
143                                char **dest, const char *value);
144 
145 static
make_timestamp(char * buf,size_t buf_len,time_t t)146 char *make_timestamp(char *buf, size_t buf_len, time_t t)
147 {
148   struct tm tm;
149 
150   memset(&tm, 0, sizeof(tm));
151   strftime(buf, buf_len, "%FT%T UTC", gmtime_r(&t, &tm));
152 
153   return buf;
154 }
155 
156 static
make_record_id(char * buf,size_t buf_len)157 char *make_record_id(char *buf, size_t buf_len)
158 {
159   struct tm tm;
160   size_t len;
161 
162   memset(&tm, 0, sizeof(tm));
163   len= my_snprintf(buf, buf_len, "%llu_", next_record_id());
164 
165   strftime(buf + len, buf_len - len,
166            "%FT%T", gmtime_r(&log_file_time, &tm));
167 
168   return buf;
169 }
170 
171 typedef struct
172 {
173   char character;
174   size_t length;
175   const char *replacement;
176 } escape_rule_t;
177 
178 static
escape_buf(const char * in,size_t * inlen,char * out,size_t * outlen,const escape_rule_t * control_escape_rules,const escape_rule_t * other_escape_rules)179 void escape_buf(const char *in, size_t *inlen, char *out, size_t *outlen,
180                 const escape_rule_t *control_escape_rules,
181                 const escape_rule_t *other_escape_rules)
182 {
183   char* outstart = out;
184   const char* base = in;
185   char* outend = out + *outlen;
186   const char* inend;
187   const escape_rule_t *replace_rule = NULL;
188 
189   inend = in + (*inlen);
190 
191   while ((in < inend) && (out < outend))
192   {
193     replace_rule = NULL;
194     if ((unsigned char)(*in) < 32) {
195       if (control_escape_rules[(unsigned int)*in].character) {
196         replace_rule = &control_escape_rules[(unsigned int)*in];
197       }
198     } else
199     {
200       const escape_rule_t *rule = NULL;
201       for (rule= other_escape_rules; rule->character; rule++)
202       {
203         if (*in == rule->character)
204         {
205           replace_rule = rule;
206           break;
207         }
208       }
209     }
210     if (replace_rule)
211     {
212           if ((outend - out) < (ptrdiff_t) replace_rule->length)
213             break;
214           memcpy(out, replace_rule->replacement, replace_rule->length);
215           out += replace_rule->length;
216     } else
217     {
218       *out++ = *in;
219     }
220     ++in;
221   }
222   *outlen = out - outstart;
223   *inlen = in - base;
224 }
225 
226 static
xml_escape(const char * in,size_t * inlen,char * out,size_t * outlen)227 void xml_escape(const char *in, size_t *inlen, char *out, size_t *outlen)
228 {
229   // Most control sequences aren't supported before XML 1.1, and most
230   // tools only support 1.0. Our output is 1.0. Escaping them wouldn't make
231   // the output more valid.
232   static const escape_rule_t control_rules[]=
233   {
234     { 0,  0, NULL },
235     { 0,  0, NULL },
236     { 0,  0, NULL },
237     { 0,  0, NULL },
238     { 0,  0, NULL },
239     { 0,  0, NULL },
240     { 0,  0, NULL },
241     { 0,  0, NULL },
242     { 0,  0, NULL },
243     { '\t', 5, "&#9;" },
244     { '\n', 6, "&#10;" },
245     { 0,  0, NULL },
246     { 0,  0, NULL },
247     { '\r', 6, "&#13;" },
248     { 0,  0, NULL },
249     { 0,  0, NULL },
250     { 0,  0, NULL },
251     { 0,  0, NULL },
252     { 0,  0, NULL },
253     { 0,  0, NULL },
254     { 0,  0, NULL },
255     { 0,  0, NULL },
256     { 0,  0, NULL },
257     { 0,  0, NULL },
258     { 0,  0, NULL },
259     { 0,  0, NULL },
260     { 0,  0, NULL },
261     { 0,  0, NULL },
262     { 0,  0, NULL },
263     { 0,  0, NULL },
264     { 0,  0, NULL },
265     { 0,  0, NULL },
266   };
267   static const escape_rule_t other_rules[]=
268   {
269     { '<',  4, "&lt;" },
270     { '>',  4, "&gt;" },
271     { '&',  5, "&amp;" },
272     { '"',  6, "&quot;" },
273     { 0,  0, NULL }
274   };
275 
276   escape_buf(in, inlen, out, outlen, control_rules, other_rules);
277 }
278 
279 static
json_escape(const char * in,size_t * inlen,char * out,size_t * outlen)280 void json_escape(const char *in, size_t *inlen, char *out, size_t *outlen)
281 {
282   static const escape_rule_t control_rules[]=
283   {
284     { 0,  6, "\\u0000" },
285     { 1,  6, "\\u0001" },
286     { 2,  6, "\\u0002" },
287     { 3,  6, "\\u0003" },
288     { 4,  6, "\\u0004" },
289     { 5,  6, "\\u0005" },
290     { 6,  6, "\\u0006" },
291     { 7,  6, "\\u0007" },
292     { '\b',  2, "\\b" },
293     { '\t',  2, "\\t" },
294     { '\n',  2, "\\n" },
295     { 11, 6, "\\u000B" },
296     { '\f',  2, "\\f" },
297     { '\r',  2, "\\r" },
298     { 14, 6, "\\u000E" },
299     { 15, 6, "\\u000F" },
300     { 16, 6, "\\u0010" },
301     { 17, 6, "\\u0011" },
302     { 18, 6, "\\u0012" },
303     { 19, 6, "\\u0013" },
304     { 20, 6, "\\u0014" },
305     { 21, 6, "\\u0015" },
306     { 22, 6, "\\u0016" },
307     { 23, 6, "\\u0017" },
308     { 24, 6, "\\u0018" },
309     { 25, 6, "\\u0019" },
310     { 26, 6, "\\u001A" },
311     { 27, 6, "\\u001B" },
312     { 28, 6, "\\u001C" },
313     { 29, 6, "\\u001D" },
314     { 30, 6, "\\u001E" },
315     { 31, 6, "\\u001F" },
316   };
317 
318   static const escape_rule_t other_rules[]=
319   {
320     { '\\', 2, "\\\\" },
321     { '"',  2, "\\\"" },
322     { '/',  2, "\\/" },
323     { 0,  0, NULL }
324   };
325 
326   escape_buf(in, inlen, out, outlen, control_rules, other_rules);
327 }
328 
329 static
csv_escape(const char * in,size_t * inlen,char * out,size_t * outlen)330 void csv_escape(const char *in, size_t *inlen, char *out, size_t *outlen)
331 {
332   // We do not have any standard control escape rules for CSVs
333   static const escape_rule_t control_rules[]=
334   {
335     { 0,  0, NULL },
336     { 0,  0, NULL },
337     { 0,  0, NULL },
338     { 0,  0, NULL },
339     { 0,  0, NULL },
340     { 0,  0, NULL },
341     { 0,  0, NULL },
342     { 0,  0, NULL },
343     { 0,  0, NULL },
344     { 0,  0, NULL },
345     { 0,  0, NULL },
346     { 0,  0, NULL },
347     { 0,  0, NULL },
348     { 0,  0, NULL },
349     { 0,  0, NULL },
350     { 0,  0, NULL },
351     { 0,  0, NULL },
352     { 0,  0, NULL },
353     { 0,  0, NULL },
354     { 0,  0, NULL },
355     { 0,  0, NULL },
356     { 0,  0, NULL },
357     { 0,  0, NULL },
358     { 0,  0, NULL },
359     { 0,  0, NULL },
360     { 0,  0, NULL },
361     { 0,  0, NULL },
362     { 0,  0, NULL },
363     { 0,  0, NULL },
364     { 0,  0, NULL },
365     { 0,  0, NULL },
366     { 0,  0, NULL },
367   };
368 
369   static const escape_rule_t other_rules[]=
370   {
371     { '"',  2, "\"\"" },
372     { 0,  0, NULL }
373   };
374 
375   escape_buf(in, inlen, out, outlen, control_rules, other_rules);
376 }
377 
378 static const escape_buf_func_t format_escape_func[]=
379   { xml_escape, xml_escape, json_escape, csv_escape };
380 
381 /*
382   Calculate the size of the otput bufer needed to escape the string.
383 
384   @param[in]  in           Input string
385   @param[in]  len          Length of the input string
386 
387   @return
388     size of the otput bufer including trailing zero
389 */
390 static
calculate_escape_string_buf_len(const char * in,size_t len)391 size_t calculate_escape_string_buf_len(const char *in, size_t len)
392 {
393   char tmp[128];
394   size_t full_outlen= 0;
395 
396   while (len > 0)
397   {
398     size_t tmp_size= sizeof(tmp);
399     size_t inlen= len;
400     format_escape_func[audit_log_format](in, &inlen, tmp, &tmp_size);
401     in+= inlen;
402     len-= inlen;
403     full_outlen+= tmp_size;
404   }
405   return full_outlen + 1;
406 }
407 
408 /*
409   Escape string according to audit_log_format.
410 
411   @param[in]  in           Input string
412   @param[in]  inlen        Length of the input string
413   @param[in]  out          Output buffer
414   @param[in]  outlen       Length of the output buffer
415   @param[out] endptr       A pointer to the character after the
416                            last escaped character in the output
417                            buffer
418   @param[out] full_outlen  Length of the output buffer that would
419                            be needed to store complete non-truncated
420                            escaped input buffer
421 
422   @return
423     pointer to the beginning of the output buffer
424 */
425 static
escape_string(const char * in,size_t inlen,char * out,size_t outlen,char ** endptr,size_t * full_outlen)426 char *escape_string(const char *in, size_t inlen,
427                     char *out, size_t outlen,
428                     char **endptr, size_t *full_outlen)
429 {
430   if (outlen == 0)
431   {
432     if (endptr)
433       *endptr= out;
434     if (full_outlen)
435       *full_outlen+= calculate_escape_string_buf_len(in, inlen);
436   }
437   else if (in != NULL)
438   {
439     size_t inlen_res= inlen;
440     --outlen;
441     format_escape_func[audit_log_format](in, &inlen_res, out, &outlen);
442     out[outlen]= 0;
443     if (endptr)
444       *endptr= out + outlen + 1;
445     if (full_outlen)
446     {
447       *full_outlen+= outlen;
448       *full_outlen+= calculate_escape_string_buf_len(in + inlen_res,
449                                                      inlen - inlen_res);
450     }
451   }
452   else
453   {
454     *out= 0;
455     if (endptr)
456       *endptr= out + 1;
457     if (full_outlen)
458       ++(*full_outlen);
459   }
460   return out;
461 }
462 
463 static
my_plugin_perror(void)464 void my_plugin_perror(void)
465 {
466   char errbuf[MYSYS_STRERROR_SIZE];
467   my_strerror(errbuf, sizeof(errbuf), errno);
468   my_plugin_log_message(&plugin_ptr, MY_ERROR_LEVEL, "Error: %s", errbuf);
469 }
470 
471 static
audit_log_write(const char * buf,size_t len)472 void audit_log_write(const char *buf, size_t len)
473 {
474   static int write_error= 0;
475 
476   if (audit_handler_write(log_handler, buf, len) < 0)
477   {
478     if (!write_error)
479     {
480       write_error= 1;
481       my_plugin_log_message(&plugin_ptr, MY_ERROR_LEVEL,
482                             "Error writing to file %s.", audit_log_file);
483       my_plugin_perror();
484     }
485   }
486   else
487   {
488     write_error= 0;
489   }
490 }
491 
492 
493 /* Defined in MySQL server */
494 extern int orig_argc;
495 extern char **orig_argv;
496 extern char server_version[SERVER_VERSION_LENGTH];
497 
498 static
make_argv(char * buf,size_t len,int argc,char ** argv)499 char *make_argv(char *buf, size_t len, int argc, char **argv)
500 {
501   size_t left= len;
502 
503   buf[0]= 0;
504   while (argc > 0 && left > 0)
505   {
506     left-= my_snprintf(buf + len - left, left,
507                        "%s%c", *argv, argc > 1 ? ' ' : 0);
508     argc--; argv++;
509   }
510 
511   return buf;
512 }
513 
514 static
audit_log_audit_record(char * buf,size_t buflen,const char * name,time_t t,size_t * outlen)515 char *audit_log_audit_record(char *buf, size_t buflen,
516                              const char *name, time_t t,
517                              size_t *outlen)
518 {
519   char id_str[MAX_RECORD_ID_SIZE];
520   char timestamp[MAX_TIMESTAMP_SIZE];
521   char arg_buf[512];
522   const char *format_string[] = {
523                      "<AUDIT_RECORD\n"
524                      "  NAME=\"%s\"\n"
525                      "  RECORD=\"%s\"\n"
526                      "  TIMESTAMP=\"%s\"\n"
527                      "  MYSQL_VERSION=\"%s\"\n"
528                      "  STARTUP_OPTIONS=\"%s\"\n"
529                      "  OS_VERSION=\""MACHINE_TYPE"-"SYSTEM_TYPE"\"\n"
530                      "/>\n",
531 
532                      "<AUDIT_RECORD>\n"
533                      "  <NAME>%s</NAME>\n"
534                      "  <RECORD>%s</RECORD>\n"
535                      "  <TIMESTAMP>%s</TIMESTAMP>\n"
536                      "  <MYSQL_VERSION>%s</MYSQL_VERSION>\n"
537                      "  <STARTUP_OPTIONS>%s</STARTUP_OPTIONS>\n"
538                      "  <OS_VERSION>"MACHINE_TYPE"-"SYSTEM_TYPE"</OS_VERSION>\n"
539                      "</AUDIT_RECORD>\n",
540 
541                      "{\"audit_record\":{\"name\":\"%s\",\"record\":\"%s\","
542                      "\"timestamp\":\"%s\",\"mysql_version\":\"%s\","
543                      "\"startup_optionsi\":\"%s\","
544                      "\"os_version\":\""MACHINE_TYPE"-"SYSTEM_TYPE"\"}}\n",
545 
546                      "\"%s\",\"%s\",\"%s\",\"%s\",\"%s\","
547                      "\""MACHINE_TYPE"-"SYSTEM_TYPE"\"\n" };
548 
549   *outlen= snprintf(buf, buflen,
550                     format_string[audit_log_format],
551                     name,
552                     make_record_id(id_str, sizeof(id_str)),
553                     make_timestamp(timestamp, sizeof(timestamp), t),
554                     server_version,
555                     make_argv(arg_buf, sizeof(arg_buf),
556                               orig_argc - 1, orig_argv + 1));
557 
558   /* make sure that record is not truncated */
559   assert(buf + *outlen <= buf + buflen);
560 
561   return buf;
562 }
563 
564 
565 static
audit_log_general_record(char * buf,size_t buflen,const char * name,time_t t,int status,const struct mysql_event_general * event,const char * default_db,size_t * outlen)566 char *audit_log_general_record(char *buf, size_t buflen,
567                                const char *name, time_t t, int status,
568                                const struct mysql_event_general *event,
569                                const char *default_db,
570                                size_t *outlen)
571 {
572   char id_str[MAX_RECORD_ID_SIZE];
573   char timestamp[MAX_TIMESTAMP_SIZE];
574   char *query, *user, *host, *external_user, *ip, *db;
575   char *endptr= buf, *endbuf= buf + buflen;
576   size_t full_outlen= 0, buflen_estimated;
577   size_t query_length;
578 
579   const char *format_string[] = {
580                      "<AUDIT_RECORD\n"
581                      "  NAME=\"%s\"\n"
582                      "  RECORD=\"%s\"\n"
583                      "  TIMESTAMP=\"%s\"\n"
584                      "  COMMAND_CLASS=\"%s\"\n"
585                      "  CONNECTION_ID=\"%lu\"\n"
586                      "  STATUS=\"%d\"\n"
587                      "  SQLTEXT=\"%s\"\n"
588                      "  USER=\"%s\"\n"
589                      "  HOST=\"%s\"\n"
590                      "  OS_USER=\"%s\"\n"
591                      "  IP=\"%s\"\n"
592                      "  DB=\"%s\"\n"
593                      "/>\n",
594 
595                      "<AUDIT_RECORD>\n"
596                      "  <NAME>%s</NAME>\n"
597                      "  <RECORD>%s</RECORD>\n"
598                      "  <TIMESTAMP>%s</TIMESTAMP>\n"
599                      "  <COMMAND_CLASS>%s</COMMAND_CLASS>\n"
600                      "  <CONNECTION_ID>%lu</CONNECTION_ID>\n"
601                      "  <STATUS>%d</STATUS>\n"
602                      "  <SQLTEXT>%s</SQLTEXT>\n"
603                      "  <USER>%s</USER>\n"
604                      "  <HOST>%s</HOST>\n"
605                      "  <OS_USER>%s</OS_USER>\n"
606                      "  <IP>%s</IP>\n"
607                      "  <DB>%s</DB>\n"
608                      "</AUDIT_RECORD>\n",
609 
610                      "{\"audit_record\":"
611                        "{\"name\":\"%s\","
612                        "\"record\":\"%s\","
613                        "\"timestamp\":\"%s\","
614                        "\"command_class\":\"%s\","
615                        "\"connection_id\":\"%lu\","
616                        "\"status\":%d,"
617                        "\"sqltext\":\"%s\","
618                        "\"user\":\"%s\","
619                        "\"host\":\"%s\","
620                        "\"os_user\":\"%s\","
621                        "\"ip\":\"%s\","
622                        "\"db\":\"%s\"}}\n",
623 
624                      ("\"%s\",\"%s\",\"%s\",\"%s\",\"%lu\",%d,\"%s\",\"%s\","
625                       "\"%s\",\"%s\",\"%s\",\"%s\"\n") };
626 
627   query_length= my_charset_utf8mb4_general_ci.mbmaxlen *
628                 event->general_query.length;
629 
630   /* Note: query_length is the maximun size using utf8m4(4 bytes) that
631    * event->general_query_length may use. In the if branch, we convert it to
632    * utf8mb4. We store the recalculated (real size) length to query variable
633    * and use the remaing of the buffer for the output that will be printed to
634    * audit log. Parameter char *buf must be big enough to store
635    * the query (using utf8mb4) + the full output of audit event, which will
636    * contain the query again. At the else branch we estime this size.
637    */
638   if (query_length < (size_t) (endbuf - endptr))
639   {
640     uint errors;
641     query_length= my_convert(endptr, query_length,
642                              &my_charset_utf8mb4_general_ci,
643                              event->general_query.str,
644                              event->general_query.length,
645                              event->general_charset, &errors);
646     query= endptr;
647     endptr+= query_length;
648 
649     full_outlen+= query_length;
650 
651     query= escape_string(query, query_length, endptr, endbuf - endptr,
652                          &endptr, &full_outlen);
653   }
654   else
655   {
656     endptr= endbuf;
657     query= escape_string(event->general_query.str, event->general_query.length,
658                          endptr, endbuf - endptr, &endptr, &full_outlen);
659     full_outlen*= my_charset_utf8mb4_general_ci.mbmaxlen;
660     full_outlen+= query_length;
661   }
662 
663   user= escape_string(event->general_user.str, event->general_user.length,
664                       endptr, endbuf - endptr, &endptr, &full_outlen);
665   host= escape_string(event->general_host.str, event->general_host.length,
666                       endptr, endbuf - endptr, &endptr, &full_outlen);
667   external_user= escape_string(event->general_external_user.str,
668                                event->general_external_user.length,
669                                endptr, endbuf - endptr, &endptr, &full_outlen);
670   ip= escape_string(event->general_ip.str, event->general_ip.length,
671                     endptr, endbuf - endptr, &endptr, &full_outlen);
672   db= escape_string(default_db, strlen(default_db),
673                     endptr, endbuf - endptr, &endptr, &full_outlen);
674 
675   buflen_estimated= full_outlen + strlen(format_string[audit_log_format]) +
676                     strlen(name) + event->general_sql_command.length +
677                     20 + /* general_thread_id */
678                     20 + /* status */
679                     MAX_RECORD_ID_SIZE + MAX_TIMESTAMP_SIZE;
680   if (buflen_estimated > buflen)
681   {
682     *outlen= buflen_estimated;
683     return NULL;
684   }
685 
686   *outlen= snprintf(endptr, endbuf - endptr,
687                     format_string[audit_log_format],
688                     name,
689                     make_record_id(id_str, sizeof(id_str)),
690                     make_timestamp(timestamp, sizeof(timestamp), t),
691                     event->general_sql_command.str,
692                     event->general_thread_id,
693                     status, query, user, host, external_user, ip, db);
694 
695   /* make sure that record is not truncated */
696   assert(endptr + *outlen <= buf + buflen);
697 
698   return endptr;
699 }
700 
701 static
audit_log_connection_record(char * buf,size_t buflen,const char * name,time_t t,const struct mysql_event_connection * event,size_t * outlen)702 char *audit_log_connection_record(char *buf, size_t buflen,
703                                   const char *name, time_t t,
704                                   const struct mysql_event_connection *event,
705                                   size_t *outlen)
706 {
707   char id_str[MAX_RECORD_ID_SIZE];
708   char timestamp[MAX_TIMESTAMP_SIZE];
709   char *user, *priv_user, *external_user, *proxy_user, *host, *ip, *database;
710   char *endptr= buf, *endbuf= buf + buflen;
711 
712   const char *format_string[] = {
713                      "<AUDIT_RECORD\n"
714                      "  NAME=\"%s\"\n"
715                      "  RECORD=\"%s\"\n"
716                      "  TIMESTAMP=\"%s\"\n"
717                      "  CONNECTION_ID=\"%lu\"\n"
718                      "  STATUS=\"%d\"\n"
719                      "  USER=\"%s\"\n"
720                      "  PRIV_USER=\"%s\"\n"
721                      "  OS_LOGIN=\"%s\"\n"
722                      "  PROXY_USER=\"%s\"\n"
723                      "  HOST=\"%s\"\n"
724                      "  IP=\"%s\"\n"
725                      "  DB=\"%s\"\n"
726                      "/>\n",
727 
728                      "<AUDIT_RECORD>\n"
729                      "  <NAME>%s</NAME>\n"
730                      "  <RECORD>%s</RECORD>\n"
731                      "  <TIMESTAMP>%s</TIMESTAMP>\n"
732                      "  <CONNECTION_ID>%lu</CONNECTION_ID>\n"
733                      "  <STATUS>%d</STATUS>\n"
734                      "  <USER>%s</USER>\n"
735                      "  <PRIV_USER>%s</PRIV_USER>\n"
736                      "  <OS_LOGIN>%s</OS_LOGIN>\n"
737                      "  <PROXY_USER>%s</PROXY_USER>\n"
738                      "  <HOST>%s</HOST>\n"
739                      "  <IP>%s</IP>\n"
740                      "  <DB>%s</DB>\n"
741                      "</AUDIT_RECORD>\n",
742 
743                      "{\"audit_record\":"
744                        "{\"name\":\"%s\","
745                        "\"record\":\"%s\","
746                        "\"timestamp\":\"%s\","
747                        "\"connection_id\":\"%lu\","
748                        "\"status\":%d,"
749                        "\"user\":\"%s\","
750                        "\"priv_user\":\"%s\","
751                        "\"os_login\":\"%s\","
752                        "\"proxy_user\":\"%s\","
753                        "\"host\":\"%s\","
754                        "\"ip\":\"%s\","
755                        "\"db\":\"%s\"}}\n",
756 
757                      ("\"%s\",\"%s\",\"%s\",\"%lu\",%d,\"%s\",\"%s\",\"%s\","
758                       "\"%s\",\"%s\",\"%s\",\"%s\"\n") };
759 
760   user= escape_string(event->user.str, event->user.length,
761                       endptr, endbuf - endptr, &endptr, NULL);
762   priv_user= escape_string(event->priv_user.str,
763                            event->priv_user.length,
764                            endptr, endbuf - endptr, &endptr, NULL);
765   external_user= escape_string(event->external_user.str,
766                                event->external_user.length,
767                                endptr, endbuf - endptr, &endptr, NULL);
768   proxy_user= escape_string(event->proxy_user.str, event->proxy_user.length,
769                             endptr, endbuf - endptr, &endptr, NULL);
770   host= escape_string(event->host.str, event->host.length,
771                       endptr, endbuf - endptr, &endptr, NULL);
772   ip= escape_string(event->ip.str, event->ip.length,
773                     endptr, endbuf - endptr, &endptr, NULL);
774   database= escape_string(event->database.str, event->database.length,
775                           endptr, endbuf - endptr, &endptr, NULL);
776 
777   assert((endptr - buf) * 2 +
778               strlen(format_string[audit_log_format]) +
779               strlen(name) +
780               MAX_RECORD_ID_SIZE +
781               MAX_TIMESTAMP_SIZE +
782               20 + /* event->thread_id */
783               20 /* event->status */
784               < buflen);
785 
786   *outlen= snprintf(endptr, endbuf - endptr,
787                     format_string[audit_log_format],
788                     name,
789                     make_record_id(id_str, sizeof(id_str)),
790                     make_timestamp(timestamp, sizeof(timestamp), t),
791                     event->connection_id,
792                     event->status, user, priv_user,external_user,
793                     proxy_user, host, ip, database);
794 
795   /* make sure that record is not truncated */
796   assert(endptr + *outlen <= buf + buflen);
797 
798   return endptr;
799 }
800 
801 static
audit_log_header(MY_STAT * stat,char * buf,size_t buflen)802 size_t audit_log_header(MY_STAT *stat, char *buf, size_t buflen)
803 {
804   const char *format_string[] = {
805                      "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
806                      "<AUDIT>\n",
807                      "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
808                      "<AUDIT>\n",
809                      "",
810                      "" };
811 
812   assert(strcmp(system_charset_info->csname, "utf8") == 0);
813 
814   log_file_time= stat->st_mtime;
815 
816   init_record_id(stat->st_size);
817 
818   if (buf == NULL)
819   {
820     return 0;
821   }
822 
823   return my_snprintf(buf, buflen, format_string[audit_log_format]);
824 }
825 
826 
827 static
audit_log_footer(char * buf,size_t buflen)828 size_t audit_log_footer(char *buf, size_t buflen)
829 {
830   const char *format_string[] = {
831                      "</AUDIT>\n",
832                      "</AUDIT>\n",
833                      "",
834                      "" };
835 
836   if (buf == NULL)
837   {
838     return 0;
839   }
840 
841   return my_snprintf(buf, buflen, format_string[audit_log_format]);
842 }
843 
844 static
init_new_log_file()845 int init_new_log_file()
846 {
847   if (audit_log_handler == HANDLER_FILE)
848   {
849     audit_handler_file_config_t opts;
850     opts.name= audit_log_file;
851     opts.rotate_on_size= audit_log_rotate_on_size;
852     opts.rotations= audit_log_rotations;
853     opts.sync_on_write= audit_log_strategy == SYNCHRONOUS;
854     opts.use_buffer= audit_log_strategy < SEMISYNCHRONOUS;
855     opts.buffer_size= audit_log_buffer_size;
856     opts.can_drop_data= audit_log_strategy == PERFORMANCE;
857     opts.header= audit_log_header;
858     opts.footer= audit_log_footer;
859 
860     log_handler= audit_handler_file_open(&opts);
861     if (log_handler == NULL)
862     {
863       my_plugin_log_message(&plugin_ptr, MY_ERROR_LEVEL,
864                             "Cannot open file %s.", audit_log_file);
865       my_plugin_perror();
866       return(1);
867     }
868   }
869   else
870   {
871     audit_handler_syslog_config_t opts;
872     opts.facility= audit_log_syslog_facility_codes[audit_log_syslog_facility];
873     opts.ident= audit_log_syslog_ident;
874     opts.priority= audit_log_syslog_priority_codes[audit_log_syslog_priority];
875     opts.header= audit_log_header;
876     opts.footer= audit_log_footer;
877 
878     log_handler= audit_handler_syslog_open(&opts);
879     if (log_handler == NULL)
880     {
881       my_plugin_log_message(&plugin_ptr, MY_ERROR_LEVEL,
882                             "Cannot open syslog.");
883       my_plugin_perror();
884       return(1);
885     }
886   }
887 
888   return(0);
889 }
890 
891 
892 static
reopen_log_file()893 int reopen_log_file()
894 {
895   if (audit_handler_flush(log_handler))
896   {
897     my_plugin_log_message(&plugin_ptr, MY_ERROR_LEVEL, "Cannot open file %s.",
898                           audit_log_file);
899     my_plugin_perror();
900     return(1);
901   }
902 
903   return(0);
904 }
905 
906 typedef struct
907 {
908   /* number of included databases */
909   int databases_included;
910   /* number of excluded databases */
911   int databases_excluded;
912   /* number of accessed databases */
913   int databases_accessed;
914   /* query */
915   const char *query;
916 } query_stack_frame;
917 
918 typedef struct
919 {
920   size_t size;
921   size_t top;
922   query_stack_frame *frames;
923 } query_stack;
924 
925 /*
926  Struct to store various THD specific data
927  */
928 typedef struct
929 {
930   /* size of allocated large buffer for record formatting */
931   size_t record_buffer_size;
932   /* large buffer for record formatting */
933   char *record_buffer;
934   /* skip session logging */
935   my_bool skip_session;
936   /* skip logging for the next query */
937   my_bool skip_query;
938   /* default database */
939   char db[NAME_LEN + 1];
940   /* default database candidate */
941   char init_db_query[NAME_LEN + 1];
942   /* call stack */
943   query_stack stack;
944 } audit_log_thd_local;
945 
946 /*
947  Return pointer to THD specific data.
948  */
949 static
950 audit_log_thd_local *get_thd_local(MYSQL_THD thd);
951 
952 /*
953  Allocate and return buffer of given size.
954  */
955 static
956 char *get_record_buffer(MYSQL_THD thd, size_t size);
957 
958 /*
959  Allocate and return given number of stack frames.
960  */
961 static
962 query_stack_frame *realloc_stack_frames(MYSQL_THD thd, size_t size);
963 
964 
965 static
audit_log_plugin_init(MYSQL_PLUGIN plugin_info)966 int audit_log_plugin_init(MYSQL_PLUGIN plugin_info)
967 {
968   char buf[1024];
969   size_t len;
970   int count;
971 
972   plugin_ptr= plugin_info;
973 
974   count= array_elements(all_audit_log_memory);
975   mysql_memory_register(AUDIT_LOG_PSI_CATEGORY, all_audit_log_memory, count);
976   logger_init_mutexes();
977 
978   audit_log_filter_init();
979 
980   if (audit_log_exclude_accounts != NULL && audit_log_include_accounts != NULL)
981   {
982     my_plugin_log_message(&plugin_ptr, MY_ERROR_LEVEL,
983                           "Both 'audit_log_exclude_accounts' and "
984                           "'audit_log_include_accounts' are not NULL\n");
985     goto validation_error;
986   }
987 
988   if (audit_log_exclude_commands != NULL && audit_log_include_commands != NULL)
989   {
990     my_plugin_log_message(&plugin_ptr, MY_ERROR_LEVEL,
991                           "Both 'audit_log_exclude_commands' and "
992                           "'audit_log_include_commands' are not NULL\n");
993     goto validation_error;
994   }
995 
996   if (audit_log_exclude_databases != NULL
997       && audit_log_include_databases != NULL)
998   {
999     my_plugin_log_message(&plugin_ptr, MY_ERROR_LEVEL,
1000                           "Both 'audit_log_exclude_databases' and "
1001                           "'audit_log_include_databases' are not NULL\n");
1002     goto validation_error;
1003   }
1004 
1005   if (audit_log_exclude_accounts != NULL)
1006   {
1007     audit_log_exclude_accounts= my_strdup(PSI_NOT_INSTRUMENTED,
1008                                           audit_log_exclude_accounts,
1009                                           MYF(MY_FAE));
1010     audit_log_set_exclude_accounts(audit_log_exclude_accounts);
1011   }
1012   if (audit_log_include_accounts != NULL)
1013   {
1014     audit_log_include_accounts= my_strdup(PSI_NOT_INSTRUMENTED,
1015                                           audit_log_include_accounts,
1016                                           MYF(MY_FAE));
1017     audit_log_set_include_accounts(audit_log_include_accounts);
1018   }
1019   if (audit_log_exclude_commands != NULL)
1020   {
1021     audit_log_exclude_commands= my_strdup(PSI_NOT_INSTRUMENTED,
1022                                           audit_log_exclude_commands,
1023                                           MYF(MY_FAE));
1024     audit_log_set_exclude_commands(audit_log_exclude_commands);
1025   }
1026   if (audit_log_include_commands != NULL)
1027   {
1028     audit_log_include_commands= my_strdup(PSI_NOT_INSTRUMENTED,
1029                                           audit_log_include_commands,
1030                                           MYF(MY_FAE));
1031     audit_log_set_include_commands(audit_log_include_commands);
1032   }
1033   if (audit_log_exclude_databases != NULL)
1034   {
1035     audit_log_exclude_databases= my_strdup(PSI_NOT_INSTRUMENTED,
1036                                           audit_log_exclude_databases,
1037                                           MYF(MY_FAE));
1038     audit_log_set_exclude_databases(audit_log_exclude_databases);
1039   }
1040   if (audit_log_include_databases != NULL)
1041   {
1042     audit_log_include_databases= my_strdup(PSI_NOT_INSTRUMENTED,
1043                                           audit_log_include_databases,
1044                                           MYF(MY_FAE));
1045     audit_log_set_include_databases(audit_log_include_databases);
1046   }
1047 
1048   if (init_new_log_file())
1049     return(1);
1050 
1051   if (audit_log_audit_record(buf, sizeof(buf), "Audit", time(NULL), &len))
1052     audit_log_write(buf, len);
1053 
1054   return 0;
1055 
1056 validation_error:
1057 
1058   audit_log_exclude_accounts= audit_log_include_accounts= NULL;
1059   audit_log_exclude_commands= audit_log_include_commands= NULL;
1060   audit_log_exclude_databases= audit_log_include_databases= NULL;
1061 
1062   return 1;
1063 }
1064 
1065 
1066 static
audit_log_plugin_deinit(void * arg MY_ATTRIBUTE ((unused)))1067 int audit_log_plugin_deinit(void *arg MY_ATTRIBUTE((unused)))
1068 {
1069   char buf[1024];
1070   size_t len;
1071 
1072   if (audit_log_audit_record(buf, sizeof(buf), "NoAudit", time(NULL), &len))
1073     audit_log_write(buf, len);
1074 
1075   audit_handler_close(log_handler);
1076 
1077   audit_log_filter_destroy();
1078 
1079   my_free(audit_log_include_accounts);
1080   my_free(audit_log_exclude_accounts);
1081 
1082   my_free(audit_log_include_databases);
1083   my_free(audit_log_exclude_databases);
1084 
1085   my_free(audit_log_include_commands);
1086   my_free(audit_log_exclude_commands);
1087 
1088   return(0);
1089 }
1090 
1091 
1092 static
is_event_class_allowed_by_policy(mysql_event_class_t class,enum audit_log_policy_t policy)1093 int is_event_class_allowed_by_policy(mysql_event_class_t class,
1094                                      enum audit_log_policy_t policy)
1095 {
1096   static unsigned int class_mask[]=
1097   {
1098     /* ALL */
1099     (1 << MYSQL_AUDIT_GENERAL_CLASS) | (1 << MYSQL_AUDIT_CONNECTION_CLASS),
1100     0,                                                        /* NONE */
1101     (1 << MYSQL_AUDIT_CONNECTION_CLASS),                      /* LOGINS */
1102     (1 << MYSQL_AUDIT_GENERAL_CLASS),                         /* QUERIES */
1103   };
1104 
1105   return (class_mask[policy] & (1 << class)) != 0;
1106 }
1107 
1108 static
next_word(const char * str,size_t * len,const struct charset_info_st * charset)1109 const char *next_word(const char *str, size_t *len,
1110                       const struct charset_info_st *charset)
1111 {
1112   while (*str && my_isspace(charset, *str))
1113   {
1114     if (*str == '/' && str[1] == '*' && str[2] == '!')
1115       str+= 3;
1116     else if (*str == '/' && str[1] == '*')
1117     {
1118       while (*str && !(*str == '*' && str[1] == '/'))
1119         str++;
1120     }
1121     else
1122       str++;
1123   }
1124 
1125   *len= 0;
1126   while (str[*len] && my_isvar(charset, str[*len]))
1127     (*len)++;
1128 
1129   if (*len == 0 && *str == '`')
1130   {
1131     (*len)++;
1132     while (str[*len])
1133     {
1134       if (str[*len] == '`' && str[*len + 1] == '`')
1135         (*len)++;
1136       else if (str[*len] == '`')
1137         break;
1138       (*len)++;
1139     }
1140     (*len)++;
1141   }
1142 
1143   return str;
1144 }
1145 
1146 
1147 static
audit_log_update_thd_local(MYSQL_THD thd,audit_log_thd_local * local,unsigned int event_class,const void * event)1148 my_bool audit_log_update_thd_local(MYSQL_THD thd,
1149                                    audit_log_thd_local *local,
1150                                    unsigned int event_class,
1151                                    const void *event)
1152 {
1153   assert(audit_log_include_accounts == NULL ||
1154               audit_log_exclude_accounts == NULL);
1155 
1156   assert(audit_log_include_databases == NULL ||
1157               audit_log_exclude_databases == NULL);
1158 
1159   assert(audit_log_include_commands == NULL ||
1160               audit_log_exclude_commands == NULL);
1161 
1162   if (event_class == MYSQL_AUDIT_CONNECTION_CLASS)
1163   {
1164     const struct mysql_event_connection *event_connection=
1165       (const struct mysql_event_connection *) event;
1166     LEX_STRING priv_user, priv_host;
1167     MYSQL_SECURITY_CONTEXT ctx;
1168 
1169     if (thd_get_security_context(thd, &ctx))
1170     {
1171       my_message(ER_AUDIT_API_ABORT, "Error: can not get security context",
1172                  MYF(0));
1173       return FALSE;
1174     }
1175 
1176     if (security_context_get_option(ctx, "priv_user", &priv_user))
1177     {
1178       my_message(ER_AUDIT_API_ABORT, "Error: can not get priv_user from "
1179                  "security context", MYF(0));
1180       return FALSE;
1181     }
1182 
1183     if (security_context_get_option(ctx, "priv_host", &priv_host))
1184     {
1185       my_message(ER_AUDIT_API_ABORT, "Error: can not get priv_host from "
1186                  "security context", MYF(0));
1187       return FALSE;
1188     }
1189 
1190     local->skip_session= FALSE;
1191     if (audit_log_include_accounts != NULL &&
1192         !audit_log_check_account_included(priv_user.str, priv_user.length,
1193                                           priv_host.str, priv_host.length))
1194       local->skip_session= TRUE;
1195     if (audit_log_exclude_accounts != NULL &&
1196         audit_log_check_account_excluded(priv_user.str, priv_user.length,
1197                                          priv_host.str, priv_host.length))
1198       local->skip_session= TRUE;
1199 
1200     if (event_connection->status == 0)
1201     {
1202       /* track default DB change */
1203       assert(event_connection->database.length <= sizeof(local->db));
1204       memcpy(local->db, event_connection->database.str,
1205              event_connection->database.length);
1206       local->db[event_connection->database.length]= 0;
1207     }
1208   }
1209   else if (event_class == MYSQL_AUDIT_GENERAL_CLASS)
1210   {
1211     const struct mysql_event_general *event_general=
1212       (const struct mysql_event_general *) event;
1213 
1214     if (event_general->event_subclass == MYSQL_AUDIT_GENERAL_STATUS)
1215     {
1216       local->skip_query= FALSE;
1217 
1218       if (local->stack.frames[local->stack.top].query
1219           == event_general->general_query.str)
1220       {
1221         local->skip_query|= audit_log_include_databases
1222               && local->stack.frames[local->stack.top].databases_accessed > 0
1223               && local->stack.frames[local->stack.top].databases_included == 0;
1224 
1225         local->skip_query|= audit_log_exclude_databases
1226               && local->stack.frames[local->stack.top].databases_accessed > 0
1227               && local->stack.frames[local->stack.top].databases_excluded
1228                  == local->stack.frames[local->stack.top].databases_accessed;
1229 
1230         local->stack.frames[local->stack.top].databases_included= 0;
1231         local->stack.frames[local->stack.top].databases_accessed= 0;
1232         local->stack.frames[local->stack.top].databases_excluded= 0;
1233         local->stack.frames[local->stack.top].query= NULL;
1234 
1235         if (local->stack.top > 0)
1236           --local->stack.top;
1237        }
1238 
1239       local->skip_query|= audit_log_include_commands
1240             && !audit_log_check_command_included(
1241                      event_general->general_sql_command.str,
1242                      event_general->general_sql_command.length);
1243 
1244       local->skip_query|= audit_log_exclude_commands
1245             && audit_log_check_command_excluded(
1246                      event_general->general_sql_command.str,
1247                      event_general->general_sql_command.length);
1248 
1249       if (!local->skip_query &&
1250           ((event_general->general_command.length == 4 &&
1251             strncmp(event_general->general_command.str, "Quit", 4) == 0) ||
1252            (event_general->general_command.length == 11 &&
1253             strncmp(event_general->general_command.str,
1254                     "Change user", 11) == 0)))
1255         local->skip_query= TRUE;
1256     }
1257 
1258     if (event_general->event_subclass == MYSQL_AUDIT_GENERAL_LOG &&
1259         event_general->general_command.length == 7 &&
1260         strncmp(event_general->general_command.str, "Init DB", 7) == 0 &&
1261         event_general->general_query.str != NULL &&
1262         strpbrk("\n\r\t ", event_general->general_query.str) == NULL)
1263     {
1264       /* Database is about to be changed. Server doesn't provide database
1265       name in STATUS event, so remember it now. */
1266 
1267       assert(event_general->general_query.length <= sizeof(local->db));
1268       memcpy(local->db, event_general->general_query.str,
1269              event_general->general_query.length);
1270       local->db[event_general->general_query.length]= 0;
1271     }
1272     if (event_general->event_subclass == MYSQL_AUDIT_GENERAL_STATUS &&
1273         event_general->general_sql_command.length == 9 &&
1274         strncmp(event_general->general_sql_command.str, "change_db", 9) == 0 &&
1275         event_general->general_command.length == 5 &&
1276         strncmp(event_general->general_command.str, "Query", 5) == 0 &&
1277         event_general->general_error_code == 0)
1278     {
1279       /* it's "use dbname" query */
1280 
1281       size_t len;
1282       const char *word;
1283 
1284       word= next_word(event_general->general_query.str, &len,
1285                       event_general->general_charset);
1286       if (strncasecmp("use", word, len) == 0)
1287       {
1288         uint errors;
1289 
1290         word= next_word(word + len, &len, event_general->general_charset);
1291         if (*word == '`')
1292         {
1293           word++;
1294           len-= 2;
1295         }
1296         len= my_convert(local->db, sizeof(local->db) - 1, system_charset_info,
1297                         word, len, event_general->general_charset, &errors);
1298         local->db[len]= 0;
1299       }
1300     }
1301   }
1302   else if (event_class == MYSQL_AUDIT_TABLE_ACCESS_CLASS)
1303   {
1304     const struct mysql_event_table_access *event_table=
1305       (const struct mysql_event_table_access *) event;
1306 
1307     if (local->stack.frames[local->stack.top].query != event_table->query.str
1308         && local->stack.frames[local->stack.top].query != NULL)
1309     {
1310       if (++local->stack.top >= local->stack.size)
1311         realloc_stack_frames(thd, local->stack.size * 2);
1312     }
1313     local->stack.frames[local->stack.top].query= event_table->query.str;
1314 
1315     ++local->stack.frames[local->stack.top].databases_accessed;
1316 
1317     if (audit_log_include_databases != NULL &&
1318         audit_log_check_database_included(event_table->table_database.str,
1319                                           event_table->table_database.length))
1320       ++local->stack.frames[local->stack.top].databases_included;
1321 
1322     if (audit_log_exclude_databases != NULL &&
1323         audit_log_check_database_excluded(event_table->table_database.str,
1324                                           event_table->table_database.length))
1325       ++local->stack.frames[local->stack.top].databases_excluded;
1326   }
1327   return TRUE;
1328 }
1329 
1330 
1331 static
audit_log_notify(MYSQL_THD thd MY_ATTRIBUTE ((unused)),mysql_event_class_t event_class,const void * event)1332 int audit_log_notify(MYSQL_THD thd MY_ATTRIBUTE((unused)),
1333                      mysql_event_class_t event_class,
1334                      const void *event)
1335 {
1336   char buf[4096];
1337   char *log_rec = NULL;
1338   char *allocated_buf= get_record_buffer(thd, 0);
1339   size_t len, buflen;
1340   audit_log_thd_local *local= get_thd_local(thd);
1341 
1342   if (!audit_log_update_thd_local(thd, local, event_class, event))
1343     return 1;
1344 
1345   if (!is_event_class_allowed_by_policy(event_class, audit_log_policy))
1346     return 0;
1347 
1348   if (local->skip_session)
1349     return 0;
1350 
1351   if (event_class == MYSQL_AUDIT_GENERAL_CLASS)
1352   {
1353     const struct mysql_event_general *event_general=
1354       (const struct mysql_event_general *) event;
1355     switch (event_general->event_subclass)
1356     {
1357     case MYSQL_AUDIT_GENERAL_STATUS:
1358       if (local->skip_query)
1359         break;
1360 
1361       /* use allocated buffer if available */
1362       if (allocated_buf != NULL)
1363       {
1364         log_rec= allocated_buf;
1365         buflen= local->record_buffer_size;
1366       }
1367       else
1368       {
1369         log_rec= buf;
1370         buflen= sizeof(buf);
1371       }
1372       log_rec= audit_log_general_record(log_rec, buflen,
1373                                         event_general->general_command.str,
1374                                         event_general->general_time,
1375                                         event_general->general_error_code,
1376                                         event_general, local->db,
1377                                         &len);
1378       if (len > buflen)
1379       {
1380         buflen= len + 1024;
1381         log_rec= audit_log_general_record(get_record_buffer(thd, buflen),
1382                                           buflen,
1383                                           event_general->general_command.str,
1384                                           event_general->general_time,
1385                                           event_general->general_error_code,
1386                                           event_general, local->db,
1387                                           &len);
1388         assert(log_rec);
1389       }
1390       if (log_rec)
1391         audit_log_write(log_rec, len);
1392       break;
1393     case MYSQL_AUDIT_GENERAL_LOG:
1394     case MYSQL_AUDIT_GENERAL_ERROR:
1395     case MYSQL_AUDIT_GENERAL_RESULT:
1396       break;
1397     }
1398   }
1399   else if (event_class == MYSQL_AUDIT_CONNECTION_CLASS)
1400   {
1401     const struct mysql_event_connection *event_connection=
1402       (const struct mysql_event_connection *) event;
1403     switch (event_connection->event_subclass)
1404     {
1405     case MYSQL_AUDIT_CONNECTION_CONNECT:
1406       log_rec= audit_log_connection_record(buf, sizeof(buf), "Connect",
1407                                            time(NULL), event_connection, &len);
1408       break;
1409     case MYSQL_AUDIT_CONNECTION_DISCONNECT:
1410       log_rec= audit_log_connection_record(buf, sizeof(buf), "Quit",
1411                                            time(NULL), event_connection, &len);
1412       break;
1413    case MYSQL_AUDIT_CONNECTION_CHANGE_USER:
1414       log_rec= audit_log_connection_record(buf, sizeof(buf), "Change user",
1415                                            time(NULL), event_connection, &len);
1416       break;
1417     default:
1418       break;
1419     }
1420     if (log_rec)
1421       audit_log_write(log_rec, len);
1422   }
1423   return 0;
1424 }
1425 
1426 
1427 /*
1428  * Plugin system vars
1429  */
1430 
1431 static MYSQL_SYSVAR_STR(file, audit_log_file,
1432   PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY | PLUGIN_VAR_MEMALLOC,
1433   "The name of the log file.", NULL, NULL, default_audit_log_file);
1434 
1435 static const char *audit_log_policy_names[]=
1436                     { "ALL", "NONE", "LOGINS", "QUERIES", 0 };
1437 
1438 static TYPELIB audit_log_policy_typelib=
1439 {
1440   array_elements(audit_log_policy_names) - 1, "audit_log_policy_typelib",
1441   audit_log_policy_names, NULL
1442 };
1443 
1444 static MYSQL_SYSVAR_ENUM(policy, audit_log_policy, PLUGIN_VAR_RQCMDARG,
1445        "The policy controlling the information written by the audit log "
1446        "plugin to its log file.", NULL, NULL, ALL,
1447        &audit_log_policy_typelib);
1448 
1449 static const char *audit_log_strategy_names[]=
1450   { "ASYNCHRONOUS", "PERFORMANCE", "SEMISYNCHRONOUS", "SYNCHRONOUS", 0 };
1451 static TYPELIB audit_log_strategy_typelib=
1452 {
1453   array_elements(audit_log_strategy_names) - 1, "audit_log_strategy_typelib",
1454   audit_log_strategy_names, NULL
1455 };
1456 
1457 static MYSQL_SYSVAR_ENUM(strategy, audit_log_strategy,
1458        PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
1459        "The logging method used by the audit log plugin, "
1460        "if FILE handler is used.", NULL, NULL,
1461        ASYNCHRONOUS, &audit_log_strategy_typelib);
1462 
1463 static const char *audit_log_format_names[]=
1464   { "OLD", "NEW", "JSON", "CSV", 0 };
1465 static TYPELIB audit_log_format_typelib=
1466 {
1467   array_elements(audit_log_format_names) - 1, "audit_log_format_typelib",
1468   audit_log_format_names, NULL
1469 };
1470 
1471 static MYSQL_SYSVAR_ENUM(format, audit_log_format,
1472        PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
1473        "The audit log file format.", NULL, NULL,
1474        ASYNCHRONOUS, &audit_log_format_typelib);
1475 
1476 static const char *audit_log_handler_names[]=
1477   { "FILE", "SYSLOG", 0 };
1478 static TYPELIB audit_log_handler_typelib=
1479 {
1480   array_elements(audit_log_handler_names) - 1, "audit_log_handler_typelib",
1481   audit_log_handler_names, NULL
1482 };
1483 
1484 static MYSQL_SYSVAR_ENUM(handler, audit_log_handler,
1485        PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
1486        "The audit log handler.", NULL, NULL,
1487        HANDLER_FILE, &audit_log_handler_typelib);
1488 
1489 static MYSQL_SYSVAR_ULONGLONG(buffer_size, audit_log_buffer_size,
1490   PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
1491   "The size of the buffer for asynchronous logging, "
1492   "if FILE handler is used.",
1493   NULL, NULL, 1048576UL, 4096UL, ULLONG_MAX, 4096UL);
1494 
1495 static
audit_log_rotate_on_size_update(MYSQL_THD thd MY_ATTRIBUTE ((unused)),struct st_mysql_sys_var * var MY_ATTRIBUTE ((unused)),void * var_ptr MY_ATTRIBUTE ((unused)),const void * save)1496 void audit_log_rotate_on_size_update(
1497           MYSQL_THD thd MY_ATTRIBUTE((unused)),
1498           struct st_mysql_sys_var *var MY_ATTRIBUTE((unused)),
1499           void *var_ptr MY_ATTRIBUTE((unused)),
1500           const void *save)
1501 {
1502   ulonglong new_val= *(ulonglong *)(save);
1503 
1504   audit_handler_set_option(log_handler, OPT_ROTATE_ON_SIZE, &new_val);
1505 
1506   audit_log_rotate_on_size= new_val;
1507 }
1508 
1509 static MYSQL_SYSVAR_ULONGLONG(rotate_on_size, audit_log_rotate_on_size,
1510   PLUGIN_VAR_RQCMDARG,
1511   "Maximum size of the log to start the rotation, if FILE handler is used.",
1512   NULL, audit_log_rotate_on_size_update, 0UL, 0UL, ULLONG_MAX, 4096UL);
1513 
1514 static
audit_log_rotations_update(MYSQL_THD thd MY_ATTRIBUTE ((unused)),struct st_mysql_sys_var * var MY_ATTRIBUTE ((unused)),void * var_ptr MY_ATTRIBUTE ((unused)),const void * save)1515 void audit_log_rotations_update(
1516           MYSQL_THD thd MY_ATTRIBUTE((unused)),
1517           struct st_mysql_sys_var *var MY_ATTRIBUTE((unused)),
1518           void *var_ptr MY_ATTRIBUTE((unused)),
1519           const void *save)
1520 {
1521   ulonglong new_val= *(ulonglong *)(save);
1522 
1523   audit_handler_set_option(log_handler, OPT_ROTATIONS, &new_val);
1524 
1525   audit_log_rotations= new_val;
1526 }
1527 
1528 static MYSQL_SYSVAR_ULONGLONG(rotations, audit_log_rotations,
1529   PLUGIN_VAR_RQCMDARG,
1530   "Maximum number of rotations to keep, if FILE handler is used.",
1531   NULL, audit_log_rotations_update, 0UL, 0UL, 999UL, 1UL);
1532 
1533 static
audit_log_flush_update(MYSQL_THD thd MY_ATTRIBUTE ((unused)),struct st_mysql_sys_var * var MY_ATTRIBUTE ((unused)),void * var_ptr MY_ATTRIBUTE ((unused)),const void * save)1534 void audit_log_flush_update(
1535           MYSQL_THD thd MY_ATTRIBUTE((unused)),
1536           struct st_mysql_sys_var *var MY_ATTRIBUTE((unused)),
1537           void *var_ptr MY_ATTRIBUTE((unused)),
1538           const void *save)
1539 {
1540   char new_val= *(const char *)(save);
1541 
1542   if (new_val != audit_log_flush && new_val)
1543   {
1544     audit_log_flush= TRUE;
1545     reopen_log_file();
1546     audit_log_flush= FALSE;
1547   }
1548 }
1549 
1550 static MYSQL_SYSVAR_BOOL(flush, audit_log_flush,
1551        PLUGIN_VAR_OPCMDARG, "Flush the log file.", NULL,
1552        audit_log_flush_update, 0);
1553 
1554 static MYSQL_SYSVAR_STR(syslog_ident, audit_log_syslog_ident,
1555   PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY | PLUGIN_VAR_MEMALLOC,
1556   "The string that will be prepended to each log message, "
1557   "if SYSLOG handler is used.",
1558   NULL, NULL, default_audit_log_syslog_ident);
1559 
1560 static TYPELIB audit_log_syslog_facility_typelib=
1561 {
1562   array_elements(audit_log_syslog_facility_names) - 1,
1563   "audit_log_syslog_facility_typelib",
1564   audit_log_syslog_facility_names, NULL
1565 };
1566 
1567 static MYSQL_SYSVAR_ENUM(syslog_facility, audit_log_syslog_facility,
1568        PLUGIN_VAR_RQCMDARG,
1569        "The syslog facility to assign to messages, if SYSLOG handler is used.",
1570        NULL, NULL, 0,
1571        &audit_log_syslog_facility_typelib);
1572 
1573 static TYPELIB audit_log_syslog_priority_typelib=
1574 {
1575   array_elements(audit_log_syslog_priority_names) - 1,
1576   "audit_log_syslog_priority_typelib",
1577   audit_log_syslog_priority_names, NULL
1578 };
1579 
1580 static MYSQL_SYSVAR_ENUM(syslog_priority, audit_log_syslog_priority,
1581        PLUGIN_VAR_RQCMDARG,
1582        "Priority to be assigned to all messages written to syslog.",
1583        NULL, NULL, 0,
1584        &audit_log_syslog_priority_typelib);
1585 
1586 static MYSQL_THDVAR_STR(record_buffer,
1587                         PLUGIN_VAR_READONLY | PLUGIN_VAR_MEMALLOC | \
1588                         PLUGIN_VAR_NOSYSVAR | PLUGIN_VAR_NOCMDOPT,
1589                         "Buffer for query formatting.", NULL, NULL, "");
1590 
1591 static MYSQL_THDVAR_STR(query_stack,
1592                         PLUGIN_VAR_READONLY | PLUGIN_VAR_MEMALLOC | \
1593                         PLUGIN_VAR_NOSYSVAR | PLUGIN_VAR_NOCMDOPT,
1594                         "Query stack.", NULL, NULL, "");
1595 
1596 static
1597 int
audit_log_exclude_accounts_validate(MYSQL_THD thd MY_ATTRIBUTE ((unused)),struct st_mysql_sys_var * var MY_ATTRIBUTE ((unused)),void * save,struct st_mysql_value * value)1598 audit_log_exclude_accounts_validate(
1599           MYSQL_THD thd MY_ATTRIBUTE((unused)),
1600           struct st_mysql_sys_var *var MY_ATTRIBUTE((unused)),
1601           void *save,
1602           struct st_mysql_value *value)
1603 {
1604   const char *new_val;
1605   char buf[80];
1606   int len= sizeof(buf);
1607 
1608   if (audit_log_include_accounts)
1609     return 1;
1610 
1611   new_val = value->val_str(value, buf, &len);
1612 
1613   *(const char **)(save) = new_val;
1614 
1615   return 0;
1616 }
1617 
1618 static
audit_log_exclude_accounts_update(MYSQL_THD thd MY_ATTRIBUTE ((unused)),struct st_mysql_sys_var * var MY_ATTRIBUTE ((unused)),void * var_ptr MY_ATTRIBUTE ((unused)),const void * save)1619 void audit_log_exclude_accounts_update(
1620           MYSQL_THD thd MY_ATTRIBUTE((unused)),
1621           struct st_mysql_sys_var *var MY_ATTRIBUTE((unused)),
1622           void *var_ptr MY_ATTRIBUTE((unused)),
1623           const void *save)
1624 {
1625   const char *new_val= *(const char **)(save);
1626 
1627   assert(audit_log_include_accounts == NULL);
1628 
1629   my_free(audit_log_exclude_accounts);
1630   audit_log_exclude_accounts= NULL;
1631 
1632   if (new_val != NULL)
1633   {
1634     audit_log_exclude_accounts= my_strdup(PSI_NOT_INSTRUMENTED,
1635                                           new_val, MYF(MY_FAE));
1636     audit_log_set_exclude_accounts(audit_log_exclude_accounts);
1637   }
1638   else
1639   {
1640     audit_log_set_exclude_accounts("");
1641   }
1642 }
1643 
1644 static MYSQL_SYSVAR_STR(exclude_accounts, audit_log_exclude_accounts,
1645        PLUGIN_VAR_RQCMDARG,
1646        "Comma separated list of accounts "
1647        "for which events should not be logged.",
1648        audit_log_exclude_accounts_validate,
1649        audit_log_exclude_accounts_update, NULL);
1650 
1651 static
1652 int
audit_log_include_accounts_validate(MYSQL_THD thd MY_ATTRIBUTE ((unused)),struct st_mysql_sys_var * var MY_ATTRIBUTE ((unused)),void * save,struct st_mysql_value * value)1653 audit_log_include_accounts_validate(
1654           MYSQL_THD thd MY_ATTRIBUTE((unused)),
1655           struct st_mysql_sys_var *var MY_ATTRIBUTE((unused)),
1656           void *save,
1657           struct st_mysql_value *value)
1658 {
1659   const char *new_val;
1660   char buf[80];
1661   int len= sizeof(buf);
1662 
1663   if (audit_log_exclude_accounts)
1664     return 1;
1665 
1666   new_val = value->val_str(value, buf, &len);
1667 
1668   *(const char **)(save) = new_val;
1669 
1670   return 0;
1671 }
1672 
1673 static
audit_log_include_accounts_update(MYSQL_THD thd MY_ATTRIBUTE ((unused)),struct st_mysql_sys_var * var MY_ATTRIBUTE ((unused)),void * var_ptr MY_ATTRIBUTE ((unused)),const void * save)1674 void audit_log_include_accounts_update(
1675           MYSQL_THD thd MY_ATTRIBUTE((unused)),
1676           struct st_mysql_sys_var *var MY_ATTRIBUTE((unused)),
1677           void *var_ptr MY_ATTRIBUTE((unused)),
1678           const void *save)
1679 {
1680   const char *new_val= *(const char **)(save);
1681 
1682   assert(audit_log_exclude_accounts == NULL);
1683 
1684   my_free(audit_log_include_accounts);
1685   audit_log_include_accounts= NULL;
1686 
1687   if (new_val != NULL)
1688   {
1689     audit_log_include_accounts= my_strdup(PSI_NOT_INSTRUMENTED,
1690                                           new_val, MYF(MY_FAE));
1691     audit_log_set_include_accounts(audit_log_include_accounts);
1692   }
1693   else
1694   {
1695     audit_log_set_include_accounts("");
1696   }
1697 }
1698 
1699 static MYSQL_SYSVAR_STR(include_accounts, audit_log_include_accounts,
1700        PLUGIN_VAR_RQCMDARG,
1701        "Comma separated list of accounts for which events should be logged.",
1702        audit_log_include_accounts_validate,
1703        audit_log_include_accounts_update, NULL);
1704 
1705 static
1706 int
audit_log_exclude_databases_validate(MYSQL_THD thd MY_ATTRIBUTE ((unused)),struct st_mysql_sys_var * var MY_ATTRIBUTE ((unused)),void * save,struct st_mysql_value * value)1707 audit_log_exclude_databases_validate(
1708           MYSQL_THD thd MY_ATTRIBUTE((unused)),
1709           struct st_mysql_sys_var *var MY_ATTRIBUTE((unused)),
1710           void *save,
1711           struct st_mysql_value *value)
1712 {
1713   const char *new_val;
1714   char buf[80];
1715   int len= sizeof(buf);
1716 
1717   if (audit_log_include_databases)
1718     return 1;
1719 
1720   new_val = value->val_str(value, buf, &len);
1721 
1722   *(const char **)(save) = new_val;
1723 
1724   return 0;
1725 }
1726 
1727 static
audit_log_exclude_databases_update(MYSQL_THD thd MY_ATTRIBUTE ((unused)),struct st_mysql_sys_var * var MY_ATTRIBUTE ((unused)),void * var_ptr MY_ATTRIBUTE ((unused)),const void * save)1728 void audit_log_exclude_databases_update(
1729           MYSQL_THD thd MY_ATTRIBUTE((unused)),
1730           struct st_mysql_sys_var *var MY_ATTRIBUTE((unused)),
1731           void *var_ptr MY_ATTRIBUTE((unused)),
1732           const void *save)
1733 {
1734   const char *new_val= *(const char **)(save);
1735 
1736   assert(audit_log_include_databases == NULL);
1737 
1738   my_free(audit_log_exclude_databases);
1739   audit_log_exclude_databases= NULL;
1740 
1741   if (new_val != NULL)
1742   {
1743     audit_log_exclude_databases= my_strdup(PSI_NOT_INSTRUMENTED,
1744                                           new_val, MYF(MY_FAE));
1745     audit_log_set_exclude_databases(audit_log_exclude_databases);
1746   }
1747   else
1748   {
1749     audit_log_set_exclude_databases("");
1750   }
1751 }
1752 
1753 static MYSQL_SYSVAR_STR(exclude_databases, audit_log_exclude_databases,
1754        PLUGIN_VAR_RQCMDARG,
1755        "Comma separated list of databases "
1756        "for which events should not be logged.",
1757        audit_log_exclude_databases_validate,
1758        audit_log_exclude_databases_update, NULL);
1759 
1760 static
1761 int
audit_log_include_databases_validate(MYSQL_THD thd MY_ATTRIBUTE ((unused)),struct st_mysql_sys_var * var MY_ATTRIBUTE ((unused)),void * save,struct st_mysql_value * value)1762 audit_log_include_databases_validate(
1763           MYSQL_THD thd MY_ATTRIBUTE((unused)),
1764           struct st_mysql_sys_var *var MY_ATTRIBUTE((unused)),
1765           void *save,
1766           struct st_mysql_value *value)
1767 {
1768   const char *new_val;
1769   char buf[80];
1770   int len= sizeof(buf);
1771 
1772   if (audit_log_exclude_databases)
1773     return 1;
1774 
1775   new_val = value->val_str(value, buf, &len);
1776 
1777   *(const char **)(save) = new_val;
1778 
1779   return 0;
1780 }
1781 
1782 static
audit_log_include_databases_update(MYSQL_THD thd MY_ATTRIBUTE ((unused)),struct st_mysql_sys_var * var MY_ATTRIBUTE ((unused)),void * var_ptr MY_ATTRIBUTE ((unused)),const void * save)1783 void audit_log_include_databases_update(
1784           MYSQL_THD thd MY_ATTRIBUTE((unused)),
1785           struct st_mysql_sys_var *var MY_ATTRIBUTE((unused)),
1786           void *var_ptr MY_ATTRIBUTE((unused)),
1787           const void *save)
1788 {
1789   const char *new_val= *(const char **)(save);
1790 
1791   assert(audit_log_exclude_databases == NULL);
1792 
1793   my_free(audit_log_include_databases);
1794   audit_log_include_databases= NULL;
1795 
1796   if (new_val != NULL)
1797   {
1798     audit_log_include_databases= my_strdup(PSI_NOT_INSTRUMENTED,
1799                                           new_val, MYF(MY_FAE));
1800     audit_log_set_include_databases(audit_log_include_databases);
1801   }
1802   else
1803   {
1804     audit_log_set_include_databases("");
1805   }
1806 }
1807 
1808 static MYSQL_SYSVAR_STR(include_databases, audit_log_include_databases,
1809        PLUGIN_VAR_RQCMDARG,
1810        "Comma separated list of databases for which events should be logged.",
1811        audit_log_include_databases_validate,
1812        audit_log_include_databases_update, NULL);
1813 
1814 static
1815 int
audit_log_exclude_commands_validate(MYSQL_THD thd MY_ATTRIBUTE ((unused)),struct st_mysql_sys_var * var MY_ATTRIBUTE ((unused)),void * save,struct st_mysql_value * value)1816 audit_log_exclude_commands_validate(
1817           MYSQL_THD thd MY_ATTRIBUTE((unused)),
1818           struct st_mysql_sys_var *var MY_ATTRIBUTE((unused)),
1819           void *save,
1820           struct st_mysql_value *value)
1821 {
1822   const char *new_val;
1823   char buf[80];
1824   int len= sizeof(buf);
1825 
1826   if (audit_log_include_commands)
1827     return 1;
1828 
1829   new_val = value->val_str(value, buf, &len);
1830 
1831   *(const char **)(save) = new_val;
1832 
1833   return 0;
1834 }
1835 
1836 static
audit_log_exclude_commands_update(MYSQL_THD thd MY_ATTRIBUTE ((unused)),struct st_mysql_sys_var * var MY_ATTRIBUTE ((unused)),void * var_ptr MY_ATTRIBUTE ((unused)),const void * save)1837 void audit_log_exclude_commands_update(
1838           MYSQL_THD thd MY_ATTRIBUTE((unused)),
1839           struct st_mysql_sys_var *var MY_ATTRIBUTE((unused)),
1840           void *var_ptr MY_ATTRIBUTE((unused)),
1841           const void *save)
1842 {
1843   const char *new_val= *(const char **)(save);
1844 
1845   assert(audit_log_include_commands == NULL);
1846 
1847   my_free(audit_log_exclude_commands);
1848   audit_log_exclude_commands= NULL;
1849 
1850   if (new_val != NULL)
1851   {
1852     audit_log_exclude_commands= my_strdup(PSI_NOT_INSTRUMENTED,
1853                                           new_val, MYF(MY_FAE));
1854     audit_log_set_exclude_commands(audit_log_exclude_commands);
1855   }
1856   else
1857   {
1858     audit_log_set_exclude_commands("");
1859   }
1860 }
1861 
1862 static MYSQL_SYSVAR_STR(exclude_commands, audit_log_exclude_commands,
1863        PLUGIN_VAR_RQCMDARG,
1864        "Comma separated list of commands "
1865        "for which events should not be logged.",
1866        audit_log_exclude_commands_validate,
1867        audit_log_exclude_commands_update, NULL);
1868 
1869 static
1870 int
audit_log_include_commands_validate(MYSQL_THD thd MY_ATTRIBUTE ((unused)),struct st_mysql_sys_var * var MY_ATTRIBUTE ((unused)),void * save,struct st_mysql_value * value)1871 audit_log_include_commands_validate(
1872           MYSQL_THD thd MY_ATTRIBUTE((unused)),
1873           struct st_mysql_sys_var *var MY_ATTRIBUTE((unused)),
1874           void *save,
1875           struct st_mysql_value *value)
1876 {
1877   const char *new_val;
1878   char buf[80];
1879   int len= sizeof(buf);
1880 
1881   if (audit_log_exclude_commands)
1882     return 1;
1883 
1884   new_val = value->val_str(value, buf, &len);
1885 
1886   *(const char **)(save) = new_val;
1887 
1888   return 0;
1889 }
1890 
1891 static
audit_log_include_commands_update(MYSQL_THD thd MY_ATTRIBUTE ((unused)),struct st_mysql_sys_var * var MY_ATTRIBUTE ((unused)),void * var_ptr MY_ATTRIBUTE ((unused)),const void * save)1892 void audit_log_include_commands_update(
1893           MYSQL_THD thd MY_ATTRIBUTE((unused)),
1894           struct st_mysql_sys_var *var MY_ATTRIBUTE((unused)),
1895           void *var_ptr MY_ATTRIBUTE((unused)),
1896           const void *save)
1897 {
1898   const char *new_val= *(const char **)(save);
1899 
1900   assert(audit_log_exclude_commands == NULL);
1901 
1902   my_free(audit_log_include_commands);
1903   audit_log_include_commands= NULL;
1904 
1905   if (new_val != NULL)
1906   {
1907     audit_log_include_commands= my_strdup(PSI_NOT_INSTRUMENTED,
1908                                           new_val, MYF(MY_FAE));
1909     audit_log_set_include_commands(audit_log_include_commands);
1910   }
1911   else
1912   {
1913     audit_log_set_include_commands("");
1914   }
1915 }
1916 
1917 static MYSQL_SYSVAR_STR(include_commands, audit_log_include_commands,
1918        PLUGIN_VAR_RQCMDARG,
1919        "Comma separated list of commands for which events should be logged.",
1920        audit_log_include_commands_validate,
1921        audit_log_include_commands_update, NULL);
1922 
1923 static MYSQL_THDVAR_STR(local,
1924        PLUGIN_VAR_READONLY | PLUGIN_VAR_MEMALLOC | \
1925        PLUGIN_VAR_NOSYSVAR | PLUGIN_VAR_NOCMDOPT,
1926        "Local store.", NULL, NULL, "");
1927 
1928 static MYSQL_THDVAR_ULONG(local_ptr,
1929        PLUGIN_VAR_READONLY | PLUGIN_VAR_NOSYSVAR | PLUGIN_VAR_NOCMDOPT,
1930        "Local store ptr.", NULL, NULL, 0, 0, ULONG_MAX, 0);
1931 
1932 static struct st_mysql_sys_var* audit_log_system_variables[] =
1933 {
1934   MYSQL_SYSVAR(file),
1935   MYSQL_SYSVAR(policy),
1936   MYSQL_SYSVAR(strategy),
1937   MYSQL_SYSVAR(format),
1938   MYSQL_SYSVAR(buffer_size),
1939   MYSQL_SYSVAR(rotate_on_size),
1940   MYSQL_SYSVAR(rotations),
1941   MYSQL_SYSVAR(flush),
1942   MYSQL_SYSVAR(handler),
1943   MYSQL_SYSVAR(syslog_ident),
1944   MYSQL_SYSVAR(syslog_priority),
1945   MYSQL_SYSVAR(syslog_facility),
1946   MYSQL_SYSVAR(record_buffer),
1947   MYSQL_SYSVAR(query_stack),
1948   MYSQL_SYSVAR(exclude_accounts),
1949   MYSQL_SYSVAR(include_accounts),
1950   MYSQL_SYSVAR(exclude_databases),
1951   MYSQL_SYSVAR(include_databases),
1952   MYSQL_SYSVAR(exclude_commands),
1953   MYSQL_SYSVAR(include_commands),
1954   MYSQL_SYSVAR(local),
1955   MYSQL_SYSVAR(local_ptr),
1956   NULL
1957 };
1958 
1959 static char thd_local_init_buf[sizeof(audit_log_thd_local)];
1960 
1961 static
audit_log_so_init()1962 void MY_ATTRIBUTE((constructor)) audit_log_so_init()
1963 {
1964   memset(thd_local_init_buf, 1, sizeof(thd_local_init_buf) - 1);
1965   thd_local_init_buf[sizeof(thd_local_init_buf) - 1]= 0;
1966 }
1967 
1968 /*
1969  Return pointer to THD specific data.
1970  */
1971 static
get_thd_local(MYSQL_THD thd)1972 audit_log_thd_local *get_thd_local(MYSQL_THD thd)
1973 {
1974   audit_log_thd_local *local= (audit_log_thd_local *) THDVAR(thd, local_ptr);
1975 
1976   compile_time_assert(sizeof(THDVAR(thd, local_ptr)) >= sizeof(void *));
1977 
1978   if (unlikely(local == NULL))
1979   {
1980     THDVAR_SET(thd, local, thd_local_init_buf);
1981     local= (audit_log_thd_local *) THDVAR(thd, local);
1982     memset(local, 0, sizeof(audit_log_thd_local));
1983     THDVAR(thd, local_ptr)= (ulong) local;
1984 
1985     realloc_stack_frames(thd, 4);
1986   }
1987   return local;
1988 }
1989 
1990 
1991 /*
1992  Allocate and return buffer of given size.
1993  */
1994 static
get_record_buffer(MYSQL_THD thd,size_t size)1995 char *get_record_buffer(MYSQL_THD thd, size_t size)
1996 {
1997   audit_log_thd_local *local= get_thd_local(thd);
1998   char *buf= local->record_buffer;
1999 
2000   if (local->record_buffer_size < size)
2001   {
2002     local->record_buffer_size= size;
2003 
2004     buf = (char *) my_malloc(PSI_NOT_INSTRUMENTED, size, MYF(MY_FAE));
2005     memset(buf, 1, size - 1);
2006     buf[size - 1]= 0;
2007 
2008     THDVAR_SET(thd, record_buffer, buf);
2009 
2010     my_free(buf);
2011 
2012     buf = (char *) THDVAR(thd, record_buffer);
2013     local->record_buffer = buf;
2014   }
2015 
2016   return buf;
2017 }
2018 
2019 
2020 /*
2021  Allocate and return given number of stack frames.
2022  */
2023 static
realloc_stack_frames(MYSQL_THD thd,size_t size)2024 query_stack_frame *realloc_stack_frames(MYSQL_THD thd, size_t size)
2025 {
2026   audit_log_thd_local *local= get_thd_local(thd);
2027   query_stack_frame *stack= (query_stack_frame *) THDVAR(thd, query_stack);
2028 
2029   if (local->stack.size < size)
2030   {
2031     char *buf= (char *) my_malloc(PSI_NOT_INSTRUMENTED,
2032                                   (local->stack.size + size) *
2033                                   sizeof(query_stack_frame),
2034                                   MYF(MY_FAE));
2035     memset(buf + local->stack.size * sizeof(query_stack_frame), 1,
2036            size * sizeof(query_stack_frame) - 1);
2037     buf[(local->stack.size + size) * sizeof(query_stack_frame) - 1]= 0;
2038     if (local->stack.size > 0)
2039       memcpy(buf, stack, local->stack.size * sizeof(query_stack_frame));
2040     THDVAR_SET(thd, query_stack,
2041                buf + local->stack.size * sizeof(query_stack_frame));
2042     stack= (query_stack_frame *) THDVAR(thd, query_stack);
2043     memset(stack, 0, size * sizeof(query_stack_frame));
2044     if (local->stack.size > 0)
2045       memcpy(stack, buf, local->stack.size * sizeof(query_stack_frame));
2046     local->stack.frames= stack;
2047     local->stack.size= size;
2048     my_free(buf);
2049   }
2050 
2051   return stack;
2052 }
2053 
2054 
2055 /*
2056   Plugin type-specific descriptor
2057 */
2058 static struct st_mysql_audit audit_log_descriptor=
2059 {
2060   MYSQL_AUDIT_INTERFACE_VERSION,                /* interface version    */
2061   NULL,                                         /* release_thd function */
2062   audit_log_notify,                             /* notify function      */
2063   { MYSQL_AUDIT_GENERAL_ALL,
2064     MYSQL_AUDIT_CONNECTION_ALL,
2065     0, 0,
2066     MYSQL_AUDIT_TABLE_ACCESS_ALL,
2067     0, 0, 0, 0, 0 }                             /* class mask           */
2068 };
2069 
2070 /*
2071   Plugin status variables for SHOW STATUS
2072 */
2073 
2074 static struct st_mysql_show_var audit_log_status_variables[]=
2075 {
2076   {"Audit_log_buffer_size_overflow",
2077     (char*) &audit_log_buffer_size_overflow,
2078     SHOW_LONG, SHOW_SCOPE_GLOBAL},
2079   {NullS, NullS, SHOW_LONG, SHOW_SCOPE_GLOBAL}
2080 };
2081 
2082 
2083 /*
2084   Plugin library descriptor
2085 */
2086 
mysql_declare_plugin(audit_log)2087 mysql_declare_plugin(audit_log)
2088 {
2089   MYSQL_AUDIT_PLUGIN,                     /* type                            */
2090   &audit_log_descriptor,                  /* descriptor                      */
2091   "audit_log",                            /* name                            */
2092   "Percona LLC and/or its affiliates.",   /* author                          */
2093   "Audit log",                            /* description                     */
2094   PLUGIN_LICENSE_GPL,
2095   audit_log_plugin_init,                  /* init function (when loaded)     */
2096   audit_log_plugin_deinit,                /* deinit function (when unloaded) */
2097   PLUGIN_VERSION,                         /* version                         */
2098   audit_log_status_variables,             /* status variables                */
2099   audit_log_system_variables,             /* system variables                */
2100   NULL,
2101   0,
2102 }
2103 mysql_declare_plugin_end;
2104 
2105