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, "	" },
241 { '\n', 6, " " },
242 { 0, 0, NULL },
243 { 0, 0, NULL },
244 { '\r', 6, " " },
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, "<" },
267 { '>', 4, ">" },
268 { '&', 5, "&" },
269 { '"', 6, """ },
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