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