1/* Copyright (c) 2007, 2013, Oracle and/or its affiliates. All rights reserved.
2
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License as published by
5 the Free Software Foundation; version 2 of the License.
6
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY; without even the implied warranty of
9 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 GNU General Public License for more details.
11
12 You should have received a copy of the GNU General Public License
13 along with this program; if not, write to the Free Software
14 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
15
16#include "mariadb.h"
17#include "sql_priv.h"
18#include "mysqld.h"
19#include "sql_audit.h"
20
21extern int initialize_audit_plugin(st_plugin_int *plugin);
22extern int finalize_audit_plugin(st_plugin_int *plugin);
23
24#ifndef EMBEDDED_LIBRARY
25
26struct st_mysql_event_generic
27{
28 unsigned long event_class_mask[MYSQL_AUDIT_CLASS_MASK_SIZE];
29 unsigned int event_class;
30 const void *event;
31};
32
33unsigned long mysql_global_audit_mask[MYSQL_AUDIT_CLASS_MASK_SIZE];
34
35static mysql_mutex_t LOCK_audit_mask;
36
37
38static inline
39void set_audit_mask(unsigned long *mask, uint event_class)
40{
41 mask[0]= 1;
42 mask[0]<<= event_class;
43}
44
45static inline
46void add_audit_mask(unsigned long *mask, const unsigned long *rhs)
47{
48 mask[0]|= rhs[0];
49}
50
51static inline
52bool check_audit_mask(const unsigned long *lhs,
53 const unsigned long *rhs)
54{
55 return !(lhs[0] & rhs[0]);
56}
57
58
59/**
60 Acquire and lock any additional audit plugins as required
61
62 @param[in] thd
63 @param[in] plugin
64 @param[in] arg
65
66 @retval FALSE Always
67*/
68
69static my_bool acquire_plugins(THD *thd, plugin_ref plugin, void *arg)
70{
71 ulong *event_class_mask= (ulong*) arg;
72 st_mysql_audit *data= plugin_data(plugin, struct st_mysql_audit *);
73
74 /* Check if this plugin is interested in the event */
75 if (check_audit_mask(data->class_mask, event_class_mask))
76 return 0;
77
78 /*
79 Check if this plugin may already be registered. This will fail to
80 acquire a newly installed plugin on a specific corner case where
81 one or more event classes already in use by the calling thread
82 are an event class of which the audit plugin has interest.
83 */
84 if (!check_audit_mask(data->class_mask, thd->audit_class_mask))
85 return 0;
86
87 /* Check if we need to initialize the array of acquired plugins */
88 if (unlikely(!thd->audit_class_plugins.buffer))
89 {
90 /* specify some reasonable initialization defaults */
91 my_init_dynamic_array(&thd->audit_class_plugins,
92 sizeof(plugin_ref), 16, 16, MYF(0));
93 }
94
95 /* lock the plugin and add it to the list */
96 plugin= my_plugin_lock(NULL, plugin);
97 insert_dynamic(&thd->audit_class_plugins, (uchar*) &plugin);
98
99 return 0;
100}
101
102
103/**
104 @brief Acquire audit plugins
105
106 @param[in] thd MySQL thread handle
107 @param[in] event_class Audit event class
108
109 @details Ensure that audit plugins interested in given event
110 class are locked by current thread.
111*/
112void mysql_audit_acquire_plugins(THD *thd, ulong *event_class_mask)
113{
114 DBUG_ENTER("mysql_audit_acquire_plugins");
115 DBUG_ASSERT(thd);
116 DBUG_ASSERT(!check_audit_mask(mysql_global_audit_mask, event_class_mask));
117
118 if (check_audit_mask(thd->audit_class_mask, event_class_mask))
119 {
120 plugin_foreach(thd, acquire_plugins, MYSQL_AUDIT_PLUGIN, event_class_mask);
121 add_audit_mask(thd->audit_class_mask, event_class_mask);
122 }
123 DBUG_VOID_RETURN;
124}
125
126
127/**
128 Release any resources associated with the current thd.
129
130 @param[in] thd
131
132*/
133
134void mysql_audit_release(THD *thd)
135{
136 plugin_ref *plugins, *plugins_last;
137
138 if (!thd || !(thd->audit_class_plugins.elements))
139 return;
140
141 plugins= (plugin_ref*) thd->audit_class_plugins.buffer;
142 plugins_last= plugins + thd->audit_class_plugins.elements;
143 for (; plugins < plugins_last; plugins++)
144 {
145 st_mysql_audit *data= plugin_data(*plugins, struct st_mysql_audit *);
146
147 /* Check to see if the plugin has a release method */
148 if (!(data->release_thd))
149 continue;
150
151 /* Tell the plugin to release its resources */
152 data->release_thd(thd);
153 }
154
155 /* Now we actually unlock the plugins */
156 plugin_unlock_list(NULL, (plugin_ref*) thd->audit_class_plugins.buffer,
157 thd->audit_class_plugins.elements);
158
159 /* Reset the state of thread values */
160 reset_dynamic(&thd->audit_class_plugins);
161 bzero(thd->audit_class_mask, sizeof(thd->audit_class_mask));
162}
163
164
165/**
166 Initialize thd variables used by Audit
167
168 @param[in] thd
169
170*/
171
172void mysql_audit_init_thd(THD *thd)
173{
174 bzero(&thd->audit_class_plugins, sizeof(thd->audit_class_plugins));
175 bzero(thd->audit_class_mask, sizeof(thd->audit_class_mask));
176}
177
178
179/**
180 Free thd variables used by Audit
181
182 @param[in] thd
183 @param[in] plugin
184 @param[in] arg
185
186 @retval FALSE Always
187*/
188
189void mysql_audit_free_thd(THD *thd)
190{
191 mysql_audit_release(thd);
192 DBUG_ASSERT(thd->audit_class_plugins.elements == 0);
193 delete_dynamic(&thd->audit_class_plugins);
194}
195
196#ifdef HAVE_PSI_INTERFACE
197static PSI_mutex_key key_LOCK_audit_mask;
198
199static PSI_mutex_info all_audit_mutexes[]=
200{
201 { &key_LOCK_audit_mask, "LOCK_audit_mask", PSI_FLAG_GLOBAL}
202};
203
204static void init_audit_psi_keys(void)
205{
206 const char* category= "sql";
207 int count;
208
209 if (PSI_server == NULL)
210 return;
211
212 count= array_elements(all_audit_mutexes);
213 PSI_server->register_mutex(category, all_audit_mutexes, count);
214}
215#endif /* HAVE_PSI_INTERFACE */
216
217/**
218 Initialize Audit global variables
219*/
220
221void mysql_audit_initialize()
222{
223#ifdef HAVE_PSI_INTERFACE
224 init_audit_psi_keys();
225#endif
226
227 mysql_mutex_init(key_LOCK_audit_mask, &LOCK_audit_mask, MY_MUTEX_INIT_FAST);
228 bzero(mysql_global_audit_mask, sizeof(mysql_global_audit_mask));
229}
230
231
232/**
233 Finalize Audit global variables
234*/
235
236void mysql_audit_finalize()
237{
238 mysql_mutex_destroy(&LOCK_audit_mask);
239}
240
241
242/**
243 Initialize an Audit plug-in
244
245 @param[in] plugin
246
247 @retval FALSE OK
248 @retval TRUE There was an error.
249*/
250
251int initialize_audit_plugin(st_plugin_int *plugin)
252{
253 st_mysql_audit *data= (st_mysql_audit*) plugin->plugin->info;
254
255 if (!data->event_notify || !data->class_mask[0])
256 {
257 sql_print_error("Plugin '%s' has invalid data.",
258 plugin->name.str);
259 return 1;
260 }
261
262 if (plugin->plugin->init && plugin->plugin->init(NULL))
263 {
264 sql_print_error("Plugin '%s' init function returned error.",
265 plugin->name.str);
266 return 1;
267 }
268
269 /* Make the interface info more easily accessible */
270 plugin->data= plugin->plugin->info;
271
272 /* Add the bits the plugin is interested in to the global mask */
273 mysql_mutex_lock(&LOCK_audit_mask);
274 add_audit_mask(mysql_global_audit_mask, data->class_mask);
275 mysql_mutex_unlock(&LOCK_audit_mask);
276
277 /*
278 Pre-acquire the newly inslalled audit plugin for events that
279 may potentially occur further during INSTALL PLUGIN.
280
281 When audit event is triggered, audit subsystem acquires interested
282 plugins by walking through plugin list. Evidently plugin list
283 iterator protects plugin list by acquiring LOCK_plugin, see
284 plugin_foreach_with_mask().
285
286 On the other hand [UN]INSTALL PLUGIN is acquiring LOCK_plugin
287 rather for a long time.
288
289 When audit event is triggered during [UN]INSTALL PLUGIN, plugin
290 list iterator acquires the same lock (within the same thread)
291 second time.
292
293 This hack should be removed when LOCK_plugin is fixed so it
294 protects only what it supposed to protect.
295
296 See also mysql_install_plugin() and mysql_uninstall_plugin()
297 */
298 THD *thd= current_thd;
299 if (thd)
300 {
301 acquire_plugins(thd, plugin_int_to_ref(plugin), data->class_mask);
302 add_audit_mask(thd->audit_class_mask, data->class_mask);
303 }
304
305 return 0;
306}
307
308
309/**
310 Performs a bitwise OR of the installed plugins event class masks
311
312 @param[in] thd
313 @param[in] plugin
314 @param[in] arg
315
316 @retval FALSE always
317*/
318static my_bool calc_class_mask(THD *thd, plugin_ref plugin, void *arg)
319{
320 st_mysql_audit *data= plugin_data(plugin, struct st_mysql_audit *);
321 if ((data= plugin_data(plugin, struct st_mysql_audit *)))
322 add_audit_mask((unsigned long *) arg, data->class_mask);
323 return 0;
324}
325
326
327/**
328 Finalize an Audit plug-in
329
330 @param[in] plugin
331
332 @retval FALSE OK
333 @retval TRUE There was an error.
334*/
335int finalize_audit_plugin(st_plugin_int *plugin)
336{
337 unsigned long event_class_mask[MYSQL_AUDIT_CLASS_MASK_SIZE];
338
339 if (plugin->plugin->deinit && plugin->plugin->deinit(NULL))
340 {
341 DBUG_PRINT("warning", ("Plugin '%s' deinit function returned error.",
342 plugin->name.str));
343 DBUG_EXECUTE("finalize_audit_plugin", return 1; );
344 }
345
346 plugin->data= NULL;
347 bzero(&event_class_mask, sizeof(event_class_mask));
348
349 /* Iterate through all the installed plugins to create new mask */
350
351 /*
352 LOCK_audit_mask/LOCK_plugin order is not fixed, but serialized with table
353 lock on mysql.plugin.
354 */
355 mysql_mutex_lock(&LOCK_audit_mask);
356 plugin_foreach(current_thd, calc_class_mask, MYSQL_AUDIT_PLUGIN,
357 &event_class_mask);
358
359 /* Set the global audit mask */
360 bmove(mysql_global_audit_mask, event_class_mask, sizeof(event_class_mask));
361 mysql_mutex_unlock(&LOCK_audit_mask);
362
363 return 0;
364}
365
366
367/**
368 Dispatches an event by invoking the plugin's event_notify method.
369
370 @param[in] thd
371 @param[in] plugin
372 @param[in] arg
373
374 @retval FALSE always
375*/
376
377static my_bool plugins_dispatch(THD *thd, plugin_ref plugin, void *arg)
378{
379 const struct st_mysql_event_generic *event_generic=
380 (const struct st_mysql_event_generic *) arg;
381 st_mysql_audit *data= plugin_data(plugin, struct st_mysql_audit *);
382
383 /* Check to see if the plugin is interested in this event */
384 if (!check_audit_mask(data->class_mask, event_generic->event_class_mask))
385 data->event_notify(thd, event_generic->event_class, event_generic->event);
386
387 return 0;
388}
389
390
391/**
392 Distributes an audit event to plug-ins
393
394 @param[in] thd
395 @param[in] event_class
396 @param[in] event
397*/
398
399void mysql_audit_notify(THD *thd, uint event_class, const void *event)
400{
401 struct st_mysql_event_generic event_generic;
402 event_generic.event_class= event_class;
403 event_generic.event= event;
404 set_audit_mask(event_generic.event_class_mask, event_class);
405 /*
406 Check if we are doing a slow global dispatch. This event occurs when
407 thd == NULL as it is not associated with any particular thread.
408 */
409 if (unlikely(!thd))
410 {
411 plugin_foreach(thd, plugins_dispatch, MYSQL_AUDIT_PLUGIN, &event_generic);
412 }
413 else
414 {
415 plugin_ref *plugins, *plugins_last;
416
417 mysql_audit_acquire_plugins(thd, event_generic.event_class_mask);
418
419 /* Use the cached set of audit plugins */
420 plugins= (plugin_ref*) thd->audit_class_plugins.buffer;
421 plugins_last= plugins + thd->audit_class_plugins.elements;
422
423 for (; plugins < plugins_last; plugins++)
424 plugins_dispatch(thd, *plugins, &event_generic);
425 }
426}
427
428
429#else /* EMBEDDED_LIBRARY */
430
431
432void mysql_audit_acquire_plugins(THD *thd, ulong *event_class_mask)
433{
434}
435
436
437void mysql_audit_initialize()
438{
439}
440
441
442void mysql_audit_finalize()
443{
444}
445
446
447int initialize_audit_plugin(st_plugin_int *plugin)
448{
449 return 1;
450}
451
452
453int finalize_audit_plugin(st_plugin_int *plugin)
454{
455 return 0;
456}
457
458
459void mysql_audit_release(THD *thd)
460{
461}
462
463void mysql_audit_init_thd(THD *thd)
464{
465}
466
467void mysql_audit_free_thd(THD *thd)
468{
469}
470
471#endif /* EMBEDDED_LIBRARY */
472