1 /* Copyright (c) 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 <my_global.h>
18 #include <my_sys.h>
19 #include <my_user.h>
20 #include <m_ctype.h>
21 #include <mysql_com.h>
22 #include <hash.h>
23 #include <string.h>
24 #include <stdlib.h>
25
26 #include "filter.h"
27 #include "audit_log.h"
28
29 typedef struct
30 {
31 /* user + '@' + host + '\0' */
32 char name[USERNAME_LENGTH + HOSTNAME_LENGTH + 2];
33 size_t length;
34 } account;
35
36 typedef struct
37 {
38 char name[NAME_LEN + 1];
39 size_t length;
40 } database;
41
42 typedef struct
43 {
44 /* has to be enought to hold one of the com_status_vars names */
45 char name[100];
46 size_t length;
47 } command;
48
49 static HASH include_accounts;
50 static HASH exclude_accounts;
51
52 static HASH include_databases;
53 static HASH exclude_databases;
54
55 static HASH include_commands;
56 static HASH exclude_commands;
57
58 #if defined(HAVE_PSI_INTERFACE)
59
60 static PSI_rwlock_key key_LOCK_account_list;
61 static PSI_rwlock_key key_LOCK_database_list;
62 static PSI_rwlock_key key_LOCK_command_list;
63 static PSI_rwlock_info all_rwlock_list[]=
64 {{ &key_LOCK_account_list, "audit_log_filter::account_list", PSI_FLAG_GLOBAL},
65 { &key_LOCK_database_list, "audit_log_filter::database_list", PSI_FLAG_GLOBAL},
66 { &key_LOCK_account_list, "audit_log_filter::command_list", PSI_FLAG_GLOBAL}};
67
68 #endif
69
70 static mysql_rwlock_t LOCK_account_list;
71 static mysql_rwlock_t LOCK_database_list;
72 static mysql_rwlock_t LOCK_command_list;
73
74 /*
75 Initialize account
76 */
77 static
account_init(account * acc,const char * user,size_t user_length,const char * host,size_t host_length)78 void account_init(account *acc, const char *user, size_t user_length,
79 const char *host, size_t host_length)
80 {
81 assert(user_length + host_length + 2 <= sizeof(acc->name));
82
83 memcpy(acc->name, user, user_length);
84 memcpy(acc->name + user_length + 1, host, host_length);
85 acc->name[user_length]= '@';
86 acc->name[user_length + host_length + 1]= 0;
87 acc->length= user_length + host_length + 1;
88 }
89
90 /*
91 Allocate memory and initialize new account
92 */
93
94 static
account_create(const char * user,size_t user_length,const char * host,size_t host_length)95 account *account_create(const char *user, size_t user_length,
96 const char *host, size_t host_length)
97 {
98 account *acc= (account *) my_malloc(key_memory_audit_log_accounts,
99 sizeof(account), MYF(MY_FAE));
100
101 account_init(acc, user, user_length, host, host_length);
102
103 return acc;
104 }
105
106 /*
107 Get account key
108 */
109 static
account_get_key(const account * acc,size_t * length,my_bool not_used MY_ATTRIBUTE ((unused)))110 uchar *account_get_key(const account *acc, size_t *length,
111 my_bool not_used MY_ATTRIBUTE((unused)))
112 {
113 *length= acc->length;
114 return (uchar*) acc->name;
115 }
116
117 /*
118 Initialize database
119 */
120 static
database_init(database * db,const char * name,size_t length)121 void database_init(database *db, const char *name, size_t length)
122 {
123 assert(length + 1 <= sizeof(db->name));
124
125 memcpy(db->name, name, length);
126 db->name[length]= 0;
127 db->length= length;
128 }
129
130 /*
131 Allocate memory and initialize new database
132 */
133
134 static
database_create(const char * name,size_t length)135 database *database_create(const char *name, size_t length)
136 {
137 database *db= (database *) my_malloc(key_memory_audit_log_databases,
138 sizeof(database), MYF(MY_FAE));
139
140 database_init(db, name, length);
141
142 return db;
143 }
144
145 /*
146 Get database key
147 */
148 static
database_get_key(const database * db,size_t * length,my_bool not_used MY_ATTRIBUTE ((unused)))149 uchar *database_get_key(const database *db, size_t *length,
150 my_bool not_used MY_ATTRIBUTE((unused)))
151 {
152 *length= db->length;
153 return (uchar*) db->name;
154 }
155
156 /*
157 Initialize command
158 */
159 static
command_init(command * cmd,const char * name,size_t length)160 void command_init(command *cmd, const char *name, size_t length)
161 {
162 assert(length + 1 <= sizeof(cmd->name));
163
164 memcpy(cmd->name, name, length);
165 cmd->name[length]= 0;
166 cmd->length= length;
167 }
168
169 /*
170 Allocate memory and initialize new command
171 */
172
173 static
command_create(const char * name,size_t length)174 command *command_create(const char *name, size_t length)
175 {
176 command *cmd= (command *) my_malloc(key_memory_audit_log_commands,
177 sizeof(command), MYF(MY_FAE));
178
179 command_init(cmd, name, length);
180
181 return cmd;
182 }
183
184 /*
185 Get command key
186 */
187 static
command_get_key(const command * acc,size_t * length,my_bool not_used MY_ATTRIBUTE ((unused)))188 uchar *command_get_key(const command *acc, size_t *length,
189 my_bool not_used MY_ATTRIBUTE((unused)))
190 {
191 *length= acc->length;
192 return (uchar*) acc->name;
193 }
194
195 /*
196 Remove enclosing quotes from string if any.
197 */
198 static
unquote_string(char * string,size_t * string_length)199 void unquote_string(char *string, size_t *string_length)
200 {
201 if (string[0] == '\'' && string[*string_length - 1] == '\'')
202 {
203 *string_length-= 2;
204 memmove(string, string + 1, *string_length);
205 string[*string_length]= 0;
206 }
207 }
208
209 /*
210 Parse comma-separated list of accounts and add it into account list.
211 Empty user name is allowed.
212 */
213 static
account_list_from_string(HASH * hash,const char * string)214 void account_list_from_string(HASH *hash, const char *string)
215 {
216 char *string_copy= my_strdup(PSI_NOT_INSTRUMENTED, string, MYF(MY_FAE));
217 char *entry= string_copy;
218 int string_length= strlen(string_copy);
219 char user[USERNAME_LENGTH + 1], host[HOSTNAME_LENGTH + 1];
220 size_t user_length, host_length;
221
222 my_hash_reset(hash);
223
224 while (entry - string_copy < string_length)
225 {
226 size_t entry_length= 0;
227 my_bool quote= FALSE;
228 account *acc;
229
230 while (*entry == ' ')
231 entry++;
232
233 entry_length= 0;
234 while (((entry[entry_length] != ' ' && entry[entry_length] != ',') || quote)
235 && entry[entry_length] != 0)
236 {
237 if (entry[entry_length] == '\'')
238 quote= !quote;
239 entry_length++;
240 }
241
242 entry[entry_length]= 0;
243
244 parse_user(entry, entry_length, user, &user_length, host, &host_length);
245 unquote_string(user, &user_length);
246 unquote_string(host, &host_length);
247 my_casedn_str(system_charset_info, host);
248
249 acc= account_create(user, user_length, host, host_length);
250 if (my_hash_insert(hash, (uchar*) acc))
251 my_free(acc);
252
253 entry+= entry_length + 1;
254 }
255
256 my_free(string_copy);
257 }
258
259 static
database_list_from_string(HASH * hash,const char * string)260 void database_list_from_string(HASH *hash, const char *string)
261 {
262 const char *entry= string;
263
264 my_hash_reset(hash);
265
266 while (*entry)
267 {
268 size_t entry_length= 0;
269 my_bool quote= FALSE;
270 char name[NAME_LEN + 1];
271 size_t name_length= 0;
272
273 while (*entry == ' ')
274 entry++;
275
276 while (((entry[entry_length] != ' ' && entry[entry_length] != ',') || quote)
277 && entry[entry_length] != 0)
278 {
279 if (quote && entry[entry_length] == '`' && entry[entry_length + 1] == '`')
280 {
281 name[name_length++]= '`';
282 entry_length+= 1;
283 }
284 else if (entry[entry_length] == '`')
285 quote= !quote;
286 else if (name_length < sizeof(name))
287 name[name_length++]= entry[entry_length];
288 entry_length++;
289 }
290
291 if (name_length > 0)
292 {
293 database *db;
294 name[name_length]= 0;
295 db= database_create(name, name_length);
296 if (my_hash_insert(hash, (uchar*) db))
297 my_free(db);
298 }
299
300 entry+= entry_length;
301
302 if (*entry == ',')
303 entry++;
304 }
305 }
306
307 /*
308 Parse comma-separated list of command and add it into command hash.
309 */
310 static
command_list_from_string(HASH * hash,const char * string)311 void command_list_from_string(HASH *hash, const char *string)
312 {
313 const char *entry= string;
314
315 my_hash_reset(hash);
316
317 while (*entry)
318 {
319 size_t len= 0;
320
321 while (*entry == ' ' || *entry == ',')
322 entry++;
323
324 while (entry[len] != ' ' && entry[len] != ',' && entry[len] != 0)
325 len++;
326
327 if (len > 0)
328 {
329 command *cmd= command_create(entry, len);
330 my_casedn_str(&my_charset_utf8_general_ci, cmd->name);
331 if (my_hash_insert(hash, (uchar*) cmd))
332 my_free(cmd);
333 }
334
335 entry+= len;
336 }
337 }
338
339 /* public interface */
340
audit_log_filter_init()341 void audit_log_filter_init()
342 {
343 #ifdef HAVE_PSI_INTERFACE
344 mysql_rwlock_register(AUDIT_LOG_PSI_CATEGORY, all_rwlock_list,
345 array_elements(all_rwlock_list));
346 #endif /* HAVE_PSI_INTERFACE */
347 mysql_rwlock_init(key_LOCK_account_list, &LOCK_account_list);
348 mysql_rwlock_init(key_LOCK_database_list, &LOCK_database_list);
349 mysql_rwlock_init(key_LOCK_command_list, &LOCK_command_list);
350
351 my_hash_init(&include_accounts, &my_charset_bin,
352 20, 0, 0,
353 (my_hash_get_key) account_get_key,
354 my_free, HASH_UNIQUE, key_memory_audit_log_accounts);
355
356 my_hash_init(&exclude_accounts, &my_charset_bin,
357 20, 0, 0,
358 (my_hash_get_key) account_get_key,
359 my_free, HASH_UNIQUE, key_memory_audit_log_accounts);
360
361 my_hash_init(&include_databases, &my_charset_bin,
362 20, 0, 0,
363 (my_hash_get_key) database_get_key,
364 my_free, HASH_UNIQUE, key_memory_audit_log_databases);
365
366 my_hash_init(&exclude_databases, &my_charset_bin,
367 20, 0, 0,
368 (my_hash_get_key) database_get_key,
369 my_free, HASH_UNIQUE, key_memory_audit_log_databases);
370
371 my_hash_init(&include_commands, &my_charset_bin,
372 20, 0, 0,
373 (my_hash_get_key) command_get_key,
374 my_free, HASH_UNIQUE, key_memory_audit_log_commands);
375
376 my_hash_init(&exclude_commands, &my_charset_bin,
377 20, 0, 0,
378 (my_hash_get_key) command_get_key,
379 my_free, HASH_UNIQUE, key_memory_audit_log_commands);
380 }
381
audit_log_filter_destroy()382 void audit_log_filter_destroy()
383 {
384 my_hash_free(&include_accounts);
385 my_hash_free(&exclude_accounts);
386 my_hash_free(&include_databases);
387 my_hash_free(&exclude_databases);
388 my_hash_free(&include_commands);
389 my_hash_free(&exclude_commands);
390 mysql_rwlock_destroy(&LOCK_account_list);
391 mysql_rwlock_destroy(&LOCK_database_list);
392 mysql_rwlock_destroy(&LOCK_account_list);
393 mysql_rwlock_destroy(&LOCK_command_list);
394 }
395
396 /*
397 Parse and store the list of included accounts.
398 */
audit_log_set_include_accounts(const char * val)399 void audit_log_set_include_accounts(const char *val)
400 {
401 mysql_rwlock_wrlock(&LOCK_account_list);
402 account_list_from_string(&include_accounts, val);
403 mysql_rwlock_unlock(&LOCK_account_list);
404 }
405
406 /*
407 Parse and store the list of excluded accounts.
408 */
audit_log_set_exclude_accounts(const char * val)409 void audit_log_set_exclude_accounts(const char *val)
410 {
411 mysql_rwlock_wrlock(&LOCK_account_list);
412 account_list_from_string(&exclude_accounts, val);
413 mysql_rwlock_unlock(&LOCK_account_list);
414 }
415
416 /*
417 Check if account has to be included.
418 */
audit_log_check_account_included(const char * user,size_t user_length,const char * host,size_t host_length)419 my_bool audit_log_check_account_included(const char *user, size_t user_length,
420 const char *host, size_t host_length)
421 {
422 account acc;
423 my_bool res;
424
425 account_init(&acc, user, user_length, host, host_length);
426
427 if (acc.length == 0)
428 return FALSE;
429
430 mysql_rwlock_rdlock(&LOCK_account_list);
431
432 res= my_hash_search(&include_accounts,
433 (const uchar*) acc.name, acc.length) != NULL;
434
435 mysql_rwlock_unlock(&LOCK_account_list);
436 return res;
437 }
438
439 /*
440 Check if account has to be excluded.
441 */
audit_log_check_account_excluded(const char * user,size_t user_length,const char * host,size_t host_length)442 my_bool audit_log_check_account_excluded(const char *user, size_t user_length,
443 const char *host, size_t host_length)
444 {
445 account acc;
446 my_bool res;
447
448 account_init(&acc, user, user_length, host, host_length);
449
450 if (acc.length == 0)
451 return FALSE;
452
453 mysql_rwlock_rdlock(&LOCK_account_list);
454
455 res= my_hash_search(&exclude_accounts,
456 (const uchar*) acc.name, acc.length) != NULL;
457 mysql_rwlock_unlock(&LOCK_account_list);
458 return res;
459 }
460
461 /*
462 Parse and store the list of included databases.
463 */
audit_log_set_include_databases(const char * val)464 void audit_log_set_include_databases(const char *val)
465 {
466 mysql_rwlock_wrlock(&LOCK_database_list);
467 database_list_from_string(&include_databases, val);
468 mysql_rwlock_unlock(&LOCK_database_list);
469 }
470
471 /*
472 Parse and store the list of excluded databases.
473 */
audit_log_set_exclude_databases(const char * val)474 void audit_log_set_exclude_databases(const char *val)
475 {
476 mysql_rwlock_wrlock(&LOCK_database_list);
477 database_list_from_string(&exclude_databases, val);
478 mysql_rwlock_unlock(&LOCK_database_list);
479 }
480
481 /*
482 Check if database has to be included.
483 */
audit_log_check_database_included(const char * name,size_t length)484 my_bool audit_log_check_database_included(const char *name, size_t length)
485 {
486 my_bool res;
487
488 if (length == 0)
489 return FALSE;
490
491 mysql_rwlock_rdlock(&LOCK_database_list);
492
493 res= my_hash_search(&include_databases,
494 (const uchar*) name, length) != NULL;
495
496 mysql_rwlock_unlock(&LOCK_database_list);
497 return res;
498 }
499
500 /*
501 Check if database has to be excluded.
502 */
audit_log_check_database_excluded(const char * name,size_t length)503 my_bool audit_log_check_database_excluded(const char *name, size_t length)
504 {
505 my_bool res;
506
507 if (length == 0)
508 return FALSE;
509
510 mysql_rwlock_rdlock(&LOCK_database_list);
511
512 res= my_hash_search(&exclude_databases,
513 (const uchar*) name, length) != NULL;
514 mysql_rwlock_unlock(&LOCK_database_list);
515 return res;
516 }
517
518
519 /*
520 Parse and store the list of included commands.
521 */
audit_log_set_include_commands(const char * val)522 void audit_log_set_include_commands(const char *val)
523 {
524 mysql_rwlock_wrlock(&LOCK_command_list);
525 command_list_from_string(&include_commands, val);
526 mysql_rwlock_unlock(&LOCK_command_list);
527 }
528
529 /*
530 Parse and store the list of excluded commands.
531 */
audit_log_set_exclude_commands(const char * val)532 void audit_log_set_exclude_commands(const char *val)
533 {
534 mysql_rwlock_wrlock(&LOCK_command_list);
535 command_list_from_string(&exclude_commands, val);
536 mysql_rwlock_unlock(&LOCK_command_list);
537 }
538
539 /*
540 Check if command has to be included.
541 */
audit_log_check_command_included(const char * name,size_t length)542 my_bool audit_log_check_command_included(const char *name, size_t length)
543 {
544 my_bool res;
545
546 if (length == 0)
547 return FALSE;
548
549 mysql_rwlock_rdlock(&LOCK_command_list);
550 res= my_hash_search(&include_commands, (const uchar*) name, length) != NULL;
551 mysql_rwlock_unlock(&LOCK_command_list);
552
553 return res;
554 }
555
556 /*
557 Check if command has to be excluded.
558 */
audit_log_check_command_excluded(const char * name,size_t length)559 my_bool audit_log_check_command_excluded(const char *name, size_t length)
560 {
561 my_bool res;
562
563 if (length == 0)
564 return FALSE;
565
566 mysql_rwlock_rdlock(&LOCK_command_list);
567 res= my_hash_search(&exclude_commands, (const uchar*) name, length) != NULL;
568 mysql_rwlock_unlock(&LOCK_command_list);
569
570 return res;
571 }
572