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-1335 USA */
15
16 #include "mariadb.h"
17 #include "sql_priv.h"
18 #include "mysqld.h"
19 #include "sql_audit.h"
20
21 extern int initialize_audit_plugin(st_plugin_int *plugin);
22 extern int finalize_audit_plugin(st_plugin_int *plugin);
23
24 #ifndef EMBEDDED_LIBRARY
25
26 struct 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
33 unsigned long mysql_global_audit_mask[MYSQL_AUDIT_CLASS_MASK_SIZE];
34
35 static mysql_mutex_t LOCK_audit_mask;
36
37
38 static inline
set_audit_mask(unsigned long * mask,uint event_class)39 void set_audit_mask(unsigned long *mask, uint event_class)
40 {
41 mask[0]= 1;
42 mask[0]<<= event_class;
43 }
44
45 static inline
add_audit_mask(unsigned long * mask,const unsigned long * rhs)46 void add_audit_mask(unsigned long *mask, const unsigned long *rhs)
47 {
48 mask[0]|= rhs[0];
49 }
50
51 static inline
check_audit_mask(const unsigned long * lhs,const unsigned long * rhs)52 bool 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
acquire_plugins(THD * thd,plugin_ref plugin,void * arg)69 static 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 */
mysql_audit_acquire_plugins(THD * thd,ulong * event_class_mask)112 void 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 thd->audit_plugin_version= global_plugin_version;
123 }
124 DBUG_VOID_RETURN;
125 }
126
127
128 /**
129 Check if there were changes in the state of plugins
130 so we need to do the mysql_audit_release asap.
131
132 @param[in] thd
133
134 */
135
mysql_audit_release_required(THD * thd)136 my_bool mysql_audit_release_required(THD *thd)
137 {
138 return thd && (thd->audit_plugin_version != global_plugin_version);
139 }
140
141
142 /**
143 Release any resources associated with the current thd.
144
145 @param[in] thd
146
147 */
148
mysql_audit_release(THD * thd)149 void mysql_audit_release(THD *thd)
150 {
151 plugin_ref *plugins, *plugins_last;
152
153 if (!thd || !(thd->audit_class_plugins.elements))
154 return;
155
156 plugins= (plugin_ref*) thd->audit_class_plugins.buffer;
157 plugins_last= plugins + thd->audit_class_plugins.elements;
158 for (; plugins < plugins_last; plugins++)
159 {
160 st_mysql_audit *data= plugin_data(*plugins, struct st_mysql_audit *);
161
162 /* Check to see if the plugin has a release method */
163 if (!(data->release_thd))
164 continue;
165
166 /* Tell the plugin to release its resources */
167 data->release_thd(thd);
168 }
169
170 /* Now we actually unlock the plugins */
171 plugin_unlock_list(NULL, (plugin_ref*) thd->audit_class_plugins.buffer,
172 thd->audit_class_plugins.elements);
173
174 /* Reset the state of thread values */
175 reset_dynamic(&thd->audit_class_plugins);
176 bzero(thd->audit_class_mask, sizeof(thd->audit_class_mask));
177 thd->audit_plugin_version= -1;
178 }
179
180
181 /**
182 Initialize thd variables used by Audit
183
184 @param[in] thd
185
186 */
187
mysql_audit_init_thd(THD * thd)188 void mysql_audit_init_thd(THD *thd)
189 {
190 bzero(&thd->audit_class_plugins, sizeof(thd->audit_class_plugins));
191 bzero(thd->audit_class_mask, sizeof(thd->audit_class_mask));
192 }
193
194
195 /**
196 Free thd variables used by Audit
197
198 @param[in] thd
199 @param[in] plugin
200 @param[in] arg
201
202 @retval FALSE Always
203 */
204
mysql_audit_free_thd(THD * thd)205 void mysql_audit_free_thd(THD *thd)
206 {
207 mysql_audit_release(thd);
208 DBUG_ASSERT(thd->audit_class_plugins.elements == 0);
209 delete_dynamic(&thd->audit_class_plugins);
210 }
211
212 #ifdef HAVE_PSI_INTERFACE
213 static PSI_mutex_key key_LOCK_audit_mask;
214
215 static PSI_mutex_info all_audit_mutexes[]=
216 {
217 { &key_LOCK_audit_mask, "LOCK_audit_mask", PSI_FLAG_GLOBAL}
218 };
219
init_audit_psi_keys(void)220 static void init_audit_psi_keys(void)
221 {
222 const char* category= "sql";
223 int count;
224
225 if (PSI_server == NULL)
226 return;
227
228 count= array_elements(all_audit_mutexes);
229 PSI_server->register_mutex(category, all_audit_mutexes, count);
230 }
231 #endif /* HAVE_PSI_INTERFACE */
232
233 /**
234 Initialize Audit global variables
235 */
236
mysql_audit_initialize()237 void mysql_audit_initialize()
238 {
239 #ifdef HAVE_PSI_INTERFACE
240 init_audit_psi_keys();
241 #endif
242
243 mysql_mutex_init(key_LOCK_audit_mask, &LOCK_audit_mask, MY_MUTEX_INIT_FAST);
244 bzero(mysql_global_audit_mask, sizeof(mysql_global_audit_mask));
245 }
246
247
248 /**
249 Finalize Audit global variables
250 */
251
mysql_audit_finalize()252 void mysql_audit_finalize()
253 {
254 mysql_mutex_destroy(&LOCK_audit_mask);
255 }
256
257
258 /**
259 Initialize an Audit plug-in
260
261 @param[in] plugin
262
263 @retval FALSE OK
264 @retval TRUE There was an error.
265 */
266
initialize_audit_plugin(st_plugin_int * plugin)267 int initialize_audit_plugin(st_plugin_int *plugin)
268 {
269 st_mysql_audit *data= (st_mysql_audit*) plugin->plugin->info;
270
271 if (!data->event_notify || !data->class_mask[0])
272 {
273 sql_print_error("Plugin '%s' has invalid data.",
274 plugin->name.str);
275 return 1;
276 }
277
278 if (plugin->plugin->init && plugin->plugin->init(NULL))
279 {
280 sql_print_error("Plugin '%s' init function returned error.",
281 plugin->name.str);
282 return 1;
283 }
284
285 /* Make the interface info more easily accessible */
286 plugin->data= plugin->plugin->info;
287
288 /* Add the bits the plugin is interested in to the global mask */
289 mysql_mutex_lock(&LOCK_audit_mask);
290 add_audit_mask(mysql_global_audit_mask, data->class_mask);
291 mysql_mutex_unlock(&LOCK_audit_mask);
292
293 /*
294 Pre-acquire the newly inslalled audit plugin for events that
295 may potentially occur further during INSTALL PLUGIN.
296
297 When audit event is triggered, audit subsystem acquires interested
298 plugins by walking through plugin list. Evidently plugin list
299 iterator protects plugin list by acquiring LOCK_plugin, see
300 plugin_foreach_with_mask().
301
302 On the other hand [UN]INSTALL PLUGIN is acquiring LOCK_plugin
303 rather for a long time.
304
305 When audit event is triggered during [UN]INSTALL PLUGIN, plugin
306 list iterator acquires the same lock (within the same thread)
307 second time.
308
309 This hack should be removed when LOCK_plugin is fixed so it
310 protects only what it supposed to protect.
311
312 See also mysql_install_plugin() and mysql_uninstall_plugin()
313 */
314 THD *thd= current_thd;
315 if (thd)
316 {
317 acquire_plugins(thd, plugin_int_to_ref(plugin), data->class_mask);
318 add_audit_mask(thd->audit_class_mask, data->class_mask);
319 }
320
321 return 0;
322 }
323
324
325 /**
326 Performs a bitwise OR of the installed plugins event class masks
327
328 @param[in] thd
329 @param[in] plugin
330 @param[in] arg
331
332 @retval FALSE always
333 */
calc_class_mask(THD * thd,plugin_ref plugin,void * arg)334 static my_bool calc_class_mask(THD *thd, plugin_ref plugin, void *arg)
335 {
336 st_mysql_audit *data= plugin_data(plugin, struct st_mysql_audit *);
337 if ((data= plugin_data(plugin, struct st_mysql_audit *)))
338 add_audit_mask((unsigned long *) arg, data->class_mask);
339 return 0;
340 }
341
342
343 /**
344 Finalize an Audit plug-in
345
346 @param[in] plugin
347
348 @retval FALSE OK
349 @retval TRUE There was an error.
350 */
finalize_audit_plugin(st_plugin_int * plugin)351 int finalize_audit_plugin(st_plugin_int *plugin)
352 {
353 unsigned long event_class_mask[MYSQL_AUDIT_CLASS_MASK_SIZE];
354
355 if (plugin->plugin->deinit && plugin->plugin->deinit(NULL))
356 {
357 DBUG_PRINT("warning", ("Plugin '%s' deinit function returned error.",
358 plugin->name.str));
359 DBUG_EXECUTE("finalize_audit_plugin", return 1; );
360 }
361
362 plugin->data= NULL;
363 bzero(&event_class_mask, sizeof(event_class_mask));
364
365 /* Iterate through all the installed plugins to create new mask */
366
367 /*
368 LOCK_audit_mask/LOCK_plugin order is not fixed, but serialized with table
369 lock on mysql.plugin.
370 */
371 mysql_mutex_lock(&LOCK_audit_mask);
372 plugin_foreach(current_thd, calc_class_mask, MYSQL_AUDIT_PLUGIN,
373 &event_class_mask);
374
375 /* Set the global audit mask */
376 bmove(mysql_global_audit_mask, event_class_mask, sizeof(event_class_mask));
377 mysql_mutex_unlock(&LOCK_audit_mask);
378
379 return 0;
380 }
381
382
383 /**
384 Dispatches an event by invoking the plugin's event_notify method.
385
386 @param[in] thd
387 @param[in] plugin
388 @param[in] arg
389
390 @retval FALSE always
391 */
392
plugins_dispatch(THD * thd,plugin_ref plugin,void * arg)393 static my_bool plugins_dispatch(THD *thd, plugin_ref plugin, void *arg)
394 {
395 const struct st_mysql_event_generic *event_generic=
396 (const struct st_mysql_event_generic *) arg;
397 st_mysql_audit *data= plugin_data(plugin, struct st_mysql_audit *);
398
399 /* Check to see if the plugin is interested in this event */
400 if (!check_audit_mask(data->class_mask, event_generic->event_class_mask))
401 data->event_notify(thd, event_generic->event_class, event_generic->event);
402
403 return 0;
404 }
405
406
407 /**
408 Distributes an audit event to plug-ins
409
410 @param[in] thd
411 @param[in] event_class
412 @param[in] event
413 */
414
mysql_audit_notify(THD * thd,uint event_class,const void * event)415 void mysql_audit_notify(THD *thd, uint event_class, const void *event)
416 {
417 struct st_mysql_event_generic event_generic;
418 event_generic.event_class= event_class;
419 event_generic.event= event;
420 set_audit_mask(event_generic.event_class_mask, event_class);
421 /*
422 Check if we are doing a slow global dispatch. This event occurs when
423 thd == NULL as it is not associated with any particular thread.
424 */
425 if (unlikely(!thd))
426 {
427 plugin_foreach(thd, plugins_dispatch, MYSQL_AUDIT_PLUGIN, &event_generic);
428 }
429 else
430 {
431 plugin_ref *plugins, *plugins_last;
432
433 mysql_audit_acquire_plugins(thd, event_generic.event_class_mask);
434
435 /* Use the cached set of audit plugins */
436 plugins= (plugin_ref*) thd->audit_class_plugins.buffer;
437 plugins_last= plugins + thd->audit_class_plugins.elements;
438
439 for (; plugins < plugins_last; plugins++)
440 plugins_dispatch(thd, *plugins, &event_generic);
441 }
442 }
443
444
445 #else /* EMBEDDED_LIBRARY */
446
447
mysql_audit_acquire_plugins(THD * thd,ulong * event_class_mask)448 void mysql_audit_acquire_plugins(THD *thd, ulong *event_class_mask)
449 {
450 }
451
452
mysql_audit_initialize()453 void mysql_audit_initialize()
454 {
455 }
456
457
mysql_audit_finalize()458 void mysql_audit_finalize()
459 {
460 }
461
462
initialize_audit_plugin(st_plugin_int * plugin)463 int initialize_audit_plugin(st_plugin_int *plugin)
464 {
465 return 1;
466 }
467
468
finalize_audit_plugin(st_plugin_int * plugin)469 int finalize_audit_plugin(st_plugin_int *plugin)
470 {
471 return 0;
472 }
473
474
mysql_audit_release(THD * thd)475 void mysql_audit_release(THD *thd)
476 {
477 }
478
mysql_audit_init_thd(THD * thd)479 void mysql_audit_init_thd(THD *thd)
480 {
481 }
482
mysql_audit_free_thd(THD * thd)483 void mysql_audit_free_thd(THD *thd)
484 {
485 }
486
487 #endif /* EMBEDDED_LIBRARY */
488