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