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 | |
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 |
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 |
46 | void add_audit_mask(unsigned long *mask, const unsigned long *rhs) |
47 | { |
48 | mask[0]|= rhs[0]; |
49 | } |
50 | |
51 | static inline |
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 | |
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 | */ |
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 | } |
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 | |
134 | void 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 | |
172 | void 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 | |
189 | void 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 |
197 | static PSI_mutex_key key_LOCK_audit_mask; |
198 | |
199 | static PSI_mutex_info all_audit_mutexes[]= |
200 | { |
201 | { &key_LOCK_audit_mask, "LOCK_audit_mask" , PSI_FLAG_GLOBAL} |
202 | }; |
203 | |
204 | static 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 | |
221 | void 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 | |
236 | void 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 | |
251 | int 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 | */ |
318 | static 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 | */ |
335 | int 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 | |
377 | static 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 | |
399 | void 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 | |
432 | void mysql_audit_acquire_plugins(THD *thd, ulong *event_class_mask) |
433 | { |
434 | } |
435 | |
436 | |
437 | void mysql_audit_initialize() |
438 | { |
439 | } |
440 | |
441 | |
442 | void mysql_audit_finalize() |
443 | { |
444 | } |
445 | |
446 | |
447 | int initialize_audit_plugin(st_plugin_int *plugin) |
448 | { |
449 | return 1; |
450 | } |
451 | |
452 | |
453 | int finalize_audit_plugin(st_plugin_int *plugin) |
454 | { |
455 | return 0; |
456 | } |
457 | |
458 | |
459 | void mysql_audit_release(THD *thd) |
460 | { |
461 | } |
462 | |
463 | void mysql_audit_init_thd(THD *thd) |
464 | { |
465 | } |
466 | |
467 | void mysql_audit_free_thd(THD *thd) |
468 | { |
469 | } |
470 | |
471 | #endif /* EMBEDDED_LIBRARY */ |
472 | |