| 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 | |