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   /* Note: query_length is the maximun size using utf8m4(4 bytes) that
621    * event->general_query_length may use. In the if branch, we convert it to
622    * utf8mb4. We store the recalculated (real size) length to query variable
623    * and use the remaing of the buffer for the output that will be printed to
624    * audit log. Parameter char *buf must be big enough to store
625    * the query (using utf8mb4) + the full output of audit event, which will
626    * contain the query again. At the else branch we estime this size.
627    */
628   if (query_length < (size_t) (endbuf - endptr))
629   {
630     uint errors;
631     query_length= my_convert(endptr, query_length,
632                              &my_charset_utf8mb4_general_ci,
633                              event->general_query,
634                              event->general_query_length,
635                              event->general_charset, &errors);
636     query= endptr;
637     endptr+= query_length;
638 
639     full_outlen+= query_length;
640 
641     query= escape_string(query, query_length, endptr, endbuf - endptr,
642                          &endptr, &full_outlen);
643   }
644   else
645   {
646     endptr= endbuf;
647     query= escape_string(event->general_query, event->general_query_length,
648                          endptr, endbuf - endptr, &endptr, &full_outlen);
649     full_outlen*= my_charset_utf8mb4_general_ci.mbmaxlen;
650     full_outlen+= query_length;
651   }
652 
653   user= escape_string(event->general_user, event->general_user_length,
654                       endptr, endbuf - endptr, &endptr, &full_outlen);
655   host= escape_string(event->general_host.str, event->general_host.length,
656                       endptr, endbuf - endptr, &endptr, &full_outlen);
657   external_user= escape_string(event->general_external_user.str,
658                                event->general_external_user.length,
659                                endptr, endbuf - endptr, &endptr, &full_outlen);
660   ip= escape_string(event->general_ip.str, event->general_ip.length,
661                     endptr, endbuf - endptr, &endptr, &full_outlen);
662   db= escape_string(default_db, strlen(default_db),
663                     endptr, endbuf - endptr, &endptr, &full_outlen);
664 
665   buflen_estimated= full_outlen + strlen(format_string[audit_log_format]) +
666                     strlen(name) + event->general_sql_command.length +
667                     20 + /* general_thread_id */
668                     20 + /* status */
669                     MAX_RECORD_ID_SIZE + MAX_TIMESTAMP_SIZE;
670   if (buflen_estimated > buflen)
671   {
672     *outlen= buflen_estimated;
673     return NULL;
674   }
675 
676   *outlen= snprintf(endptr, endbuf - endptr,
677                     format_string[audit_log_format],
678                     name,
679                     make_record_id(id_str, sizeof(id_str)),
680                     make_timestamp(timestamp, sizeof(timestamp), t),
681                     event->general_sql_command.str,
682                     event->general_thread_id,
683                     status, query, user, host, external_user, ip, db);
684 
685   /* make sure that record is not truncated */
686   DBUG_ASSERT(endptr + *outlen <= buf + buflen);
687 
688   return endptr;
689 }
690 
691 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)692 char *audit_log_connection_record(char *buf, size_t buflen,
693                                   const char *name, time_t t,
694                                   const struct mysql_event_connection *event,
695                                   size_t *outlen)
696 {
697   char id_str[MAX_RECORD_ID_SIZE];
698   char timestamp[MAX_TIMESTAMP_SIZE];
699   char *user, *priv_user, *external_user, *proxy_user, *host, *ip, *database;
700   char *endptr= buf, *endbuf= buf + buflen;
701 
702   const char *format_string[] = {
703                      "<AUDIT_RECORD\n"
704                      "  NAME=\"%s\"\n"
705                      "  RECORD=\"%s\"\n"
706                      "  TIMESTAMP=\"%s\"\n"
707                      "  CONNECTION_ID=\"%lu\"\n"
708                      "  STATUS=\"%d\"\n"
709                      "  USER=\"%s\"\n"
710                      "  PRIV_USER=\"%s\"\n"
711                      "  OS_LOGIN=\"%s\"\n"
712                      "  PROXY_USER=\"%s\"\n"
713                      "  HOST=\"%s\"\n"
714                      "  IP=\"%s\"\n"
715                      "  DB=\"%s\"\n"
716                      "/>\n",
717 
718                      "<AUDIT_RECORD>\n"
719                      "  <NAME>%s</NAME>\n"
720                      "  <RECORD>%s</RECORD>\n"
721                      "  <TIMESTAMP>%s</TIMESTAMP>\n"
722                      "  <CONNECTION_ID>%lu</CONNECTION_ID>\n"
723                      "  <STATUS>%d</STATUS>\n"
724                      "  <USER>%s</USER>\n"
725                      "  <PRIV_USER>%s</PRIV_USER>\n"
726                      "  <OS_LOGIN>%s</OS_LOGIN>\n"
727                      "  <PROXY_USER>%s</PROXY_USER>\n"
728                      "  <HOST>%s</HOST>\n"
729                      "  <IP>%s</IP>\n"
730                      "  <DB>%s</DB>\n"
731                      "</AUDIT_RECORD>\n",
732 
733                      "{\"audit_record\":"
734                        "{\"name\":\"%s\","
735                        "\"record\":\"%s\","
736                        "\"timestamp\":\"%s\","
737                        "\"connection_id\":\"%lu\","
738                        "\"status\":%d,"
739                        "\"user\":\"%s\","
740                        "\"priv_user\":\"%s\","
741                        "\"os_login\":\"%s\","
742                        "\"proxy_user\":\"%s\","
743                        "\"host\":\"%s\","
744                        "\"ip\":\"%s\","
745                        "\"db\":\"%s\"}}\n",
746 
747                      "\"%s\",\"%s\",\"%s\",\"%lu\",%d,\"%s\",\"%s\",\"%s\","
748                      "\"%s\",\"%s\",\"%s\",\"%s\"\n" };
749 
750   user= escape_string(event->user, event->user_length,
751                       endptr, endbuf - endptr, &endptr, NULL);
752   priv_user= escape_string(event->priv_user,
753                            event->priv_user_length,
754                            endptr, endbuf - endptr, &endptr, NULL);
755   external_user= escape_string(event->external_user,
756                                event->external_user_length,
757                                endptr, endbuf - endptr, &endptr, NULL);
758   proxy_user= escape_string(event->proxy_user, event->proxy_user_length,
759                             endptr, endbuf - endptr, &endptr, NULL);
760   host= escape_string(event->host, event->host_length,
761                       endptr, endbuf - endptr, &endptr, NULL);
762   ip= escape_string(event->ip, event->ip_length,
763                     endptr, endbuf - endptr, &endptr, NULL);
764   database= escape_string(event->database, event->database_length,
765                           endptr, endbuf - endptr, &endptr, NULL);
766 
767   DBUG_ASSERT((endptr - buf) * 2 +
768               strlen(format_string[audit_log_format]) +
769               strlen(name) +
770               MAX_RECORD_ID_SIZE +
771               MAX_TIMESTAMP_SIZE +
772               20 + /* event->thread_id */
773               20 /* event->status */
774               < buflen);
775 
776   *outlen= snprintf(endptr, endbuf - endptr,
777                     format_string[audit_log_format],
778                     name,
779                     make_record_id(id_str, sizeof(id_str)),
780                     make_timestamp(timestamp, sizeof(timestamp), t),
781                     event->thread_id,
782                     event->status, user, priv_user,external_user,
783                     proxy_user, host, ip, database);
784 
785   /* make sure that record is not truncated */
786   DBUG_ASSERT(endptr + *outlen <= buf + buflen);
787 
788   return endptr;
789 }
790 
791 static
audit_log_header(MY_STAT * stat,char * buf,size_t buflen)792 size_t audit_log_header(MY_STAT *stat, char *buf, size_t buflen)
793 {
794   const char *format_string[] = {
795                      "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
796                      "<AUDIT>\n",
797                      "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
798                      "<AUDIT>\n",
799                      "",
800                      "" };
801 
802   DBUG_ASSERT(strcmp(system_charset_info->csname, "utf8") == 0);
803 
804   log_file_time= stat->st_mtime;
805 
806   init_record_id(stat->st_size);
807 
808   if (buf == NULL)
809   {
810     return 0;
811   }
812 
813   return my_snprintf(buf, buflen, format_string[audit_log_format]);
814 }
815 
816 
817 static
audit_log_footer(char * buf,size_t buflen)818 size_t audit_log_footer(char *buf, size_t buflen)
819 {
820   const char *format_string[] = {
821                      "</AUDIT>\n",
822                      "</AUDIT>\n",
823                      "",
824                      "" };
825 
826   if (buf == NULL)
827   {
828     return 0;
829   }
830 
831   return my_snprintf(buf, buflen, format_string[audit_log_format]);
832 }
833 
834 static
init_new_log_file()835 int init_new_log_file()
836 {
837   if (audit_log_handler == HANDLER_FILE)
838   {
839     audit_handler_file_config_t opts;
840     opts.name= audit_log_file;
841     opts.rotate_on_size= audit_log_rotate_on_size;
842     opts.rotations= audit_log_rotations;
843     opts.sync_on_write= audit_log_strategy == SYNCHRONOUS;
844     opts.use_buffer= audit_log_strategy < SEMISYNCHRONOUS;
845     opts.buffer_size= audit_log_buffer_size;
846     opts.can_drop_data= audit_log_strategy == PERFORMANCE;
847     opts.header= audit_log_header;
848     opts.footer= audit_log_footer;
849 
850     log_handler= audit_handler_file_open(&opts);
851     if (log_handler == NULL)
852     {
853       fprintf_timestamp(stderr);
854       fprintf(stderr, "Cannot open file %s. ", audit_log_file);
855       perror("Error: ");
856       return(1);
857     }
858   }
859   else
860   {
861     audit_handler_syslog_config_t opts;
862     opts.facility= audit_log_syslog_facility_codes[audit_log_syslog_facility];
863     opts.ident= audit_log_syslog_ident;
864     opts.priority= audit_log_syslog_priority_codes[audit_log_syslog_priority];
865     opts.header= audit_log_header;
866     opts.footer= audit_log_footer;
867 
868     log_handler= audit_handler_syslog_open(&opts);
869     if (log_handler == NULL)
870     {
871       fprintf_timestamp(stderr);
872       fprintf(stderr, "Cannot open syslog. ");
873       perror("Error: ");
874       return(1);
875     }
876   }
877 
878   return(0);
879 }
880 
881 
882 static
reopen_log_file()883 int reopen_log_file()
884 {
885   if (audit_handler_flush(log_handler))
886   {
887     fprintf_timestamp(stderr);
888     fprintf(stderr, "Cannot open file %s. ", audit_log_file);
889     perror("Error: ");
890     return(1);
891   }
892 
893   return(0);
894 }
895 
896 /*
897  Struct to store various THD specific data
898  */
899 typedef struct
900 {
901   /* size of allocated large buffer for record formatting */
902   size_t record_buffer_size;
903   /* large buffer for record formatting */
904   char *record_buffer;
905   /* skip logging session */
906   my_bool skip_session;
907   /* skip logging for the next query */
908   my_bool skip_query;
909   /* default database */
910   char db[NAME_LEN + 1];
911   /* default database candidate */
912   char init_db_query[NAME_LEN + 1];
913 } audit_log_thd_local;
914 
915 /*
916  Return pointer to THD specific data.
917  */
918 static
919 audit_log_thd_local *get_thd_local(MYSQL_THD thd);
920 
921 /*
922  Allocate and return buffer of given size.
923  */
924 static
925 char *get_record_buffer(MYSQL_THD thd, size_t size);
926 
927 
928 static
audit_log_plugin_init(void * arg MY_ATTRIBUTE ((unused)))929 int audit_log_plugin_init(void *arg MY_ATTRIBUTE((unused)))
930 {
931   char buf[1024];
932   size_t len;
933 
934   logger_init_mutexes();
935 
936   audit_log_filter_init();
937 
938   if (audit_log_exclude_accounts != NULL && audit_log_include_accounts != NULL)
939   {
940     fprintf(stderr, "Both 'audit_log_exclude_accounts' and "
941             "'audit_log_include_accounts' are not NULL\n");
942     goto validation_error;
943   }
944 
945   if (audit_log_exclude_commands != NULL && audit_log_include_commands != NULL)
946   {
947     fprintf(stderr, "Both 'audit_log_exclude_commands' and "
948             "'audit_log_include_commands' are not NULL\n");
949     goto validation_error;
950   }
951 
952   if (audit_log_exclude_accounts != NULL)
953   {
954     audit_log_exclude_accounts= my_strdup(audit_log_exclude_accounts,
955                                           MYF(MY_FAE));
956     audit_log_set_exclude_accounts(audit_log_exclude_accounts);
957   }
958   if (audit_log_include_accounts != NULL)
959   {
960     audit_log_include_accounts= my_strdup(audit_log_include_accounts,
961                                           MYF(MY_FAE));
962     audit_log_set_include_accounts(audit_log_include_accounts);
963   }
964   if (audit_log_exclude_commands != NULL)
965   {
966     audit_log_exclude_commands= my_strdup(audit_log_exclude_commands,
967                                           MYF(MY_FAE));
968     audit_log_set_exclude_commands(audit_log_exclude_commands);
969   }
970   if (audit_log_include_commands != NULL)
971   {
972     audit_log_include_commands= my_strdup(audit_log_include_commands,
973                                           MYF(MY_FAE));
974     audit_log_set_include_commands(audit_log_include_commands);
975   }
976 
977   if (init_new_log_file())
978     return(1);
979 
980   if (audit_log_audit_record(buf, sizeof(buf), "Audit", time(NULL), &len))
981     audit_log_write(buf, len);
982 
983   return 0;
984 
985 validation_error:
986 
987   audit_log_exclude_accounts= audit_log_include_accounts= NULL;
988   audit_log_exclude_commands= audit_log_include_commands= NULL;
989 
990   return 1;
991 }
992 
993 
994 static
audit_log_plugin_deinit(void * arg MY_ATTRIBUTE ((unused)))995 int audit_log_plugin_deinit(void *arg MY_ATTRIBUTE((unused)))
996 {
997   char buf[1024];
998   size_t len;
999 
1000   if (audit_log_audit_record(buf, sizeof(buf), "NoAudit", time(NULL), &len))
1001     audit_log_write(buf, len);
1002 
1003   audit_handler_close(log_handler);
1004 
1005   audit_log_filter_destroy();
1006 
1007   my_free(audit_log_include_accounts);
1008   my_free(audit_log_exclude_accounts);
1009 
1010   my_free(audit_log_include_commands);
1011   my_free(audit_log_exclude_commands);
1012 
1013   return(0);
1014 }
1015 
1016 
1017 static
is_event_class_allowed_by_policy(unsigned int class,enum audit_log_policy_t policy)1018 int is_event_class_allowed_by_policy(unsigned int class,
1019                                      enum audit_log_policy_t policy)
1020 {
1021   static unsigned int class_mask[]=
1022   {
1023     MYSQL_AUDIT_GENERAL_CLASSMASK | MYSQL_AUDIT_CONNECTION_CLASSMASK, /* ALL */
1024     0,                                                             /* NONE */
1025     MYSQL_AUDIT_CONNECTION_CLASSMASK,                              /* LOGINS */
1026     MYSQL_AUDIT_GENERAL_CLASSMASK,                                 /* QUERIES */
1027   };
1028 
1029   return (class_mask[policy] & (1 << class)) != 0;
1030 }
1031 
1032 static
next_word(const char * str,size_t * len,const struct charset_info_st * charset)1033 const char *next_word(const char *str, size_t *len,
1034                       const struct charset_info_st *charset)
1035 {
1036   while (*str && my_isspace(charset, *str))
1037   {
1038     if (*str == '/' && str[1] == '*' && str[2] == '!')
1039       str+= 3;
1040     else if (*str == '/' && str[1] == '*')
1041     {
1042       while (*str && !(*str == '*' && str[1] == '/'))
1043         str++;
1044     }
1045     else
1046       str++;
1047   }
1048 
1049   *len= 0;
1050   while (str[*len] && my_isvar(charset, str[*len]))
1051     (*len)++;
1052 
1053   if (*len == 0 && *str == '`')
1054   {
1055     (*len)++;
1056     while (str[*len])
1057     {
1058       if (str[*len] == '`' && str[*len + 1] == '`')
1059         (*len)++;
1060       else if (str[*len] == '`')
1061         break;
1062       (*len)++;
1063     }
1064     (*len)++;
1065   }
1066 
1067   return str;
1068 }
1069 
1070 
1071 static
audit_log_update_thd_local(MYSQL_THD thd,audit_log_thd_local * local,unsigned int event_class,const void * event)1072 void audit_log_update_thd_local(MYSQL_THD thd,
1073                                 audit_log_thd_local *local,
1074                                 unsigned int event_class,
1075                                 const void *event)
1076 {
1077   DBUG_ASSERT(audit_log_include_accounts == NULL ||
1078               audit_log_exclude_accounts == NULL);
1079   DBUG_ASSERT(audit_log_include_commands == NULL ||
1080               audit_log_exclude_commands == NULL);
1081 
1082   if (event_class == MYSQL_AUDIT_CONNECTION_CLASS)
1083   {
1084     const struct mysql_event_connection *event_connection=
1085       (const struct mysql_event_connection *) event;
1086 
1087     const char *host = get_priv_host(thd);
1088     const size_t host_length = strlen(host);
1089 
1090     local->skip_session= FALSE;
1091     if (audit_log_include_accounts != NULL &&
1092         !audit_log_check_account_included(event_connection->priv_user,
1093                                           event_connection->priv_user_length,
1094                                           host,
1095                                           host_length))
1096       local->skip_session= TRUE;
1097     if (audit_log_exclude_accounts != NULL &&
1098         audit_log_check_account_excluded(event_connection->priv_user,
1099                                          event_connection->priv_user_length,
1100                                          host,
1101                                          host_length))
1102       local->skip_session= TRUE;
1103 
1104     if (event_connection->status == 0)
1105     {
1106       /* track default DB change */
1107       DBUG_ASSERT(event_connection->database_length <= sizeof(local->db));
1108       memcpy(local->db, event_connection->database,
1109              event_connection->database_length);
1110       local->db[event_connection->database_length]= 0;
1111     }
1112   }
1113   else if (event_class == MYSQL_AUDIT_GENERAL_CLASS)
1114   {
1115     const struct mysql_event_general *event_general=
1116       (const struct mysql_event_general *) event;
1117 
1118     if (event_general->event_subclass == MYSQL_AUDIT_GENERAL_STATUS)
1119     {
1120       local->skip_query= audit_log_include_commands
1121             && !audit_log_check_command_included(
1122                      event_general->general_sql_command.str,
1123                      event_general->general_sql_command.length);
1124 
1125       local->skip_query|= audit_log_exclude_commands
1126             && audit_log_check_command_excluded(
1127                      event_general->general_sql_command.str,
1128                      event_general->general_sql_command.length);
1129 
1130       if (!local->skip_query &&
1131           ((event_general->general_command_length == 4 &&
1132             strncmp(event_general->general_command, "Quit", 4) == 0) ||
1133            (event_general->general_command_length == 11 &&
1134             strncmp(event_general->general_command,
1135                     "Change user", 11) == 0)))
1136         local->skip_query= TRUE;
1137     }
1138 
1139     if (event_general->event_subclass == MYSQL_AUDIT_GENERAL_LOG &&
1140         event_general->general_command_length == 7 &&
1141         strncmp(event_general->general_command, "Init DB", 7) == 0 &&
1142         event_general->general_query != NULL &&
1143         strpbrk("\n\r\t ", event_general->general_query) == NULL)
1144     {
1145       /* Database is about to be changed. Server doesn't provide database
1146       name in STATUS event, so remember it now. */
1147 
1148       DBUG_ASSERT(event_general->general_query_length <= sizeof(local->db));
1149       memcpy(local->db, event_general->general_query,
1150              event_general->general_query_length);
1151       local->db[event_general->general_query_length]= 0;
1152     }
1153     if (event_general->event_subclass == MYSQL_AUDIT_GENERAL_STATUS &&
1154         event_general->general_sql_command.length == 9 &&
1155         strncmp(event_general->general_sql_command.str, "change_db", 9) == 0 &&
1156         event_general->general_command_length == 5 &&
1157         strncmp(event_general->general_command, "Query", 5) == 0 &&
1158         event_general->general_error_code == 0)
1159     {
1160       /* it's "use dbname" query */
1161 
1162       size_t len;
1163       const char *word;
1164 
1165       word= next_word(event_general->general_query, &len,
1166                       event_general->general_charset);
1167       if (strncasecmp("use", word, len) == 0)
1168       {
1169         uint errors;
1170 
1171         word= next_word(word + len, &len, event_general->general_charset);
1172         if (*word == '`')
1173         {
1174           word++;
1175           len-= 2;
1176         }
1177         len= my_convert(local->db, sizeof(local->db) - 1, system_charset_info,
1178                         word, len, event_general->general_charset, &errors);
1179         local->db[len]= 0;
1180       }
1181     }
1182   }
1183 }
1184 
1185 
1186 static
audit_log_notify(MYSQL_THD thd MY_ATTRIBUTE ((unused)),unsigned int event_class,const void * event)1187 void audit_log_notify(MYSQL_THD thd MY_ATTRIBUTE((unused)),
1188                       unsigned int event_class,
1189                       const void *event)
1190 {
1191   char buf[4096];
1192   char *log_rec = NULL;
1193   char *allocated_buf= get_record_buffer(thd, 0);
1194   size_t len, buflen;
1195   audit_log_thd_local *local= get_thd_local(thd);
1196 
1197   audit_log_update_thd_local(thd, local, event_class, event);
1198 
1199   if (!is_event_class_allowed_by_policy(event_class, audit_log_policy))
1200     return;
1201 
1202   if (local->skip_session)
1203     return;
1204 
1205   if (event_class == MYSQL_AUDIT_GENERAL_CLASS)
1206   {
1207     const struct mysql_event_general *event_general=
1208       (const struct mysql_event_general *) event;
1209     switch (event_general->event_subclass)
1210     {
1211     case MYSQL_AUDIT_GENERAL_STATUS:
1212       if (local->skip_query)
1213         break;
1214 
1215       /* use allocated buffer if available */
1216       if (allocated_buf != NULL)
1217       {
1218         log_rec= allocated_buf;
1219         buflen= local->record_buffer_size;
1220       }
1221       else
1222       {
1223         log_rec= buf;
1224         buflen= sizeof(buf);
1225       }
1226       log_rec= audit_log_general_record(log_rec, buflen,
1227                                         event_general->general_command,
1228                                         event_general->general_time,
1229                                         event_general->general_error_code,
1230                                         event_general, local->db,
1231                                         &len);
1232       if (len > buflen)
1233       {
1234         buflen= len + 1024;
1235         log_rec= audit_log_general_record(get_record_buffer(thd, buflen),
1236                                           buflen,
1237                                           event_general->general_command,
1238                                           event_general->general_time,
1239                                           event_general->general_error_code,
1240                                           event_general, local->db,
1241                                           &len);
1242         DBUG_ASSERT(log_rec);
1243       }
1244       if (log_rec)
1245         audit_log_write(log_rec, len);
1246       break;
1247     }
1248   }
1249   else if (event_class == MYSQL_AUDIT_CONNECTION_CLASS)
1250   {
1251     const struct mysql_event_connection *event_connection=
1252       (const struct mysql_event_connection *) event;
1253     switch (event_connection->event_subclass)
1254     {
1255     case MYSQL_AUDIT_CONNECTION_CONNECT:
1256       log_rec= audit_log_connection_record(buf, sizeof(buf), "Connect",
1257                                            time(NULL), event_connection, &len);
1258       break;
1259     case MYSQL_AUDIT_CONNECTION_DISCONNECT:
1260       log_rec= audit_log_connection_record(buf, sizeof(buf), "Quit",
1261                                            time(NULL), event_connection, &len);
1262       break;
1263    case MYSQL_AUDIT_CONNECTION_CHANGE_USER:
1264       log_rec= audit_log_connection_record(buf, sizeof(buf), "Change user",
1265                                            time(NULL), event_connection, &len);
1266       break;
1267     default:
1268       break;
1269     }
1270     if (log_rec)
1271       audit_log_write(log_rec, len);
1272   }
1273 }
1274 
1275 
1276 /*
1277  * Plugin system vars
1278  */
1279 
1280 static MYSQL_SYSVAR_STR(file, audit_log_file,
1281   PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY | PLUGIN_VAR_MEMALLOC,
1282   "The name of the log file.", NULL, NULL, default_audit_log_file);
1283 
1284 static const char *audit_log_policy_names[]=
1285                     { "ALL", "NONE", "LOGINS", "QUERIES", 0 };
1286 
1287 static TYPELIB audit_log_policy_typelib=
1288 {
1289   array_elements(audit_log_policy_names) - 1, "audit_log_policy_typelib",
1290   audit_log_policy_names, NULL
1291 };
1292 
1293 static MYSQL_SYSVAR_ENUM(policy, audit_log_policy, PLUGIN_VAR_RQCMDARG,
1294        "The policy controlling the information written by the audit log "
1295        "plugin to its log file.", NULL, NULL, ALL,
1296        &audit_log_policy_typelib);
1297 
1298 static const char *audit_log_strategy_names[]=
1299   { "ASYNCHRONOUS", "PERFORMANCE", "SEMISYNCHRONOUS", "SYNCHRONOUS", 0 };
1300 static TYPELIB audit_log_strategy_typelib=
1301 {
1302   array_elements(audit_log_strategy_names) - 1, "audit_log_strategy_typelib",
1303   audit_log_strategy_names, NULL
1304 };
1305 
1306 static MYSQL_SYSVAR_ENUM(strategy, audit_log_strategy,
1307        PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
1308        "The logging method used by the audit log plugin, "
1309        "if FILE handler is used.", NULL, NULL,
1310        ASYNCHRONOUS, &audit_log_strategy_typelib);
1311 
1312 static const char *audit_log_format_names[]=
1313   { "OLD", "NEW", "JSON", "CSV", 0 };
1314 static TYPELIB audit_log_format_typelib=
1315 {
1316   array_elements(audit_log_format_names) - 1, "audit_log_format_typelib",
1317   audit_log_format_names, NULL
1318 };
1319 
1320 static MYSQL_SYSVAR_ENUM(format, audit_log_format,
1321        PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
1322        "The audit log file format.", NULL, NULL,
1323        ASYNCHRONOUS, &audit_log_format_typelib);
1324 
1325 static const char *audit_log_handler_names[]=
1326   { "FILE", "SYSLOG", 0 };
1327 static TYPELIB audit_log_handler_typelib=
1328 {
1329   array_elements(audit_log_handler_names) - 1, "audit_log_handler_typelib",
1330   audit_log_handler_names, NULL
1331 };
1332 
1333 static MYSQL_SYSVAR_ENUM(handler, audit_log_handler,
1334        PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
1335        "The audit log handler.", NULL, NULL,
1336        HANDLER_FILE, &audit_log_handler_typelib);
1337 
1338 static MYSQL_SYSVAR_ULONGLONG(buffer_size, audit_log_buffer_size,
1339   PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
1340   "The size of the buffer for asynchronous logging, "
1341   "if FILE handler is used.",
1342   NULL, NULL, 1048576UL, 4096UL, ULONGLONG_MAX, 4096UL);
1343 
1344 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)1345 void audit_log_rotate_on_size_update(
1346           MYSQL_THD thd MY_ATTRIBUTE((unused)),
1347           struct st_mysql_sys_var *var MY_ATTRIBUTE((unused)),
1348           void *var_ptr MY_ATTRIBUTE((unused)),
1349           const void *save)
1350 {
1351   ulonglong new_val= *(ulonglong *)(save);
1352 
1353   audit_handler_set_option(log_handler, OPT_ROTATE_ON_SIZE, &new_val);
1354 
1355   audit_log_rotate_on_size= new_val;
1356 }
1357 
1358 static MYSQL_SYSVAR_ULONGLONG(rotate_on_size, audit_log_rotate_on_size,
1359   PLUGIN_VAR_RQCMDARG,
1360   "Maximum size of the log to start the rotation, if FILE handler is used.",
1361   NULL, audit_log_rotate_on_size_update, 0UL, 0UL, ULONGLONG_MAX, 4096UL);
1362 
1363 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)1364 void audit_log_rotations_update(
1365           MYSQL_THD thd MY_ATTRIBUTE((unused)),
1366           struct st_mysql_sys_var *var MY_ATTRIBUTE((unused)),
1367           void *var_ptr MY_ATTRIBUTE((unused)),
1368           const void *save)
1369 {
1370   ulonglong new_val= *(ulonglong *)(save);
1371 
1372   audit_handler_set_option(log_handler, OPT_ROTATIONS, &new_val);
1373 
1374   audit_log_rotations= new_val;
1375 }
1376 
1377 static MYSQL_SYSVAR_ULONGLONG(rotations, audit_log_rotations,
1378   PLUGIN_VAR_RQCMDARG,
1379   "Maximum number of rotations to keep, if FILE handler is used.",
1380   NULL, audit_log_rotations_update, 0UL, 0UL, 999UL, 1UL);
1381 
1382 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)1383 void audit_log_flush_update(
1384           MYSQL_THD thd MY_ATTRIBUTE((unused)),
1385           struct st_mysql_sys_var *var MY_ATTRIBUTE((unused)),
1386           void *var_ptr MY_ATTRIBUTE((unused)),
1387           const void *save)
1388 {
1389   char new_val= *(const char *)(save);
1390 
1391   if (new_val != audit_log_flush && new_val)
1392   {
1393     audit_log_flush= TRUE;
1394     reopen_log_file();
1395     audit_log_flush= FALSE;
1396   }
1397 }
1398 
1399 static MYSQL_SYSVAR_BOOL(flush, audit_log_flush,
1400        PLUGIN_VAR_OPCMDARG, "Flush the log file.", NULL,
1401        audit_log_flush_update, 0);
1402 
1403 static MYSQL_SYSVAR_STR(syslog_ident, audit_log_syslog_ident,
1404   PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY | PLUGIN_VAR_MEMALLOC,
1405   "The string that will be prepended to each log message, "
1406   "if SYSLOG handler is used.",
1407   NULL, NULL, default_audit_log_syslog_ident);
1408 
1409 static TYPELIB audit_log_syslog_facility_typelib=
1410 {
1411   array_elements(audit_log_syslog_facility_names) - 1,
1412   "audit_log_syslog_facility_typelib",
1413   audit_log_syslog_facility_names, NULL
1414 };
1415 
1416 static MYSQL_SYSVAR_ENUM(syslog_facility, audit_log_syslog_facility,
1417        PLUGIN_VAR_RQCMDARG,
1418        "The syslog facility to assign to messages, if SYSLOG handler is used.",
1419        NULL, NULL, 0,
1420        &audit_log_syslog_facility_typelib);
1421 
1422 static TYPELIB audit_log_syslog_priority_typelib=
1423 {
1424   array_elements(audit_log_syslog_priority_names) - 1,
1425   "audit_log_syslog_priority_typelib",
1426   audit_log_syslog_priority_names, NULL
1427 };
1428 
1429 static MYSQL_SYSVAR_ENUM(syslog_priority, audit_log_syslog_priority,
1430        PLUGIN_VAR_RQCMDARG,
1431        "Priority to be assigned to all messages written to syslog.",
1432        NULL, NULL, 0,
1433        &audit_log_syslog_priority_typelib);
1434 
1435 static MYSQL_THDVAR_STR(record_buffer,
1436                         PLUGIN_VAR_READONLY | PLUGIN_VAR_MEMALLOC | \
1437                         PLUGIN_VAR_NOSYSVAR | PLUGIN_VAR_NOCMDOPT,
1438                         "Buffer for query formatting.", NULL, NULL, "");
1439 
1440 static
1441 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)1442 audit_log_exclude_accounts_validate(
1443           MYSQL_THD thd MY_ATTRIBUTE((unused)),
1444           struct st_mysql_sys_var *var MY_ATTRIBUTE((unused)),
1445           void *save,
1446           struct st_mysql_value *value)
1447 {
1448   const char *new_val;
1449   char buf[80];
1450   int len= sizeof(buf);
1451 
1452   if (audit_log_include_accounts)
1453     return 1;
1454 
1455   new_val = value->val_str(value, buf, &len);
1456 
1457   *(const char **)(save) = new_val;
1458 
1459   return 0;
1460 }
1461 
1462 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)1463 void audit_log_exclude_accounts_update(
1464           MYSQL_THD thd MY_ATTRIBUTE((unused)),
1465           struct st_mysql_sys_var *var MY_ATTRIBUTE((unused)),
1466           void *var_ptr MY_ATTRIBUTE((unused)),
1467           const void *save)
1468 {
1469   const char *new_val= *(const char **)(save);
1470 
1471   DBUG_ASSERT(audit_log_include_accounts == NULL);
1472 
1473   my_free(audit_log_exclude_accounts);
1474   audit_log_exclude_accounts= NULL;
1475 
1476   if (new_val != NULL)
1477   {
1478     audit_log_exclude_accounts= my_strdup(new_val, MYF(MY_FAE));
1479     audit_log_set_exclude_accounts(audit_log_exclude_accounts);
1480   }
1481   else
1482   {
1483     audit_log_set_exclude_accounts("");
1484   }
1485 }
1486 
1487 static MYSQL_SYSVAR_STR(exclude_accounts, audit_log_exclude_accounts,
1488        PLUGIN_VAR_RQCMDARG,
1489        "Comma separated list of accounts "
1490        "for which events should not be logged.",
1491        audit_log_exclude_accounts_validate,
1492        audit_log_exclude_accounts_update, NULL);
1493 
1494 static
1495 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)1496 audit_log_include_accounts_validate(
1497           MYSQL_THD thd MY_ATTRIBUTE((unused)),
1498           struct st_mysql_sys_var *var MY_ATTRIBUTE((unused)),
1499           void *save,
1500           struct st_mysql_value *value)
1501 {
1502   const char *new_val;
1503   char buf[80];
1504   int len= sizeof(buf);
1505 
1506   if (audit_log_exclude_accounts)
1507     return 1;
1508 
1509   new_val = value->val_str(value, buf, &len);
1510 
1511   *(const char **)(save) = new_val;
1512 
1513   return 0;
1514 }
1515 
1516 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)1517 void audit_log_include_accounts_update(
1518           MYSQL_THD thd MY_ATTRIBUTE((unused)),
1519           struct st_mysql_sys_var *var MY_ATTRIBUTE((unused)),
1520           void *var_ptr MY_ATTRIBUTE((unused)),
1521           const void *save)
1522 {
1523   const char *new_val= *(const char **)(save);
1524 
1525   DBUG_ASSERT(audit_log_exclude_accounts == NULL);
1526 
1527   my_free(audit_log_include_accounts);
1528   audit_log_include_accounts= NULL;
1529 
1530   if (new_val != NULL)
1531   {
1532     audit_log_include_accounts= my_strdup(new_val, MYF(MY_FAE));
1533     audit_log_set_include_accounts(audit_log_include_accounts);
1534   }
1535   else
1536   {
1537     audit_log_set_include_accounts("");
1538   }
1539 }
1540 
1541 static MYSQL_SYSVAR_STR(include_accounts, audit_log_include_accounts,
1542        PLUGIN_VAR_RQCMDARG,
1543        "Comma separated list of accounts for which events should be logged.",
1544        audit_log_include_accounts_validate,
1545        audit_log_include_accounts_update, NULL);
1546 
1547 static
1548 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)1549 audit_log_exclude_commands_validate(
1550           MYSQL_THD thd MY_ATTRIBUTE((unused)),
1551           struct st_mysql_sys_var *var MY_ATTRIBUTE((unused)),
1552           void *save,
1553           struct st_mysql_value *value)
1554 {
1555   const char *new_val;
1556   char buf[80];
1557   int len= sizeof(buf);
1558 
1559   if (audit_log_include_commands)
1560     return 1;
1561 
1562   new_val = value->val_str(value, buf, &len);
1563 
1564   *(const char **)(save) = new_val;
1565 
1566   return 0;
1567 }
1568 
1569 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)1570 void audit_log_exclude_commands_update(
1571           MYSQL_THD thd MY_ATTRIBUTE((unused)),
1572           struct st_mysql_sys_var *var MY_ATTRIBUTE((unused)),
1573           void *var_ptr MY_ATTRIBUTE((unused)),
1574           const void *save)
1575 {
1576   const char *new_val= *(const char **)(save);
1577 
1578   DBUG_ASSERT(audit_log_include_commands == NULL);
1579 
1580   my_free(audit_log_exclude_commands);
1581   audit_log_exclude_commands= NULL;
1582 
1583   if (new_val != NULL)
1584   {
1585     audit_log_exclude_commands= my_strdup(new_val, MYF(MY_FAE));
1586     audit_log_set_exclude_commands(audit_log_exclude_commands);
1587   }
1588   else
1589   {
1590     audit_log_set_exclude_commands("");
1591   }
1592 }
1593 
1594 static MYSQL_SYSVAR_STR(exclude_commands, audit_log_exclude_commands,
1595        PLUGIN_VAR_RQCMDARG,
1596        "Comma separated list of commands "
1597        "for which events should not be logged.",
1598        audit_log_exclude_commands_validate,
1599        audit_log_exclude_commands_update, NULL);
1600 
1601 static
1602 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)1603 audit_log_include_commands_validate(
1604           MYSQL_THD thd MY_ATTRIBUTE((unused)),
1605           struct st_mysql_sys_var *var MY_ATTRIBUTE((unused)),
1606           void *save,
1607           struct st_mysql_value *value)
1608 {
1609   const char *new_val;
1610   char buf[80];
1611   int len= sizeof(buf);
1612 
1613   if (audit_log_exclude_commands)
1614     return 1;
1615 
1616   new_val = value->val_str(value, buf, &len);
1617 
1618   *(const char **)(save) = new_val;
1619 
1620   return 0;
1621 }
1622 
1623 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)1624 void audit_log_include_commands_update(
1625           MYSQL_THD thd MY_ATTRIBUTE((unused)),
1626           struct st_mysql_sys_var *var MY_ATTRIBUTE((unused)),
1627           void *var_ptr MY_ATTRIBUTE((unused)),
1628           const void *save)
1629 {
1630   const char *new_val= *(const char **)(save);
1631 
1632   DBUG_ASSERT(audit_log_exclude_commands == NULL);
1633 
1634   my_free(audit_log_include_commands);
1635   audit_log_include_commands= NULL;
1636 
1637   if (new_val != NULL)
1638   {
1639     audit_log_include_commands= my_strdup(new_val, MYF(MY_FAE));
1640     audit_log_set_include_commands(audit_log_include_commands);
1641   }
1642   else
1643   {
1644     audit_log_set_include_commands("");
1645   }
1646 }
1647 
1648 static MYSQL_SYSVAR_STR(include_commands, audit_log_include_commands,
1649        PLUGIN_VAR_RQCMDARG,
1650        "Comma separated list of commands for which events should be logged.",
1651        audit_log_include_commands_validate,
1652        audit_log_include_commands_update, NULL);
1653 
1654 static MYSQL_THDVAR_STR(local,
1655        PLUGIN_VAR_READONLY | PLUGIN_VAR_MEMALLOC | \
1656        PLUGIN_VAR_NOSYSVAR | PLUGIN_VAR_NOCMDOPT,
1657        "Local store.", NULL, NULL, "");
1658 
1659 static MYSQL_THDVAR_ULONG(local_ptr,
1660        PLUGIN_VAR_READONLY | PLUGIN_VAR_NOSYSVAR | PLUGIN_VAR_NOCMDOPT,
1661        "Local store ptr.", NULL, NULL, 0, 0, ULONG_MAX, 0);
1662 
1663 static struct st_mysql_sys_var* audit_log_system_variables[] =
1664 {
1665   MYSQL_SYSVAR(file),
1666   MYSQL_SYSVAR(policy),
1667   MYSQL_SYSVAR(strategy),
1668   MYSQL_SYSVAR(format),
1669   MYSQL_SYSVAR(buffer_size),
1670   MYSQL_SYSVAR(rotate_on_size),
1671   MYSQL_SYSVAR(rotations),
1672   MYSQL_SYSVAR(flush),
1673   MYSQL_SYSVAR(handler),
1674   MYSQL_SYSVAR(syslog_ident),
1675   MYSQL_SYSVAR(syslog_priority),
1676   MYSQL_SYSVAR(syslog_facility),
1677   MYSQL_SYSVAR(record_buffer),
1678   MYSQL_SYSVAR(exclude_accounts),
1679   MYSQL_SYSVAR(include_accounts),
1680   MYSQL_SYSVAR(exclude_commands),
1681   MYSQL_SYSVAR(include_commands),
1682   MYSQL_SYSVAR(local),
1683   MYSQL_SYSVAR(local_ptr),
1684   NULL
1685 };
1686 
1687 static char thd_local_init_buf[sizeof(audit_log_thd_local)];
1688 
1689 static
audit_log_so_init()1690 void MY_ATTRIBUTE((constructor)) audit_log_so_init()
1691 {
1692   memset(thd_local_init_buf, 1, sizeof(thd_local_init_buf) - 1);
1693   thd_local_init_buf[sizeof(thd_local_init_buf) - 1]= 0;
1694 }
1695 
1696 /*
1697  Return pointer to THD specific data.
1698  */
1699 static
get_thd_local(MYSQL_THD thd)1700 audit_log_thd_local *get_thd_local(MYSQL_THD thd)
1701 {
1702   audit_log_thd_local *local= (audit_log_thd_local *) THDVAR(thd, local_ptr);
1703 
1704   compile_time_assert(sizeof(THDVAR(thd, local_ptr)) >= sizeof(void *));
1705 
1706   if (unlikely(local == NULL))
1707   {
1708     THDVAR_SET(thd, local, thd_local_init_buf);
1709     local= (audit_log_thd_local *) THDVAR(thd, local);
1710     memset(local, 0, sizeof(audit_log_thd_local));
1711     THDVAR(thd, local_ptr)= (ulong) local;
1712   }
1713   return local;
1714 }
1715 
1716 
1717 /*
1718  Allocate and return buffer of given size.
1719  */
1720 static
get_record_buffer(MYSQL_THD thd,size_t size)1721 char *get_record_buffer(MYSQL_THD thd, size_t size)
1722 {
1723   audit_log_thd_local *local= get_thd_local(thd);
1724   char *buf= local->record_buffer;
1725 
1726   if (local->record_buffer_size < size)
1727   {
1728     local->record_buffer_size= size;
1729 
1730     buf = (char *) my_malloc(size, MYF(MY_FAE));
1731     memset(buf, 1, size - 1);
1732     buf[size - 1]= 0;
1733 
1734     THDVAR_SET(thd, record_buffer, buf);
1735 
1736     my_free(buf);
1737 
1738     buf = (char *) THDVAR(thd, record_buffer);
1739     local->record_buffer = buf;
1740   }
1741 
1742   return buf;
1743 }
1744 
1745 
1746 /*
1747   Plugin type-specific descriptor
1748 */
1749 static struct st_mysql_audit audit_log_descriptor=
1750 {
1751   MYSQL_AUDIT_INTERFACE_VERSION,                    /* interface version    */
1752   NULL,                                             /* release_thd function */
1753   audit_log_notify,                                 /* notify function      */
1754   { MYSQL_AUDIT_GENERAL_CLASSMASK |
1755     MYSQL_AUDIT_CONNECTION_CLASSMASK }              /* class mask           */
1756 };
1757 
1758 /*
1759   Plugin status variables for SHOW STATUS
1760 */
1761 
1762 static struct st_mysql_show_var audit_log_status_variables[]=
1763 {
1764   {"Audit_log_buffer_size_overflow",
1765     (char*) &audit_log_buffer_size_overflow,
1766     SHOW_LONG},
1767   { 0, 0, 0}
1768 };
1769 
1770 
1771 /*
1772   Plugin library descriptor
1773 */
1774 
mysql_declare_plugin(audit_log)1775 mysql_declare_plugin(audit_log)
1776 {
1777   MYSQL_AUDIT_PLUGIN,                     /* type                            */
1778   &audit_log_descriptor,                  /* descriptor                      */
1779   "audit_log",                            /* name                            */
1780   "Percona LLC and/or its affiliates.",   /* author                          */
1781   "Audit log",                            /* description                     */
1782   PLUGIN_LICENSE_GPL,
1783   audit_log_plugin_init,                  /* init function (when loaded)     */
1784   audit_log_plugin_deinit,                /* deinit function (when unloaded) */
1785   PLUGIN_VERSION,                         /* version                         */
1786   audit_log_status_variables,             /* status variables                */
1787   audit_log_system_variables,             /* system variables                */
1788   NULL,
1789   0,
1790 }
1791 mysql_declare_plugin_end;
1792 
1793