550 lines
16 KiB
C++
550 lines
16 KiB
C++
/* Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License, version 2.0,
|
|
as published by the Free Software Foundation.
|
|
|
|
This program is also distributed with certain software (including
|
|
but not limited to OpenSSL) that is licensed under separate terms,
|
|
as designated in a particular file or component or in included license
|
|
documentation. The authors of MySQL hereby grant you an additional
|
|
permission to link the program and your derivative works with the
|
|
separately licensed software that they have included with MySQL.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License, version 2.0, for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
|
|
|
|
/**
|
|
@brief
|
|
|
|
audit_api_message_emit component exposes audit_log_message UDF that
|
|
generates MYSQL_AUDIT_MESSAGE_USER event of the MYSQL_AUDIT_MESSAGE_CLASS
|
|
class.
|
|
|
|
All installed audit plugins that subscribe MYSQL_AUDIT_MESSAGE_USER event
|
|
will receive this event.
|
|
*/
|
|
|
|
#include <ctype.h>
|
|
#include <my_dbug.h>
|
|
#include <my_macros.h>
|
|
#include <mysql/components/component_implementation.h>
|
|
#include <mysql/components/my_service.h>
|
|
#include <mysql/components/service_implementation.h>
|
|
#include <mysql/components/services/audit_api_message_service.h>
|
|
#include <mysql/components/services/udf_registration.h>
|
|
#include <mysql/service_plugin_registry.h>
|
|
#include <mysql_com.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/types.h>
|
|
#include <algorithm>
|
|
#include <cstdarg>
|
|
#include <map>
|
|
#include <memory>
|
|
#include <string>
|
|
|
|
REQUIRES_SERVICE_PLACEHOLDER(udf_registration);
|
|
REQUIRES_SERVICE_PLACEHOLDER(mysql_audit_api_message);
|
|
|
|
/**
|
|
@class IError_handler
|
|
|
|
Error handling interface.
|
|
*/
|
|
class IError_handler {
|
|
public:
|
|
/**
|
|
Virtual destructor.
|
|
*/
|
|
virtual ~IError_handler() {}
|
|
/**
|
|
Error reporting method.
|
|
|
|
@param message Error message.
|
|
*/
|
|
virtual void error(const char *message, ...) = 0;
|
|
};
|
|
|
|
/**
|
|
Argument validation function prototype.
|
|
|
|
@param handler Error reporting handler.
|
|
@param arg Argument value pointer.
|
|
@param length Argument value length.
|
|
@param arg_pos Argument pos.
|
|
*/
|
|
typedef bool (*validate_function)(IError_handler &handler, const char *arg,
|
|
unsigned long length, size_t arg_pos);
|
|
|
|
/**
|
|
Check, whether the argument is not null pointer.
|
|
*/
|
|
static bool not_null(IError_handler &handler, const char *arg,
|
|
unsigned long length MY_ATTRIBUTE((unused)),
|
|
size_t arg_pos) {
|
|
if (arg == nullptr) {
|
|
handler.error("Argument cannot be NULL [%d].", arg_pos);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
Structure used for argument type validation.
|
|
*/
|
|
struct Arg_type {
|
|
/**
|
|
Expected argument type.
|
|
*/
|
|
Item_result type;
|
|
/**
|
|
Custom argument validation function.
|
|
*/
|
|
validate_function validator;
|
|
};
|
|
|
|
/**
|
|
Obligatory UDF parameters.
|
|
*/
|
|
Arg_type audit_log_primary_args[] = {{STRING_RESULT, not_null},
|
|
{STRING_RESULT, not_null},
|
|
{STRING_RESULT, not_null}};
|
|
/**
|
|
Key value string parameters.
|
|
*/
|
|
Arg_type audit_log_key_value_string_args[] = {{STRING_RESULT, not_null},
|
|
{STRING_RESULT, nullptr}};
|
|
/**
|
|
Key value numeric parameters.
|
|
*/
|
|
Arg_type audit_log_key_value_int_args[] = {{STRING_RESULT, not_null},
|
|
{INT_RESULT, nullptr}};
|
|
/**
|
|
Argument definition structure.
|
|
*/
|
|
struct Arg_def {
|
|
/**
|
|
Argument array pointer.
|
|
*/
|
|
Arg_type *args;
|
|
/**
|
|
Number of arguments in the array.
|
|
*/
|
|
size_t count;
|
|
};
|
|
|
|
/**
|
|
Obligatory arguments definition (component, producer, message).
|
|
*/
|
|
Arg_def audit_log_primary_args_def[] = {
|
|
{audit_log_primary_args, array_elements(audit_log_primary_args)}};
|
|
|
|
/**
|
|
Optional arguments defition (key, value).
|
|
*/
|
|
Arg_def audit_log_extra_args_def[] = {
|
|
{audit_log_key_value_string_args,
|
|
array_elements(audit_log_key_value_string_args)},
|
|
{audit_log_key_value_int_args,
|
|
array_elements(audit_log_key_value_int_args)}};
|
|
|
|
/**
|
|
Max argument count of all definitions.
|
|
|
|
@param arg_def Argument definitions.
|
|
@param arg_def_size Argument definitions size.
|
|
|
|
@return Max argument count.
|
|
*/
|
|
size_t max_arg_count(Arg_def *arg_def, size_t arg_def_size) {
|
|
size_t max = 0;
|
|
|
|
while (arg_def_size-- > 0) {
|
|
max = std::max(arg_def++->count, max);
|
|
}
|
|
|
|
return max;
|
|
}
|
|
|
|
/**
|
|
Check, whether specified arguments match the definitions.
|
|
|
|
@param handler Error reporting handler.
|
|
@param arg_count Number of arguments.
|
|
@param arg_type Argument type array.
|
|
@param arg_def Argument definitions.
|
|
@param arg_def_size Argument definitions size.
|
|
@param args UDF arguments.
|
|
@param arg_lengths UDF argument lengths.
|
|
@param strict_arg_count Strictly check provided argument count. If this is
|
|
set to false, if the provided argument count is
|
|
greater, this does not return error.
|
|
|
|
@return
|
|
@retval -1 None of the argument definition was matched.
|
|
@retval >=0 n-th argument definition was matched.
|
|
*/
|
|
static int arg_check(IError_handler &handler, unsigned int arg_count,
|
|
Item_result *arg_type, Arg_def *arg_def,
|
|
size_t arg_def_size, char **args,
|
|
unsigned long *arg_lengths, bool strict_arg_count = true) {
|
|
/*
|
|
This array should not be smaller than number of argument definitions.
|
|
*/
|
|
bool res[2];
|
|
bool result = false;
|
|
|
|
DBUG_ASSERT(array_elements(res) >= arg_def_size);
|
|
|
|
/*
|
|
Check, whether provided argument count matches expected argument count.
|
|
*/
|
|
for (size_t i = 0; i < arg_def_size; ++i)
|
|
if ((res[i] = ((strict_arg_count && arg_def[i].count == arg_count) ||
|
|
(!strict_arg_count && arg_def[i].count <= arg_count))))
|
|
result = true;
|
|
|
|
/*
|
|
At least one argument count was matched againts definition.
|
|
*/
|
|
if (result == false) {
|
|
handler.error("Invalid argument count.");
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
Do not use actual argument count. Use maximal argument count provided by
|
|
the definitions.
|
|
*/
|
|
arg_count = static_cast<unsigned int>(max_arg_count(arg_def, arg_def_size));
|
|
|
|
for (size_t i = 0; i < arg_count; ++i) {
|
|
size_t j;
|
|
|
|
/*
|
|
Check argument type.
|
|
*/
|
|
for (j = 0, result = false; j < arg_def_size; ++j) {
|
|
if ((res[j] = res[j] && arg_def[j].args[i].type == arg_type[i]))
|
|
result = true;
|
|
}
|
|
|
|
if (result == false) {
|
|
handler.error("Invalid argument type [%d].", i);
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
Apply custom arg validation.
|
|
*/
|
|
for (j = 0, result = false; j < arg_def_size; ++j)
|
|
if ((res[j] = res[j] && (arg_def[j].args[i].validator == nullptr ||
|
|
arg_def[j].args[i].validator(
|
|
handler, args[i], arg_lengths[i], i))))
|
|
result = true;
|
|
|
|
if (result == false) {
|
|
/*
|
|
Error has been already set by the validator.
|
|
*/
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
Find which argument definition was matched.
|
|
*/
|
|
for (size_t i = 0; i < arg_def_size; ++i) {
|
|
if (res[i]) return static_cast<int>(i);
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
Argument check for UDF provided arguments. This checks both, obligatory and
|
|
optional arguments.
|
|
|
|
@param handler Error handler used for error handling.
|
|
@param args UDF_ARGS structure.
|
|
|
|
@return
|
|
@retval false Succeeded. Arguments are ok.
|
|
@retval true Failed. Error is reported via specified handler.
|
|
*/
|
|
static bool arg_check(IError_handler &handler, UDF_ARGS *args) {
|
|
/*
|
|
Check obligatory args.
|
|
*/
|
|
int arg_res;
|
|
|
|
if ((arg_res = arg_check(handler, args->arg_count, args->arg_type,
|
|
audit_log_primary_args_def,
|
|
array_elements(audit_log_primary_args_def),
|
|
args->args, args->lengths, false)) < 0) {
|
|
return true;
|
|
}
|
|
|
|
unsigned int arg_count =
|
|
args->arg_count -
|
|
static_cast<unsigned int>(audit_log_primary_args_def[arg_res].count);
|
|
Item_result *arg_type =
|
|
args->arg_type + audit_log_primary_args_def[arg_res].count;
|
|
char **arguments = args->args + audit_log_primary_args_def[arg_res].count;
|
|
unsigned long *arg_lengths =
|
|
args->lengths + audit_log_primary_args_def[arg_res].count;
|
|
|
|
while (arg_count > 0) {
|
|
/*
|
|
Check key-value pairs if present.
|
|
*/
|
|
if ((arg_res =
|
|
arg_check(handler, arg_count, arg_type, audit_log_extra_args_def,
|
|
array_elements(audit_log_extra_args_def), arguments,
|
|
arg_lengths, false)) < 0) {
|
|
return true;
|
|
}
|
|
|
|
arg_count -=
|
|
static_cast<unsigned int>(audit_log_extra_args_def[arg_res].count);
|
|
arg_type += audit_log_extra_args_def[arg_res].count;
|
|
arguments += audit_log_extra_args_def[arg_res].count;
|
|
arg_lengths += audit_log_extra_args_def[arg_res].count;
|
|
};
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
@class String_error_handler
|
|
|
|
Error handler that copies error message into specified buffer.
|
|
*/
|
|
class String_error_handler : public IError_handler {
|
|
public:
|
|
/**
|
|
Object construction.
|
|
|
|
@param buffer Buffer, where the error is to be copied.
|
|
@param size Buffer size.
|
|
@param out_size [out] Written bytes into the buffer.
|
|
*/
|
|
String_error_handler(char *buffer, size_t size,
|
|
unsigned long *out_size = nullptr)
|
|
: m_buffer(buffer), m_size(size), m_out_size(out_size) {}
|
|
|
|
/**
|
|
Copy message into the buffer.
|
|
|
|
@param message Message to be copied.
|
|
*/
|
|
virtual void error(const char *message, ...)
|
|
MY_ATTRIBUTE((format(printf, 2, 3))) {
|
|
va_list va;
|
|
va_start(va, message);
|
|
int copied = vsnprintf(m_buffer, m_size - 1, message, va);
|
|
va_end(va);
|
|
m_buffer[copied] = '\0';
|
|
|
|
if (m_out_size != nullptr) *m_out_size = static_cast<unsigned long>(copied);
|
|
}
|
|
|
|
private:
|
|
/**
|
|
Buffer pointer.
|
|
*/
|
|
char *m_buffer;
|
|
/**
|
|
Buffer size.
|
|
*/
|
|
size_t m_size;
|
|
/**
|
|
Written buffer size.
|
|
*/
|
|
unsigned long *m_out_size;
|
|
};
|
|
|
|
/**
|
|
UDF function itself.
|
|
*/
|
|
static char *emit(UDF_INIT *initid MY_ATTRIBUTE((unused)), UDF_ARGS *args,
|
|
char *result, unsigned long *length,
|
|
unsigned char *null_value MY_ATTRIBUTE((unused)),
|
|
unsigned char *error MY_ATTRIBUTE((unused))) {
|
|
/*
|
|
Store the error as the result of the UDF.
|
|
*/
|
|
String_error_handler handler(result, static_cast<size_t>(*length), length);
|
|
|
|
int arg_res;
|
|
|
|
/*
|
|
Check obligatory args.
|
|
*/
|
|
if ((arg_res = arg_check(handler, args->arg_count, args->arg_type,
|
|
audit_log_primary_args_def,
|
|
array_elements(audit_log_primary_args_def),
|
|
args->args, args->lengths, false)) < 0) {
|
|
return result;
|
|
}
|
|
|
|
Item_result *arg_type =
|
|
args->arg_type +
|
|
static_cast<unsigned int>(audit_log_primary_args_def[arg_res].count);
|
|
char **arguments = args->args + audit_log_primary_args_def[arg_res].count;
|
|
unsigned long *arg_lengths =
|
|
args->lengths + audit_log_primary_args_def[arg_res].count;
|
|
|
|
/*
|
|
Obligatory parameters has been consumed. Process key value arguments.
|
|
*/
|
|
std::map<std::string, mysql_event_message_key_value_t> key_values;
|
|
|
|
/*
|
|
Check whether the key value has not been duplicated and create
|
|
audit_api_message_key_value structures that go directly into audit api
|
|
call.
|
|
|
|
This step could be improved, but we need to implement duplicates check.
|
|
*/
|
|
for (unsigned int arg_count =
|
|
args->arg_count -
|
|
static_cast<unsigned int>(audit_log_primary_args_def[arg_res].count);
|
|
arg_count > 0; arg_count -= static_cast<unsigned int>(
|
|
audit_log_extra_args_def[arg_res].count)) {
|
|
if ((arg_res =
|
|
arg_check(handler, arg_count, arg_type, audit_log_extra_args_def,
|
|
array_elements(audit_log_extra_args_def), arguments,
|
|
arg_lengths, false)) < 0)
|
|
return result;
|
|
|
|
std::string key(*arguments, *arg_lengths);
|
|
|
|
std::map<std::string, mysql_event_message_key_value_t>::const_iterator
|
|
iter = key_values.find(key);
|
|
if (iter != key_values.end()) {
|
|
handler.error("Duplicated key [%d].", args->arg_count - arg_count);
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
Build the structure depending on the type of the specified UDF argument.
|
|
*/
|
|
mysql_event_message_key_value_t val;
|
|
|
|
/*
|
|
Key is always text.
|
|
*/
|
|
val.key.str = arguments[0];
|
|
val.key.length = arg_lengths[0];
|
|
|
|
/*
|
|
Value depends on the type of the argument.
|
|
*/
|
|
if (arg_res == 0) {
|
|
val.value_type = MYSQL_AUDIT_MESSAGE_VALUE_TYPE_STR;
|
|
val.value.str.str = arguments[1];
|
|
val.value.str.length = arg_lengths[1];
|
|
} else if (arg_res == 1) {
|
|
val.value_type = MYSQL_AUDIT_MESSAGE_VALUE_TYPE_NUM;
|
|
val.value.num = *reinterpret_cast<long long *>(arguments[1]);
|
|
}
|
|
|
|
key_values[key] = val;
|
|
|
|
arg_type += audit_log_extra_args_def[arg_res].count;
|
|
arguments += audit_log_extra_args_def[arg_res].count;
|
|
arg_lengths += audit_log_extra_args_def[arg_res].count;
|
|
}
|
|
|
|
/**
|
|
Allocate array that is used by the audit api service.
|
|
*/
|
|
std::unique_ptr<mysql_event_message_key_value_t[]> key_value_map(
|
|
key_values.size() > 0
|
|
? new mysql_event_message_key_value_t[key_values.size()]
|
|
: nullptr);
|
|
|
|
mysql_event_message_key_value_t *kv = key_value_map.get();
|
|
|
|
/*
|
|
Convert key value map into an array passed to the message function.
|
|
*/
|
|
for (std::map<std::string, mysql_event_message_key_value_t>::const_iterator
|
|
i = key_values.begin();
|
|
i != key_values.end(); ++i, ++kv) {
|
|
*kv = i->second;
|
|
}
|
|
|
|
/*
|
|
Everything was ok till this point.
|
|
*/
|
|
*length = static_cast<unsigned long>(sprintf(result, "%s", "OK"));
|
|
|
|
/*
|
|
Audit message service does not return any meaningful value.
|
|
*/
|
|
mysql_service_mysql_audit_api_message->emit(
|
|
MYSQL_AUDIT_MESSAGE_USER, args->args[0], args->lengths[0], args->args[1],
|
|
args->lengths[1], args->args[2], args->lengths[2], key_value_map.get(),
|
|
key_values.size());
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
UDF initialization. Check argument correctness.
|
|
|
|
@param args UDF arguments.
|
|
@param message Buffer, where the error is to be written.
|
|
*/
|
|
static bool emit_init(UDF_INIT *, UDF_ARGS *args, char *message) {
|
|
String_error_handler handler(message, MYSQL_ERRMSG_SIZE);
|
|
|
|
return arg_check(handler, args);
|
|
}
|
|
|
|
/**
|
|
Component initialization.
|
|
*/
|
|
static mysql_service_status_t init() {
|
|
return mysql_service_udf_registration->udf_register(
|
|
"audit_api_message_emit_udf", STRING_RESULT, (Udf_func_any)emit,
|
|
(Udf_func_init)emit_init, NULL);
|
|
}
|
|
|
|
/**
|
|
Component deinitialization.
|
|
*/
|
|
static mysql_service_status_t deinit() {
|
|
int was_present = 0;
|
|
return mysql_service_udf_registration->udf_unregister(
|
|
"audit_api_message_emit_udf", &was_present) != 0;
|
|
}
|
|
|
|
BEGIN_COMPONENT_PROVIDES(audit_api_message_emit)
|
|
END_COMPONENT_PROVIDES();
|
|
|
|
BEGIN_COMPONENT_REQUIRES(audit_api_message_emit)
|
|
REQUIRES_SERVICE(mysql_audit_api_message), REQUIRES_SERVICE(udf_registration),
|
|
END_COMPONENT_REQUIRES();
|
|
|
|
BEGIN_COMPONENT_METADATA(audit_api_message_emit)
|
|
METADATA("mysql.author", "Oracle Corporation"),
|
|
METADATA("mysql.license", "GPL"), END_COMPONENT_METADATA();
|
|
|
|
DECLARE_COMPONENT(audit_api_message_emit, "audit_api_message_emit")
|
|
init, deinit END_DECLARE_COMPONENT();
|
|
|
|
DECLARE_LIBRARY_COMPONENTS &COMPONENT_REF(audit_api_message_emit)
|
|
END_DECLARE_LIBRARY_COMPONENTS
|