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 <typelib.h>
27 #include <mysql_version.h>
28 #include <mysql_com.h>
29 #include <my_pthread.h>
30 #include <syslog.h>
31 
32 #include "audit_log.h"
33 #include "logger.h"
34 #include "buffer.h"
35 #include "audit_handler.h"
36 #include "filter.h"
37 #include "security_context_wrapper.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_commands= NULL;
70 static char *audit_log_include_commands= NULL;
71 uint64 audit_log_buffer_size_overflow = 0;
72 
73 static int audit_log_syslog_facility_codes[]=
74   { LOG_USER,   LOG_AUTHPRIV, LOG_CRON,   LOG_DAEMON, LOG_FTP,
75     LOG_KERN,   LOG_LPR,      LOG_MAIL,   LOG_NEWS,
76 #if (defined LOG_SECURITY)
77     LOG_SECURITY,
78 #endif
79     LOG_SYSLOG, LOG_AUTH,     LOG_UUCP,   LOG_LOCAL0, LOG_LOCAL1,
80     LOG_LOCAL2, LOG_LOCAL3,   LOG_LOCAL4, LOG_LOCAL5, LOG_LOCAL6,
81     LOG_LOCAL7, 0};
82 
83 
84 static const char *audit_log_syslog_facility_names[]=
85   { "LOG_USER",   "LOG_AUTHPRIV", "LOG_CRON",   "LOG_DAEMON", "LOG_FTP",
86     "LOG_KERN",   "LOG_LPR",      "LOG_MAIL",   "LOG_NEWS",
87 #if (defined LOG_SECURITY)
88     "LOG_SECURITY",
89 #endif
90     "LOG_SYSLOG", "LOG_AUTH",     "LOG_UUCP",   "LOG_LOCAL0", "LOG_LOCAL1",
91     "LOG_LOCAL2", "LOG_LOCAL3",   "LOG_LOCAL4", "LOG_LOCAL5", "LOG_LOCAL6",
92     "LOG_LOCAL7", 0 };
93 
94 
95 static const int audit_log_syslog_priority_codes[]=
96   { LOG_INFO,   LOG_ALERT, LOG_CRIT, LOG_ERR, LOG_WARNING,
97     LOG_NOTICE, LOG_EMERG,  LOG_DEBUG, 0 };
98 
99 
100 static const char *audit_log_syslog_priority_names[]=
101   { "LOG_INFO",   "LOG_ALERT", "LOG_CRIT", "LOG_ERR", "LOG_WARNING",
102     "LOG_NOTICE", "LOG_EMERG", "LOG_DEBUG", 0 };
103 
104 
105 static
init_record_id(off_t size)106 void init_record_id(off_t size)
107 {
108   record_id= size;
109 }
110 
111 
112 static
next_record_id()113 ulonglong next_record_id()
114 {
115   return __sync_add_and_fetch(&record_id, 1);
116 }
117 
118 
119 #define MAX_RECORD_ID_SIZE  50
120 #define MAX_TIMESTAMP_SIZE  25
121 
122 void plugin_thdvar_safe_update(MYSQL_THD thd, struct st_mysql_sys_var *var,
123                                char **dest, const char *value);
124 
125 static
fprintf_timestamp(FILE * file)126 void fprintf_timestamp(FILE *file)
127 {
128   char timebuf[50];
129   struct tm tm;
130   time_t curtime;
131 
132   memset(&tm, 0, sizeof(tm));
133   time(&curtime);
134   localtime_r(&curtime, &tm);
135 
136   strftime(timebuf, sizeof(timebuf), "%FT%T", gmtime_r(&curtime, &tm));
137 
138   fprintf(file, "%s audit_log: ", timebuf);
139 }
140 
141 
142 static
make_timestamp(char * buf,size_t buf_len,time_t t)143 char *make_timestamp(char *buf, size_t buf_len, time_t t)
144 {
145   struct tm tm;
146 
147   memset(&tm, 0, sizeof(tm));
148   strftime(buf, buf_len, "%FT%T UTC", gmtime_r(&t, &tm));
149 
150   return buf;
151 }
152 
153 static
make_record_id(char * buf,size_t buf_len)154 char *make_record_id(char *buf, size_t buf_len)
155 {
156   struct tm tm;
157   size_t len;
158 
159   memset(&tm, 0, sizeof(tm));
160   len= snprintf(buf, buf_len, "%llu_", next_record_id());
161 
162   strftime(buf + len, buf_len - len,
163            "%FT%T", gmtime_r(&log_file_time, &tm));
164 
165   return buf;
166 }
167 
168 typedef struct
169 {
170   char character;
171   size_t length;
172   const char *replacement;
173 } escape_rule_t;
174 
175 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)176 void escape_buf(const char *in, size_t *inlen, char *out, size_t *outlen,
177                 const escape_rule_t *control_escape_rules,
178                 const escape_rule_t *other_escape_rules)
179 {
180   char* outstart = out;
181   const char* base = in;
182   char* outend = out + *outlen;
183   const char* inend;
184   const escape_rule_t *replace_rule = NULL;
185 
186   inend = in + (*inlen);
187 
188   while ((in < inend) && (out < outend))
189   {
190     replace_rule = NULL;
191     if ((unsigned char)(*in) < 32) {
192       if (control_escape_rules[(unsigned int)*in].character) {
193         replace_rule = &control_escape_rules[(unsigned int)*in];
194       }
195     } else
196     {
197       const escape_rule_t *rule = NULL;
198       for (rule= other_escape_rules; rule->character; rule++)
199       {
200         if (*in == rule->character)
201         {
202           replace_rule = rule;
203           break;
204         }
205       }
206     }
207     if (replace_rule)
208     {
209           if ((outend - out) < (ptrdiff_t) replace_rule->length)
210             break;
211           memcpy(out, replace_rule->replacement, replace_rule->length);
212           out += replace_rule->length;
213     } else
214     {
215       *out++ = *in;
216     }
217     ++in;
218   }
219   *outlen = out - outstart;
220   *inlen = in - base;
221 }
222 
223 static
xml_escape(const char * in,size_t * inlen,char * out,size_t * outlen)224 void xml_escape(const char *in, size_t *inlen, char *out, size_t *outlen)
225 {
226   // Most control sequences aren't supported before XML 1.1, and most
227   // tools only support 1.0. Our output is 1.0. Escaping them wouldn't make
228   // the output more valid.
229   static const escape_rule_t control_rules[]=
230   {
231     { 0,  0, NULL },
232     { 0,  0, NULL },
233     { 0,  0, NULL },
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     { '\t', 5, "&#9;" },
241     { '\n', 6, "&#10;" },
242     { 0,  0, NULL },
243     { 0,  0, NULL },
244     { '\r', 6, "&#13;" },
245     { 0,  0, NULL },
246     { 0,  0, NULL },
247     { 0,  0, NULL },
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   };
264   static const escape_rule_t other_rules[]=
265   {
266     { '<',  4, "&lt;" },
267     { '>',  4, "&gt;" },
268     { '&',  5, "&amp;" },
269     { '"',  6, "&quot;" },
270     { 0,  0, NULL }
271   };
272 
273   escape_buf(in, inlen, out, outlen, control_rules, other_rules);
274 }
275 
276 static
json_escape(const char * in,size_t * inlen,char * out,size_t * outlen)277 void json_escape(const char *in, size_t *inlen, char *out, size_t *outlen)
278 {
279   static const escape_rule_t control_rules[]=
280   {
281     { 0,  6, "\\u0000" },
282     { 1,  6, "\\u0001" },
283     { 2,  6, "\\u0002" },
284     { 3,  6, "\\u0003" },
285     { 4,  6, "\\u0004" },
286     { 5,  6, "\\u0005" },
287     { 6,  6, "\\u0006" },
288     { 7,  6, "\\u0007" },
289     { '\b',  2, "\\b" },
290     { '\t',  2, "\\t" },
291     { '\n',  2, "\\n" },
292     { 11, 6, "\\u000B" },
293     { '\f',  2, "\\f" },
294     { '\r',  2, "\\r" },
295     { 14, 6, "\\u000E" },
296     { 15, 6, "\\u000F" },
297     { 16, 6, "\\u0010" },
298     { 17, 6, "\\u0011" },
299     { 18, 6, "\\u0012" },
300     { 19, 6, "\\u0013" },
301     { 20, 6, "\\u0014" },
302     { 21, 6, "\\u0015" },
303     { 22, 6, "\\u0016" },
304     { 23, 6, "\\u0017" },
305     { 24, 6, "\\u0018" },
306     { 25, 6, "\\u0019" },
307     { 26, 6, "\\u001A" },
308     { 27, 6, "\\u001B" },
309     { 28, 6, "\\u001C" },
310     { 29, 6, "\\u001D" },
311     { 30, 6, "\\u001E" },
312     { 31, 6, "\\u001F" },
313   };
314 
315   static const escape_rule_t other_rules[]=
316   {
317     { '\\', 2, "\\\\" },
318     { '"',  2, "\\\"" },
319     { '/',  2, "\\/" },
320     { 0,  0, NULL }
321   };
322 
323   escape_buf(in, inlen, out, outlen, control_rules, other_rules);
324 }
325 
326 static
csv_escape(const char * in,size_t * inlen,char * out,size_t * outlen)327 void csv_escape(const char *in, size_t *inlen, char *out, size_t *outlen)
328 {
329   // We do not have any standard control escape rules for CSVs
330   static const escape_rule_t control_rules[]=
331   {
332     { 0,  0, NULL },
333     { 0,  0, NULL },
334     { 0,  0, NULL },
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   };
365 
366   static const escape_rule_t other_rules[]=
367   {
368     { '"',  2, "\"\"" },
369     { 0,  0, NULL }
370   };
371 
372   escape_buf(in, inlen, out, outlen, control_rules, other_rules);
373 }
374 
375 static const escape_buf_func_t format_escape_func[]=
376   { xml_escape, xml_escape, json_escape, csv_escape };
377 
378 /*
379   Calculate the size of the otput bufer needed to escape the string.
380 
381   @param[in]  in           Input string
382   @param[in]  len          Length of the input string
383 
384   @return
385     size of the otput bufer including trailing zero
386 */
387 static
calculate_escape_string_buf_len(const char * in,size_t len)388 size_t calculate_escape_string_buf_len(const char *in, size_t len)
389 {
390   char tmp[128];
391   size_t full_outlen= 0;
392 
393   while (len > 0)
394   {
395     size_t tmp_size= sizeof(tmp);
396     size_t inlen= len;
397     format_escape_func[audit_log_format](in, &inlen, tmp, &tmp_size);
398     in+= inlen;
399     len-= inlen;
400     full_outlen+= tmp_size;
401   }
402   return full_outlen + 1;
403 }
404 
405 /*
406   Escape string according to audit_log_format.
407 
408   @param[in]  in           Input string
409   @param[in]  inlen        Length of the input string
410   @param[in]  out          Output buffer
411   @param[in]  outlen       Length of the output buffer
412   @param[out] endptr       A pointer to the character after the
413                            last escaped character in the output
414                            buffer
415   @param[out] full_outlen  Length of the output buffer that would
416                            be needed to store complete non-truncated
417                            escaped input buffer
418 
419   @return
420     pointer to the beginning of the output buffer
421 */
422 static
escape_string(const char * in,size_t inlen,char * out,size_t outlen,char ** endptr,size_t * full_outlen)423 char *escape_string(const char *in, size_t inlen,
424                     char *out, size_t outlen,
425                     char **endptr, size_t *full_outlen)
426 {
427   if (outlen == 0)
428   {
429     if (endptr)
430       *endptr= out;
431     if (full_outlen)
432       *full_outlen+= calculate_escape_string_buf_len(in, inlen);
433   }
434   else if (in != NULL)
435   {
436     size_t inlen_res= inlen;
437     --outlen;
438     format_escape_func[audit_log_format](in, &inlen_res, out, &outlen);
439     out[outlen]= 0;
440     if (endptr)
441       *endptr= out + outlen + 1;
442     if (full_outlen)
443     {
444       *full_outlen+= outlen;
445       *full_outlen+= calculate_escape_string_buf_len(in + inlen_res,
446                                                      inlen - inlen_res);
447     }
448   }
449   else
450   {
451     *out= 0;
452     if (endptr)
453       *endptr= out + 1;
454     if (full_outlen)
455       ++(*full_outlen);
456   }
457   return out;
458 }
459 
460 
461 static
audit_log_write(const char * buf,size_t len)462 void audit_log_write(const char *buf, size_t len)
463 {
464   static int write_error= 0;
465 
466   if (audit_handler_write(log_handler, buf, len) < 0)
467   {
468     if (!write_error)
469     {
470       write_error= 1;
471       fprintf_timestamp(stderr);
472       fprintf(stderr, "Error writing to file %s. ", audit_log_file);
473       perror("Error: ");
474     }
475   }
476   else
477   {
478     write_error= 0;
479   }
480 }
481 
482 
483 /* Defined in MySQL server */
484 extern int orig_argc;
485 extern char **orig_argv;
486 extern char server_version[SERVER_VERSION_LENGTH];
487 
488 static
make_argv(char * buf,size_t len,int argc,char ** argv)489 char *make_argv(char *buf, size_t len, int argc, char **argv)
490 {
491   size_t left= len;
492 
493   buf[0]= 0;
494   while (argc > 0 && left > 0)
495   {
496     left-= my_snprintf(buf + len - left, left,
497                        "%s%c", *argv, argc > 1 ? ' ' : 0);
498     argc--; argv++;
499   }
500 
501   return buf;
502 }
503 
504 static
audit_log_audit_record(char * buf,size_t buflen,const char * name,time_t t,size_t * outlen)505 char *audit_log_audit_record(char *buf, size_t buflen,
506                              const char *name, time_t t,
507                              size_t *outlen)
508 {
509   char id_str[MAX_RECORD_ID_SIZE];
510   char timestamp[MAX_TIMESTAMP_SIZE];
511   char arg_buf[512];
512   const char *format_string[] = {
513                      "<AUDIT_RECORD\n"
514                      "  NAME=\"%s\"\n"
515                      "  RECORD=\"%s\"\n"
516                      "  TIMESTAMP=\"%s\"\n"
517                      "  MYSQL_VERSION=\"%s\"\n"
518                      "  STARTUP_OPTIONS=\"%s\"\n"
519                      "  OS_VERSION=\""MACHINE_TYPE"-"SYSTEM_TYPE"\"\n"
520                      "/>\n",
521 
522                      "<AUDIT_RECORD>\n"
523                      "  <NAME>%s</NAME>\n"
524                      "  <RECORD>%s</RECORD>\n"
525                      "  <TIMESTAMP>%s</TIMESTAMP>\n"
526                      "  <MYSQL_VERSION>%s</MYSQL_VERSION>\n"
527                      "  <STARTUP_OPTIONS>%s</STARTUP_OPTIONS>\n"
528                      "  <OS_VERSION>"MACHINE_TYPE"-"SYSTEM_TYPE"</OS_VERSION>\n"
529                      "</AUDIT_RECORD>\n",
530 
531                      "{\"audit_record\":{\"name\":\"%s\",\"record\":\"%s\","
532                      "\"timestamp\":\"%s\",\"mysql_version\":\"%s\","
533                      "\"startup_optionsi\":\"%s\","
534                      "\"os_version\":\""MACHINE_TYPE"-"SYSTEM_TYPE"\"}}\n",
535 
536                      "\"%s\",\"%s\",\"%s\",\"%s\",\"%s\","
537                      "\""MACHINE_TYPE"-"SYSTEM_TYPE"\"\n" };
538 
539   *outlen= snprintf(buf, buflen,
540                     format_string[audit_log_format],
541                     name,
542                     make_record_id(id_str, sizeof(id_str)),
543                     make_timestamp(timestamp, sizeof(timestamp), t),
544                     server_version,
545                     make_argv(arg_buf, sizeof(arg_buf),
546                               orig_argc - 1, orig_argv + 1));
547 
548   /* make sure that record is not truncated */
549   DBUG_ASSERT(buf + *outlen <= buf + buflen);
550 
551   return buf;
552 }
553 
554 
555 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)556 char *audit_log_general_record(char *buf, size_t buflen,
557                                const char *name, time_t t, int status,
558                                const struct mysql_event_general *event,
559                                const char *default_db,
560                                size_t *outlen)
561 {
562   char id_str[MAX_RECORD_ID_SIZE];
563   char timestamp[MAX_TIMESTAMP_SIZE];
564   char *query, *user, *host, *external_user, *ip, *db;
565   char *endptr= buf, *endbuf= buf + buflen;
566   size_t full_outlen= 0, buflen_estimated;
567   size_t query_length;
568 
569   const char *format_string[] = {
570                      "<AUDIT_RECORD\n"
571                      "  NAME=\"%s\"\n"
572                      "  RECORD=\"%s\"\n"
573                      "  TIMESTAMP=\"%s\"\n"
574                      "  COMMAND_CLASS=\"%s\"\n"
575                      "  CONNECTION_ID=\"%lu\"\n"
576                      "  STATUS=\"%d\"\n"
577                      "  SQLTEXT=\"%s\"\n"
578                      "  USER=\"%s\"\n"
579                      "  HOST=\"%s\"\n"
580                      "  OS_USER=\"%s\"\n"
581                      "  IP=\"%s\"\n"
582                      "  DB=\"%s\"\n"
583                      "/>\n",
584 
585                      "<AUDIT_RECORD>\n"
586                      "  <NAME>%s</NAME>\n"
587                      "  <RECORD>%s</RECORD>\n"
588                      "  <TIMESTAMP>%s</TIMESTAMP>\n"
589                      "  <COMMAND_CLASS>%s</COMMAND_CLASS>\n"
590                      "  <CONNECTION_ID>%lu</CONNECTION_ID>\n"
591                      "  <STATUS>%d</STATUS>\n"
592                      "  <SQLTEXT>%s</SQLTEXT>\n"
593                      "  <USER>%s</USER>\n"
594                      "  <HOST>%s</HOST>\n"
595                      "  <OS_USER>%s</OS_USER>\n"
596                      "  <IP>%s</IP>\n"
597                      "  <DB>%s</DB>\n"
598                      "</AUDIT_RECORD>\n",
599 
600                      "{\"audit_record\":"
601                        "{\"name\":\"%s\","
602                        "\"record\":\"%s\","
603                        "\"timestamp\":\"%s\","
604                        "\"command_class\":\"%s\","
605                        "\"connection_id\":\"%lu\","
606                        "\"status\":%d,"
607                        "\"sqltext\":\"%s\","
608                        "\"user\":\"%s\","
609                        "\"host\":\"%s\","
610                        "\"os_user\":\"%s\","
611                        "\"ip\":\"%s\","
612                        "\"db\":\"%s\"}}\n",
613 
614                      "\"%s\",\"%s\",\"%s\",\"%s\",\"%lu\",%d,\"%s\",\"%s\","
615                      "\"%s\",\"%s\",\"%s\",\"%s\"\n" };
616 
617   query_length= my_charset_utf8mb4_general_ci.mbmaxlen *
618                 event->general_query_length;
619 
620   if (query_length < (size_t) (endbuf - endptr))
621   {
622     uint errors;
623     query_length= my_convert(endptr, query_length,
624                              &my_charset_utf8mb4_general_ci,
625                              event->general_query,
626                              event->general_query_length,
627                              event->general_charset, &errors);
628     query= endptr;
629     endptr+= query_length;
630 
631     full_outlen+= query_length;
632 
633     query= escape_string(query, query_length, endptr, endbuf - endptr,
634                          &endptr, &full_outlen);
635   }
636   else
637   {
638     endptr= endbuf;
639     query= escape_string(event->general_query, event->general_query_length,
640                          endptr, endbuf - endptr, &endptr, &full_outlen);
641     full_outlen*= my_charset_utf8mb4_general_ci.mbmaxlen;
642     full_outlen+= query_length * my_charset_utf8mb4_general_ci.mbmaxlen;
643   }
644 
645   user= escape_string(event->general_user, event->general_user_length,
646                       endptr, endbuf - endptr, &endptr, &full_outlen);
647   host= escape_string(event->general_host.str, event->general_host.length,
648                       endptr, endbuf - endptr, &endptr, &full_outlen);
649   external_user= escape_string(event->general_external_user.str,
650                                event->general_external_user.length,
651                                endptr, endbuf - endptr, &endptr, &full_outlen);
652   ip= escape_string(event->general_ip.str, event->general_ip.length,
653                     endptr, endbuf - endptr, &endptr, &full_outlen);
654   db= escape_string(default_db, strlen(default_db),
655                     endptr, endbuf - endptr, &endptr, &full_outlen);
656 
657   buflen_estimated= full_outlen * 2 +
658                     strlen(format_string[audit_log_format]) +
659                     strlen(name) +
660                     event->general_sql_command.length +
661                     20 + /* general_thread_id */
662                     20 + /* status */
663                     MAX_RECORD_ID_SIZE + MAX_TIMESTAMP_SIZE;
664   if (buflen_estimated > buflen)
665   {
666     *outlen= buflen_estimated;
667     return NULL;
668   }
669 
670   *outlen= snprintf(endptr, endbuf - endptr,
671                     format_string[audit_log_format],
672                     name,
673                     make_record_id(id_str, sizeof(id_str)),
674                     make_timestamp(timestamp, sizeof(timestamp), t),
675                     event->general_sql_command.str,
676                     event->general_thread_id,
677                     status, query, user, host, external_user, ip, db);
678 
679   /* make sure that record is not truncated */
680   DBUG_ASSERT(endptr + *outlen <= buf + buflen);
681 
682   return endptr;
683 }
684 
685 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)686 char *audit_log_connection_record(char *buf, size_t buflen,
687                                   const char *name, time_t t,
688                                   const struct mysql_event_connection *event,
689                                   size_t *outlen)
690 {
691   char id_str[MAX_RECORD_ID_SIZE];
692   char timestamp[MAX_TIMESTAMP_SIZE];
693   char *user, *priv_user, *external_user, *proxy_user, *host, *ip, *database;
694   char *endptr= buf, *endbuf= buf + buflen;
695 
696   const char *format_string[] = {
697                      "<AUDIT_RECORD\n"
698                      "  NAME=\"%s\"\n"
699                      "  RECORD=\"%s\"\n"
700                      "  TIMESTAMP=\"%s\"\n"
701                      "  CONNECTION_ID=\"%lu\"\n"
702                      "  STATUS=\"%d\"\n"
703                      "  USER=\"%s\"\n"
704                      "  PRIV_USER=\"%s\"\n"
705                      "  OS_LOGIN=\"%s\"\n"
706                      "  PROXY_USER=\"%s\"\n"
707                      "  HOST=\"%s\"\n"
708                      "  IP=\"%s\"\n"
709                      "  DB=\"%s\"\n"
710                      "/>\n",
711 
712                      "<AUDIT_RECORD>\n"
713                      "  <NAME>%s</NAME>\n"
714                      "  <RECORD>%s</RECORD>\n"
715                      "  <TIMESTAMP>%s</TIMESTAMP>\n"
716                      "  <CONNECTION_ID>%lu</CONNECTION_ID>\n"
717                      "  <STATUS>%d</STATUS>\n"
718                      "  <USER>%s</USER>\n"
719                      "  <PRIV_USER>%s</PRIV_USER>\n"
720                      "  <OS_LOGIN>%s</OS_LOGIN>\n"
721                      "  <PROXY_USER>%s</PROXY_USER>\n"
722                      "  <HOST>%s</HOST>\n"
723                      "  <IP>%s</IP>\n"
724                      "  <DB>%s</DB>\n"
725                      "</AUDIT_RECORD>\n",
726 
727                      "{\"audit_record\":"
728                        "{\"name\":\"%s\","
729                        "\"record\":\"%s\","
730                        "\"timestamp\":\"%s\","
731                        "\"connection_id\":\"%lu\","
732                        "\"status\":%d,"
733                        "\"user\":\"%s\","
734                        "\"priv_user\":\"%s\","
735                        "\"os_login\":\"%s\","
736                        "\"proxy_user\":\"%s\","
737                        "\"host\":\"%s\","
738                        "\"ip\":\"%s\","
739                        "\"db\":\"%s\"}}\n",
740 
741                      "\"%s\",\"%s\",\"%s\",\"%lu\",%d,\"%s\",\"%s\",\"%s\","
742                      "\"%s\",\"%s\",\"%s\",\"%s\"\n" };
743 
744   user= escape_string(event->user, event->user_length,
745                       endptr, endbuf - endptr, &endptr, NULL);
746   priv_user= escape_string(event->priv_user,
747                            event->priv_user_length,
748                            endptr, endbuf - endptr, &endptr, NULL);
749   external_user= escape_string(event->external_user,
750                                event->external_user_length,
751                                endptr, endbuf - endptr, &endptr, NULL);
752   proxy_user= escape_string(event->proxy_user, event->proxy_user_length,
753                             endptr, endbuf - endptr, &endptr, NULL);
754   host= escape_string(event->host, event->host_length,
755                       endptr, endbuf - endptr, &endptr, NULL);
756   ip= escape_string(event->ip, event->ip_length,
757                     endptr, endbuf - endptr, &endptr, NULL);
758   database= escape_string(event->database, event->database_length,
759                           endptr, endbuf - endptr, &endptr, NULL);
760 
761   DBUG_ASSERT((endptr - buf) * 2 +
762               strlen(format_string[audit_log_format]) +
763               strlen(name) +
764               MAX_RECORD_ID_SIZE +
765               MAX_TIMESTAMP_SIZE +
766               20 + /* event->thread_id */
767               20 /* event->status */
768               < buflen);
769 
770   *outlen= snprintf(endptr, endbuf - endptr,
771                     format_string[audit_log_format],
772                     name,
773                     make_record_id(id_str, sizeof(id_str)),
774                     make_timestamp(timestamp, sizeof(timestamp), t),
775                     event->thread_id,
776                     event->status, user, priv_user,external_user,
777                     proxy_user, host, ip, database);
778 
779   /* make sure that record is not truncated */
780   DBUG_ASSERT(endptr + *outlen <= buf + buflen);
781 
782   return endptr;
783 }
784 
785 static
audit_log_header(MY_STAT * stat,char * buf,size_t buflen)786 size_t audit_log_header(MY_STAT *stat, char *buf, size_t buflen)
787 {
788   const char *format_string[] = {
789                      "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
790                      "<AUDIT>\n",
791                      "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
792                      "<AUDIT>\n",
793                      "",
794                      "" };
795 
796   DBUG_ASSERT(strcmp(system_charset_info->csname, "utf8") == 0);
797 
798   log_file_time= stat->st_mtime;
799 
800   init_record_id(stat->st_size);
801 
802   if (buf == NULL)
803   {
804     return 0;
805   }
806 
807   return my_snprintf(buf, buflen, format_string[audit_log_format]);
808 }
809 
810 
811 static
audit_log_footer(char * buf,size_t buflen)812 size_t audit_log_footer(char *buf, size_t buflen)
813 {
814   const char *format_string[] = {
815                      "</AUDIT>\n",
816                      "</AUDIT>\n",
817                      "",
818                      "" };
819 
820   if (buf == NULL)
821   {
822     return 0;
823   }
824 
825   return my_snprintf(buf, buflen, format_string[audit_log_format]);
826 }
827 
828 static
init_new_log_file()829 int init_new_log_file()
830 {
831   if (audit_log_handler == HANDLER_FILE)
832   {
833     audit_handler_file_config_t opts;
834     opts.name= audit_log_file;
835     opts.rotate_on_size= audit_log_rotate_on_size;
836     opts.rotations= audit_log_rotations;
837     opts.sync_on_write= audit_log_strategy == SYNCHRONOUS;
838     opts.use_buffer= audit_log_strategy < SEMISYNCHRONOUS;
839     opts.buffer_size= audit_log_buffer_size;
840     opts.can_drop_data= audit_log_strategy == PERFORMANCE;
841     opts.header= audit_log_header;
842     opts.footer= audit_log_footer;
843 
844     log_handler= audit_handler_file_open(&opts);
845     if (log_handler == NULL)
846     {
847       fprintf_timestamp(stderr);
848       fprintf(stderr, "Cannot open file %s. ", audit_log_file);
849       perror("Error: ");
850       return(1);
851     }
852   }
853   else
854   {
855     audit_handler_syslog_config_t opts;
856     opts.facility= audit_log_syslog_facility_codes[audit_log_syslog_facility];
857     opts.ident= audit_log_syslog_ident;
858     opts.priority= audit_log_syslog_priority_codes[audit_log_syslog_priority];
859     opts.header= audit_log_header;
860     opts.footer= audit_log_footer;
861 
862     log_handler= audit_handler_syslog_open(&opts);
863     if (log_handler == NULL)
864     {
865       fprintf_timestamp(stderr);
866       fprintf(stderr, "Cannot open syslog. ");
867       perror("Error: ");
868       return(1);
869     }
870   }
871 
872   return(0);
873 }
874 
875 
876 static
reopen_log_file()877 int reopen_log_file()
878 {
879   if (audit_handler_flush(log_handler))
880   {
881     fprintf_timestamp(stderr);
882     fprintf(stderr, "Cannot open file %s. ", audit_log_file);
883     perror("Error: ");
884     return(1);
885   }
886 
887   return(0);
888 }
889 
890 /*
891  Struct to store various THD specific data
892  */
893 typedef struct
894 {
895   /* size of allocated large buffer for record formatting */
896   size_t record_buffer_size;
897   /* large buffer for record formatting */
898   char *record_buffer;
899   /* skip logging session */
900   my_bool skip_session;
901   /* skip logging for the next query */
902   my_bool skip_query;
903   /* default database */
904   char db[NAME_LEN + 1];
905   /* default database candidate */
906   char init_db_query[NAME_LEN + 1];
907 } audit_log_thd_local;
908 
909 /*
910  Return pointer to THD specific data.
911  */
912 static
913 audit_log_thd_local *get_thd_local(MYSQL_THD thd);
914 
915 /*
916  Allocate and return buffer of given size.
917  */
918 static
919 char *get_record_buffer(MYSQL_THD thd, size_t size);
920 
921 
922 static
audit_log_plugin_init(void * arg MY_ATTRIBUTE ((unused)))923 int audit_log_plugin_init(void *arg MY_ATTRIBUTE((unused)))
924 {
925   char buf[1024];
926   size_t len;
927 
928   logger_init_mutexes();
929 
930   audit_log_filter_init();
931 
932   if (audit_log_exclude_accounts != NULL && audit_log_include_accounts != NULL)
933   {
934     fprintf(stderr, "Both 'audit_log_exclude_accounts' and "
935             "'audit_log_include_accounts' are not NULL\n");
936     goto validation_error;
937   }
938 
939   if (audit_log_exclude_commands != NULL && audit_log_include_commands != NULL)
940   {
941     fprintf(stderr, "Both 'audit_log_exclude_commands' and "
942             "'audit_log_include_commands' are not NULL\n");
943     goto validation_error;
944   }
945 
946   if (audit_log_exclude_accounts != NULL)
947   {
948     audit_log_exclude_accounts= my_strdup(audit_log_exclude_accounts,
949                                           MYF(MY_FAE));
950     audit_log_set_exclude_accounts(audit_log_exclude_accounts);
951   }
952   if (audit_log_include_accounts != NULL)
953   {
954     audit_log_include_accounts= my_strdup(audit_log_include_accounts,
955                                           MYF(MY_FAE));
956     audit_log_set_include_accounts(audit_log_include_accounts);
957   }
958   if (audit_log_exclude_commands != NULL)
959   {
960     audit_log_exclude_commands= my_strdup(audit_log_exclude_commands,
961                                           MYF(MY_FAE));
962     audit_log_set_exclude_commands(audit_log_exclude_commands);
963   }
964   if (audit_log_include_commands != NULL)
965   {
966     audit_log_include_commands= my_strdup(audit_log_include_commands,
967                                           MYF(MY_FAE));
968     audit_log_set_include_commands(audit_log_include_commands);
969   }
970 
971   if (init_new_log_file())
972     return(1);
973 
974   if (audit_log_audit_record(buf, sizeof(buf), "Audit", time(NULL), &len))
975     audit_log_write(buf, len);
976 
977   return 0;
978 
979 validation_error:
980 
981   audit_log_exclude_accounts= audit_log_include_accounts= NULL;
982   audit_log_exclude_commands= audit_log_include_commands= NULL;
983 
984   return 1;
985 }
986 
987 
988 static
audit_log_plugin_deinit(void * arg MY_ATTRIBUTE ((unused)))989 int audit_log_plugin_deinit(void *arg MY_ATTRIBUTE((unused)))
990 {
991   char buf[1024];
992   size_t len;
993 
994   if (audit_log_audit_record(buf, sizeof(buf), "NoAudit", time(NULL), &len))
995     audit_log_write(buf, len);
996 
997   audit_handler_close(log_handler);
998 
999   audit_log_filter_destroy();
1000 
1001   my_free(audit_log_include_accounts);
1002   my_free(audit_log_exclude_accounts);
1003 
1004   my_free(audit_log_include_commands);
1005   my_free(audit_log_exclude_commands);
1006 
1007   return(0);
1008 }
1009 
1010 
1011 static
is_event_class_allowed_by_policy(unsigned int class,enum audit_log_policy_t policy)1012 int is_event_class_allowed_by_policy(unsigned int class,
1013                                      enum audit_log_policy_t policy)
1014 {
1015   static unsigned int class_mask[]=
1016   {
1017     MYSQL_AUDIT_GENERAL_CLASSMASK | MYSQL_AUDIT_CONNECTION_CLASSMASK, /* ALL */
1018     0,                                                             /* NONE */
1019     MYSQL_AUDIT_CONNECTION_CLASSMASK,                              /* LOGINS */
1020     MYSQL_AUDIT_GENERAL_CLASSMASK,                                 /* QUERIES */
1021   };
1022 
1023   return (class_mask[policy] & (1 << class)) != 0;
1024 }
1025 
1026 static
next_word(const char * str,size_t * len,const struct charset_info_st * charset)1027 const char *next_word(const char *str, size_t *len,
1028                       const struct charset_info_st *charset)
1029 {
1030   while (*str && my_isspace(charset, *str))
1031   {
1032     if (*str == '/' && str[1] == '*' && str[2] == '!')
1033       str+= 3;
1034     else if (*str == '/' && str[1] == '*')
1035     {
1036       while (*str && !(*str == '*' && str[1] == '/'))
1037         str++;
1038     }
1039     else
1040       str++;
1041   }
1042 
1043   *len= 0;
1044   while (str[*len] && my_isvar(charset, str[*len]))
1045     (*len)++;
1046 
1047   if (*len == 0 && *str == '`')
1048   {
1049     (*len)++;
1050     while (str[*len])
1051     {
1052       if (str[*len] == '`' && str[*len + 1] == '`')
1053         (*len)++;
1054       else if (str[*len] == '`')
1055         break;
1056       (*len)++;
1057     }
1058     (*len)++;
1059   }
1060 
1061   return str;
1062 }
1063 
1064 
1065 static
audit_log_update_thd_local(MYSQL_THD thd,audit_log_thd_local * local,unsigned int event_class,const void * event)1066 void audit_log_update_thd_local(MYSQL_THD thd,
1067                                 audit_log_thd_local *local,
1068                                 unsigned int event_class,
1069                                 const void *event)
1070 {
1071   DBUG_ASSERT(audit_log_include_accounts == NULL ||
1072               audit_log_exclude_accounts == NULL);
1073   DBUG_ASSERT(audit_log_include_commands == NULL ||
1074               audit_log_exclude_commands == NULL);
1075 
1076   if (event_class == MYSQL_AUDIT_CONNECTION_CLASS)
1077   {
1078     const struct mysql_event_connection *event_connection=
1079       (const struct mysql_event_connection *) event;
1080 
1081     const char *host = get_priv_host(thd);
1082     const size_t host_length = strlen(host);
1083 
1084     local->skip_session= FALSE;
1085     if (audit_log_include_accounts != NULL &&
1086         !audit_log_check_account_included(event_connection->priv_user,
1087                                           event_connection->priv_user_length,
1088                                           host,
1089                                           host_length))
1090       local->skip_session= TRUE;
1091     if (audit_log_exclude_accounts != NULL &&
1092         audit_log_check_account_excluded(event_connection->priv_user,
1093                                          event_connection->priv_user_length,
1094                                          host,
1095                                          host_length))
1096       local->skip_session= TRUE;
1097 
1098     if (event_connection->status == 0)
1099     {
1100       /* track default DB change */
1101       DBUG_ASSERT(event_connection->database_length <= sizeof(local->db));
1102       memcpy(local->db, event_connection->database,
1103              event_connection->database_length);
1104       local->db[event_connection->database_length]= 0;
1105     }
1106   }
1107   else if (event_class == MYSQL_AUDIT_GENERAL_CLASS)
1108   {
1109     const struct mysql_event_general *event_general=
1110       (const struct mysql_event_general *) event;
1111 
1112     if (event_general->event_subclass == MYSQL_AUDIT_GENERAL_STATUS)
1113     {
1114       local->skip_query= audit_log_include_commands
1115             && !audit_log_check_command_included(
1116                      event_general->general_sql_command.str,
1117                      event_general->general_sql_command.length);
1118 
1119       local->skip_query|= audit_log_exclude_commands
1120             && audit_log_check_command_excluded(
1121                      event_general->general_sql_command.str,
1122                      event_general->general_sql_command.length);
1123 
1124       if (!local->skip_query &&
1125           ((event_general->general_command_length == 4 &&
1126             strncmp(event_general->general_command, "Quit", 4) == 0) ||
1127            (event_general->general_command_length == 11 &&
1128             strncmp(event_general->general_command,
1129                     "Change user", 11) == 0)))
1130         local->skip_query= TRUE;
1131     }
1132 
1133     if (event_general->event_subclass == MYSQL_AUDIT_GENERAL_LOG &&
1134         event_general->general_command_length == 7 &&
1135         strncmp(event_general->general_command, "Init DB", 7) == 0 &&
1136         event_general->general_query != NULL &&
1137         strpbrk("\n\r\t ", event_general->general_query) == NULL)
1138     {
1139       /* Database is about to be changed. Server doesn't provide database
1140       name in STATUS event, so remember it now. */
1141 
1142       DBUG_ASSERT(event_general->general_query_length <= sizeof(local->db));
1143       memcpy(local->db, event_general->general_query,
1144              event_general->general_query_length);
1145       local->db[event_general->general_query_length]= 0;
1146     }
1147     if (event_general->event_subclass == MYSQL_AUDIT_GENERAL_STATUS &&
1148         event_general->general_sql_command.length == 9 &&
1149         strncmp(event_general->general_sql_command.str, "change_db", 9) == 0 &&
1150         event_general->general_command_length == 5 &&
1151         strncmp(event_general->general_command, "Query", 5) == 0 &&
1152         event_general->general_error_code == 0)
1153     {
1154       /* it's "use dbname" query */
1155 
1156       size_t len;
1157       const char *word;
1158 
1159       word= next_word(event_general->general_query, &len,
1160                       event_general->general_charset);
1161       if (strncasecmp("use", word, len) == 0)
1162       {
1163         uint errors;
1164 
1165         word= next_word(word + len, &len, event_general->general_charset);
1166         if (*word == '`')
1167         {
1168           word++;
1169           len-= 2;
1170         }
1171         len= my_convert(local->db, sizeof(local->db) - 1, system_charset_info,
1172                         word, len, event_general->general_charset, &errors);
1173         local->db[len]= 0;
1174       }
1175     }
1176   }
1177 }
1178 
1179 
1180 static
audit_log_notify(MYSQL_THD thd MY_ATTRIBUTE ((unused)),unsigned int event_class,const void * event)1181 void audit_log_notify(MYSQL_THD thd MY_ATTRIBUTE((unused)),
1182                       unsigned int event_class,
1183                       const void *event)
1184 {
1185   char buf[4096];
1186   char *log_rec = NULL;
1187   char *allocated_buf= get_record_buffer(thd, 0);
1188   size_t len, buflen;
1189   audit_log_thd_local *local= get_thd_local(thd);
1190 
1191   audit_log_update_thd_local(thd, local, event_class, event);
1192 
1193   if (!is_event_class_allowed_by_policy(event_class, audit_log_policy))
1194     return;
1195 
1196   if (local->skip_session)
1197     return;
1198 
1199   if (event_class == MYSQL_AUDIT_GENERAL_CLASS)
1200   {
1201     const struct mysql_event_general *event_general=
1202       (const struct mysql_event_general *) event;
1203     switch (event_general->event_subclass)
1204     {
1205     case MYSQL_AUDIT_GENERAL_STATUS:
1206       if (local->skip_query)
1207         break;
1208 
1209       /* use allocated buffer if available */
1210       if (allocated_buf != NULL)
1211       {
1212         log_rec= allocated_buf;
1213         buflen= local->record_buffer_size;
1214       }
1215       else
1216       {
1217         log_rec= buf;
1218         buflen= sizeof(buf);
1219       }
1220       log_rec= audit_log_general_record(log_rec, buflen,
1221                                         event_general->general_command,
1222                                         event_general->general_time,
1223                                         event_general->general_error_code,
1224                                         event_general, local->db,
1225                                         &len);
1226       if (len > buflen)
1227       {
1228         buflen= len + 1024;
1229         log_rec= audit_log_general_record(get_record_buffer(thd, buflen),
1230                                           buflen,
1231                                           event_general->general_command,
1232                                           event_general->general_time,
1233                                           event_general->general_error_code,
1234                                           event_general, local->db,
1235                                           &len);
1236         DBUG_ASSERT(log_rec);
1237       }
1238       if (log_rec)
1239         audit_log_write(log_rec, len);
1240       break;
1241     }
1242   }
1243   else if (event_class == MYSQL_AUDIT_CONNECTION_CLASS)
1244   {
1245     const struct mysql_event_connection *event_connection=
1246       (const struct mysql_event_connection *) event;
1247     switch (event_connection->event_subclass)
1248     {
1249     case MYSQL_AUDIT_CONNECTION_CONNECT:
1250       log_rec= audit_log_connection_record(buf, sizeof(buf), "Connect",
1251                                            time(NULL), event_connection, &len);
1252       break;
1253     case MYSQL_AUDIT_CONNECTION_DISCONNECT:
1254       log_rec= audit_log_connection_record(buf, sizeof(buf), "Quit",
1255                                            time(NULL), event_connection, &len);
1256       break;
1257    case MYSQL_AUDIT_CONNECTION_CHANGE_USER:
1258       log_rec= audit_log_connection_record(buf, sizeof(buf), "Change user",
1259                                            time(NULL), event_connection, &len);
1260       break;
1261     default:
1262       break;
1263     }
1264     if (log_rec)
1265       audit_log_write(log_rec, len);
1266   }
1267 }
1268 
1269 
1270 /*
1271  * Plugin system vars
1272  */
1273 
1274 static MYSQL_SYSVAR_STR(file, audit_log_file,
1275   PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY | PLUGIN_VAR_MEMALLOC,
1276   "The name of the log file.", NULL, NULL, default_audit_log_file);
1277 
1278 static const char *audit_log_policy_names[]=
1279                     { "ALL", "NONE", "LOGINS", "QUERIES", 0 };
1280 
1281 static TYPELIB audit_log_policy_typelib=
1282 {
1283   array_elements(audit_log_policy_names) - 1, "audit_log_policy_typelib",
1284   audit_log_policy_names, NULL
1285 };
1286 
1287 static MYSQL_SYSVAR_ENUM(policy, audit_log_policy, PLUGIN_VAR_RQCMDARG,
1288        "The policy controlling the information written by the audit log "
1289        "plugin to its log file.", NULL, NULL, ALL,
1290        &audit_log_policy_typelib);
1291 
1292 static const char *audit_log_strategy_names[]=
1293   { "ASYNCHRONOUS", "PERFORMANCE", "SEMISYNCHRONOUS", "SYNCHRONOUS", 0 };
1294 static TYPELIB audit_log_strategy_typelib=
1295 {
1296   array_elements(audit_log_strategy_names) - 1, "audit_log_strategy_typelib",
1297   audit_log_strategy_names, NULL
1298 };
1299 
1300 static MYSQL_SYSVAR_ENUM(strategy, audit_log_strategy,
1301        PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
1302        "The logging method used by the audit log plugin, "
1303        "if FILE handler is used.", NULL, NULL,
1304        ASYNCHRONOUS, &audit_log_strategy_typelib);
1305 
1306 static const char *audit_log_format_names[]=
1307   { "OLD", "NEW", "JSON", "CSV", 0 };
1308 static TYPELIB audit_log_format_typelib=
1309 {
1310   array_elements(audit_log_format_names) - 1, "audit_log_format_typelib",
1311   audit_log_format_names, NULL
1312 };
1313 
1314 static MYSQL_SYSVAR_ENUM(format, audit_log_format,
1315        PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
1316        "The audit log file format.", NULL, NULL,
1317        ASYNCHRONOUS, &audit_log_format_typelib);
1318 
1319 static const char *audit_log_handler_names[]=
1320   { "FILE", "SYSLOG", 0 };
1321 static TYPELIB audit_log_handler_typelib=
1322 {
1323   array_elements(audit_log_handler_names) - 1, "audit_log_handler_typelib",
1324   audit_log_handler_names, NULL
1325 };
1326 
1327 static MYSQL_SYSVAR_ENUM(handler, audit_log_handler,
1328        PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
1329        "The audit log handler.", NULL, NULL,
1330        HANDLER_FILE, &audit_log_handler_typelib);
1331 
1332 static MYSQL_SYSVAR_ULONGLONG(buffer_size, audit_log_buffer_size,
1333   PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
1334   "The size of the buffer for asynchronous logging, "
1335   "if FILE handler is used.",
1336   NULL, NULL, 1048576UL, 4096UL, ULONGLONG_MAX, 4096UL);
1337 
1338 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)1339 void audit_log_rotate_on_size_update(
1340           MYSQL_THD thd MY_ATTRIBUTE((unused)),
1341           struct st_mysql_sys_var *var MY_ATTRIBUTE((unused)),
1342           void *var_ptr MY_ATTRIBUTE((unused)),
1343           const void *save)
1344 {
1345   ulonglong new_val= *(ulonglong *)(save);
1346 
1347   audit_handler_set_option(log_handler, OPT_ROTATE_ON_SIZE, &new_val);
1348 
1349   audit_log_rotate_on_size= new_val;
1350 }
1351 
1352 static MYSQL_SYSVAR_ULONGLONG(rotate_on_size, audit_log_rotate_on_size,
1353   PLUGIN_VAR_RQCMDARG,
1354   "Maximum size of the log to start the rotation, if FILE handler is used.",
1355   NULL, audit_log_rotate_on_size_update, 0UL, 0UL, ULONGLONG_MAX, 4096UL);
1356 
1357 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)1358 void audit_log_rotations_update(
1359           MYSQL_THD thd MY_ATTRIBUTE((unused)),
1360           struct st_mysql_sys_var *var MY_ATTRIBUTE((unused)),
1361           void *var_ptr MY_ATTRIBUTE((unused)),
1362           const void *save)
1363 {
1364   ulonglong new_val= *(ulonglong *)(save);
1365 
1366   audit_handler_set_option(log_handler, OPT_ROTATIONS, &new_val);
1367 
1368   audit_log_rotations= new_val;
1369 }
1370 
1371 static MYSQL_SYSVAR_ULONGLONG(rotations, audit_log_rotations,
1372   PLUGIN_VAR_RQCMDARG,
1373   "Maximum number of rotations to keep, if FILE handler is used.",
1374   NULL, audit_log_rotations_update, 0UL, 0UL, 999UL, 1UL);
1375 
1376 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)1377 void audit_log_flush_update(
1378           MYSQL_THD thd MY_ATTRIBUTE((unused)),
1379           struct st_mysql_sys_var *var MY_ATTRIBUTE((unused)),
1380           void *var_ptr MY_ATTRIBUTE((unused)),
1381           const void *save)
1382 {
1383   char new_val= *(const char *)(save);
1384 
1385   if (new_val != audit_log_flush && new_val)
1386   {
1387     audit_log_flush= TRUE;
1388     reopen_log_file();
1389     audit_log_flush= FALSE;
1390   }
1391 }
1392 
1393 static MYSQL_SYSVAR_BOOL(flush, audit_log_flush,
1394        PLUGIN_VAR_OPCMDARG, "Flush the log file.", NULL,
1395        audit_log_flush_update, 0);
1396 
1397 static MYSQL_SYSVAR_STR(syslog_ident, audit_log_syslog_ident,
1398   PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY | PLUGIN_VAR_MEMALLOC,
1399   "The string that will be prepended to each log message, "
1400   "if SYSLOG handler is used.",
1401   NULL, NULL, default_audit_log_syslog_ident);
1402 
1403 static TYPELIB audit_log_syslog_facility_typelib=
1404 {
1405   array_elements(audit_log_syslog_facility_names) - 1,
1406   "audit_log_syslog_facility_typelib",
1407   audit_log_syslog_facility_names, NULL
1408 };
1409 
1410 static MYSQL_SYSVAR_ENUM(syslog_facility, audit_log_syslog_facility,
1411        PLUGIN_VAR_RQCMDARG,
1412        "The syslog facility to assign to messages, if SYSLOG handler is used.",
1413        NULL, NULL, 0,
1414        &audit_log_syslog_facility_typelib);
1415 
1416 static TYPELIB audit_log_syslog_priority_typelib=
1417 {
1418   array_elements(audit_log_syslog_priority_names) - 1,
1419   "audit_log_syslog_priority_typelib",
1420   audit_log_syslog_priority_names, NULL
1421 };
1422 
1423 static MYSQL_SYSVAR_ENUM(syslog_priority, audit_log_syslog_priority,
1424        PLUGIN_VAR_RQCMDARG,
1425        "Priority to be assigned to all messages written to syslog.",
1426        NULL, NULL, 0,
1427        &audit_log_syslog_priority_typelib);
1428 
1429 static MYSQL_THDVAR_STR(record_buffer,
1430                         PLUGIN_VAR_READONLY | PLUGIN_VAR_MEMALLOC | \
1431                         PLUGIN_VAR_NOSYSVAR | PLUGIN_VAR_NOCMDOPT,
1432                         "Buffer for query formatting.", NULL, NULL, "");
1433 
1434 static
1435 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)1436 audit_log_exclude_accounts_validate(
1437           MYSQL_THD thd MY_ATTRIBUTE((unused)),
1438           struct st_mysql_sys_var *var MY_ATTRIBUTE((unused)),
1439           void *save,
1440           struct st_mysql_value *value)
1441 {
1442   const char *new_val;
1443   char buf[80];
1444   int len= sizeof(buf);
1445 
1446   if (audit_log_include_accounts)
1447     return 1;
1448 
1449   new_val = value->val_str(value, buf, &len);
1450 
1451   *(const char **)(save) = new_val;
1452 
1453   return 0;
1454 }
1455 
1456 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)1457 void audit_log_exclude_accounts_update(
1458           MYSQL_THD thd MY_ATTRIBUTE((unused)),
1459           struct st_mysql_sys_var *var MY_ATTRIBUTE((unused)),
1460           void *var_ptr MY_ATTRIBUTE((unused)),
1461           const void *save)
1462 {
1463   const char *new_val= *(const char **)(save);
1464 
1465   DBUG_ASSERT(audit_log_include_accounts == NULL);
1466 
1467   my_free(audit_log_exclude_accounts);
1468   audit_log_exclude_accounts= NULL;
1469 
1470   if (new_val != NULL)
1471   {
1472     audit_log_exclude_accounts= my_strdup(new_val, MYF(MY_FAE));
1473     audit_log_set_exclude_accounts(audit_log_exclude_accounts);
1474   }
1475   else
1476   {
1477     audit_log_set_exclude_accounts("");
1478   }
1479 }
1480 
1481 static MYSQL_SYSVAR_STR(exclude_accounts, audit_log_exclude_accounts,
1482        PLUGIN_VAR_RQCMDARG,
1483        "Comma separated list of accounts "
1484        "for which events should not be logged.",
1485        audit_log_exclude_accounts_validate,
1486        audit_log_exclude_accounts_update, NULL);
1487 
1488 static
1489 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)1490 audit_log_include_accounts_validate(
1491           MYSQL_THD thd MY_ATTRIBUTE((unused)),
1492           struct st_mysql_sys_var *var MY_ATTRIBUTE((unused)),
1493           void *save,
1494           struct st_mysql_value *value)
1495 {
1496   const char *new_val;
1497   char buf[80];
1498   int len= sizeof(buf);
1499 
1500   if (audit_log_exclude_accounts)
1501     return 1;
1502 
1503   new_val = value->val_str(value, buf, &len);
1504 
1505   *(const char **)(save) = new_val;
1506 
1507   return 0;
1508 }
1509 
1510 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)1511 void audit_log_include_accounts_update(
1512           MYSQL_THD thd MY_ATTRIBUTE((unused)),
1513           struct st_mysql_sys_var *var MY_ATTRIBUTE((unused)),
1514           void *var_ptr MY_ATTRIBUTE((unused)),
1515           const void *save)
1516 {
1517   const char *new_val= *(const char **)(save);
1518 
1519   DBUG_ASSERT(audit_log_exclude_accounts == NULL);
1520 
1521   my_free(audit_log_include_accounts);
1522   audit_log_include_accounts= NULL;
1523 
1524   if (new_val != NULL)
1525   {
1526     audit_log_include_accounts= my_strdup(new_val, MYF(MY_FAE));
1527     audit_log_set_include_accounts(audit_log_include_accounts);
1528   }
1529   else
1530   {
1531     audit_log_set_include_accounts("");
1532   }
1533 }
1534 
1535 static MYSQL_SYSVAR_STR(include_accounts, audit_log_include_accounts,
1536        PLUGIN_VAR_RQCMDARG,
1537        "Comma separated list of accounts for which events should be logged.",
1538        audit_log_include_accounts_validate,
1539        audit_log_include_accounts_update, NULL);
1540 
1541 static
1542 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)1543 audit_log_exclude_commands_validate(
1544           MYSQL_THD thd MY_ATTRIBUTE((unused)),
1545           struct st_mysql_sys_var *var MY_ATTRIBUTE((unused)),
1546           void *save,
1547           struct st_mysql_value *value)
1548 {
1549   const char *new_val;
1550   char buf[80];
1551   int len= sizeof(buf);
1552 
1553   if (audit_log_include_commands)
1554     return 1;
1555 
1556   new_val = value->val_str(value, buf, &len);
1557 
1558   *(const char **)(save) = new_val;
1559 
1560   return 0;
1561 }
1562 
1563 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)1564 void audit_log_exclude_commands_update(
1565           MYSQL_THD thd MY_ATTRIBUTE((unused)),
1566           struct st_mysql_sys_var *var MY_ATTRIBUTE((unused)),
1567           void *var_ptr MY_ATTRIBUTE((unused)),
1568           const void *save)
1569 {
1570   const char *new_val= *(const char **)(save);
1571 
1572   DBUG_ASSERT(audit_log_include_commands == NULL);
1573 
1574   my_free(audit_log_exclude_commands);
1575   audit_log_exclude_commands= NULL;
1576 
1577   if (new_val != NULL)
1578   {
1579     audit_log_exclude_commands= my_strdup(new_val, MYF(MY_FAE));
1580     audit_log_set_exclude_commands(audit_log_exclude_commands);
1581   }
1582   else
1583   {
1584     audit_log_set_exclude_commands("");
1585   }
1586 }
1587 
1588 static MYSQL_SYSVAR_STR(exclude_commands, audit_log_exclude_commands,
1589        PLUGIN_VAR_RQCMDARG,
1590        "Comma separated list of commands "
1591        "for which events should not be logged.",
1592        audit_log_exclude_commands_validate,
1593        audit_log_exclude_commands_update, NULL);
1594 
1595 static
1596 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)1597 audit_log_include_commands_validate(
1598           MYSQL_THD thd MY_ATTRIBUTE((unused)),
1599           struct st_mysql_sys_var *var MY_ATTRIBUTE((unused)),
1600           void *save,
1601           struct st_mysql_value *value)
1602 {
1603   const char *new_val;
1604   char buf[80];
1605   int len= sizeof(buf);
1606 
1607   if (audit_log_exclude_commands)
1608     return 1;
1609 
1610   new_val = value->val_str(value, buf, &len);
1611 
1612   *(const char **)(save) = new_val;
1613 
1614   return 0;
1615 }
1616 
1617 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)1618 void audit_log_include_commands_update(
1619           MYSQL_THD thd MY_ATTRIBUTE((unused)),
1620           struct st_mysql_sys_var *var MY_ATTRIBUTE((unused)),
1621           void *var_ptr MY_ATTRIBUTE((unused)),
1622           const void *save)
1623 {
1624   const char *new_val= *(const char **)(save);
1625 
1626   DBUG_ASSERT(audit_log_exclude_commands == NULL);
1627 
1628   my_free(audit_log_include_commands);
1629   audit_log_include_commands= NULL;
1630 
1631   if (new_val != NULL)
1632   {
1633     audit_log_include_commands= my_strdup(new_val, MYF(MY_FAE));
1634     audit_log_set_include_commands(audit_log_include_commands);
1635   }
1636   else
1637   {
1638     audit_log_set_include_commands("");
1639   }
1640 }
1641 
1642 static MYSQL_SYSVAR_STR(include_commands, audit_log_include_commands,
1643        PLUGIN_VAR_RQCMDARG,
1644        "Comma separated list of commands for which events should be logged.",
1645        audit_log_include_commands_validate,
1646        audit_log_include_commands_update, NULL);
1647 
1648 static MYSQL_THDVAR_STR(local,
1649        PLUGIN_VAR_READONLY | PLUGIN_VAR_MEMALLOC | \
1650        PLUGIN_VAR_NOSYSVAR | PLUGIN_VAR_NOCMDOPT,
1651        "Local store.", NULL, NULL, "");
1652 
1653 static MYSQL_THDVAR_ULONG(local_ptr,
1654        PLUGIN_VAR_READONLY | PLUGIN_VAR_NOSYSVAR | PLUGIN_VAR_NOCMDOPT,
1655        "Local store ptr.", NULL, NULL, 0, 0, ULONG_MAX, 0);
1656 
1657 static struct st_mysql_sys_var* audit_log_system_variables[] =
1658 {
1659   MYSQL_SYSVAR(file),
1660   MYSQL_SYSVAR(policy),
1661   MYSQL_SYSVAR(strategy),
1662   MYSQL_SYSVAR(format),
1663   MYSQL_SYSVAR(buffer_size),
1664   MYSQL_SYSVAR(rotate_on_size),
1665   MYSQL_SYSVAR(rotations),
1666   MYSQL_SYSVAR(flush),
1667   MYSQL_SYSVAR(handler),
1668   MYSQL_SYSVAR(syslog_ident),
1669   MYSQL_SYSVAR(syslog_priority),
1670   MYSQL_SYSVAR(syslog_facility),
1671   MYSQL_SYSVAR(record_buffer),
1672   MYSQL_SYSVAR(exclude_accounts),
1673   MYSQL_SYSVAR(include_accounts),
1674   MYSQL_SYSVAR(exclude_commands),
1675   MYSQL_SYSVAR(include_commands),
1676   MYSQL_SYSVAR(local),
1677   MYSQL_SYSVAR(local_ptr),
1678   NULL
1679 };
1680 
1681 static char thd_local_init_buf[sizeof(audit_log_thd_local)];
1682 
1683 static
audit_log_so_init()1684 void MY_ATTRIBUTE((constructor)) audit_log_so_init()
1685 {
1686   memset(thd_local_init_buf, 1, sizeof(thd_local_init_buf) - 1);
1687   thd_local_init_buf[sizeof(thd_local_init_buf) - 1]= 0;
1688 }
1689 
1690 /*
1691  Return pointer to THD specific data.
1692  */
1693 static
get_thd_local(MYSQL_THD thd)1694 audit_log_thd_local *get_thd_local(MYSQL_THD thd)
1695 {
1696   audit_log_thd_local *local= (audit_log_thd_local *) THDVAR(thd, local_ptr);
1697 
1698   compile_time_assert(sizeof(THDVAR(thd, local_ptr)) >= sizeof(void *));
1699 
1700   if (unlikely(local == NULL))
1701   {
1702     THDVAR_SET(thd, local, thd_local_init_buf);
1703     local= (audit_log_thd_local *) THDVAR(thd, local);
1704     memset(local, 0, sizeof(audit_log_thd_local));
1705     THDVAR(thd, local_ptr)= (ulong) local;
1706   }
1707   return local;
1708 }
1709 
1710 
1711 /*
1712  Allocate and return buffer of given size.
1713  */
1714 static
get_record_buffer(MYSQL_THD thd,size_t size)1715 char *get_record_buffer(MYSQL_THD thd, size_t size)
1716 {
1717   audit_log_thd_local *local= get_thd_local(thd);
1718   char *buf= local->record_buffer;
1719 
1720   if (local->record_buffer_size < size)
1721   {
1722     local->record_buffer_size= size;
1723 
1724     buf = (char *) my_malloc(size, MYF(MY_FAE));
1725     memset(buf, 1, size - 1);
1726     buf[size - 1]= 0;
1727 
1728     THDVAR_SET(thd, record_buffer, buf);
1729 
1730     my_free(buf);
1731 
1732     buf = (char *) THDVAR(thd, record_buffer);
1733     local->record_buffer = buf;
1734   }
1735 
1736   return buf;
1737 }
1738 
1739 
1740 /*
1741   Plugin type-specific descriptor
1742 */
1743 static struct st_mysql_audit audit_log_descriptor=
1744 {
1745   MYSQL_AUDIT_INTERFACE_VERSION,                    /* interface version    */
1746   NULL,                                             /* release_thd function */
1747   audit_log_notify,                                 /* notify function      */
1748   { MYSQL_AUDIT_GENERAL_CLASSMASK |
1749     MYSQL_AUDIT_CONNECTION_CLASSMASK }              /* class mask           */
1750 };
1751 
1752 /*
1753   Plugin status variables for SHOW STATUS
1754 */
1755 
1756 static struct st_mysql_show_var audit_log_status_variables[]=
1757 {
1758   {"Audit_log_buffer_size_overflow",
1759     (char*) &audit_log_buffer_size_overflow,
1760     SHOW_LONG},
1761   { 0, 0, 0}
1762 };
1763 
1764 
1765 /*
1766   Plugin library descriptor
1767 */
1768 
mysql_declare_plugin(audit_log)1769 mysql_declare_plugin(audit_log)
1770 {
1771   MYSQL_AUDIT_PLUGIN,                     /* type                            */
1772   &audit_log_descriptor,                  /* descriptor                      */
1773   "audit_log",                            /* name                            */
1774   "Percona LLC and/or its affiliates.",   /* author                          */
1775   "Audit log",                            /* description                     */
1776   PLUGIN_LICENSE_GPL,
1777   audit_log_plugin_init,                  /* init function (when loaded)     */
1778   audit_log_plugin_deinit,                /* deinit function (when unloaded) */
1779   PLUGIN_VERSION,                         /* version                         */
1780   audit_log_status_variables,             /* status variables                */
1781   audit_log_system_variables,             /* system variables                */
1782   NULL,
1783   0,
1784 }
1785 mysql_declare_plugin_end;
1786 
1787