992 lines
25 KiB
C++
992 lines
25 KiB
C++
/*
|
|
* audit_handler.h
|
|
*
|
|
* Created on: Feb 6, 2011
|
|
* Author: guyl
|
|
*/
|
|
|
|
#ifndef AUDIT_HANDLER_H_
|
|
#define AUDIT_HANDLER_H_
|
|
|
|
#include "mysql_inc.h"
|
|
#include <yajl/yajl_gen.h>
|
|
|
|
#ifndef PCRE_STATIC
|
|
#define PCRE_STATIC
|
|
#endif
|
|
|
|
#include <pcre.h>
|
|
|
|
#define AUDIT_LOG_PREFIX "Trellix Audit Plugin:"
|
|
#define AUDIT_PROTOCOL_VERSION "1.0"
|
|
|
|
#if !defined(MARIADB_BASE_VERSION) && MYSQL_VERSION_ID >= 50709
|
|
// For locking we use the native lock routines provided by MySQL.
|
|
// The data types and functions for native locking changed at 5.7.x.
|
|
// Try to hide this with macros.
|
|
#define rw_lock_t native_rw_lock_t
|
|
#define rw_rdlock native_rw_rdlock
|
|
#define rw_wrlock native_rw_wrlock
|
|
#define rw_unlock native_rw_unlock
|
|
#define rwlock_destroy native_rw_destroy
|
|
#define my_rwlock_init(lock, unused) native_rw_init(lock)
|
|
#endif
|
|
|
|
class THD;
|
|
|
|
#define MAX_NUM_QUERY_TABLE_ELEM 100
|
|
typedef struct _QueryTableInf {
|
|
int num_of_elem;
|
|
char *db[MAX_NUM_QUERY_TABLE_ELEM];
|
|
char *table_name[MAX_NUM_QUERY_TABLE_ELEM];
|
|
const char *object_type[MAX_NUM_QUERY_TABLE_ELEM];
|
|
} QueryTableInf;
|
|
|
|
#define MAX_NUM_QUEUE_ELEM 1024
|
|
typedef struct _THDPRINTED {
|
|
size_t cur_index;
|
|
char is_thd_printed_queue[MAX_NUM_QUEUE_ELEM];
|
|
} THDPRINTED;
|
|
|
|
struct PeerInfo {
|
|
unsigned long pid;
|
|
enum { MAX_APP_NAME_LEN = 128, MAX_USER_NAME_LEN = 128 };
|
|
char appName[MAX_APP_NAME_LEN + 1];
|
|
char osUser[MAX_USER_NAME_LEN + 1]; // allow lots, in case from LDAP or some such
|
|
PeerInfo() : pid(0) {
|
|
memset(appName, 0, sizeof appName);
|
|
memset(osUser, 0, sizeof osUser);
|
|
}
|
|
};
|
|
|
|
PeerInfo *retrieve_peerinfo(THD *thd);
|
|
|
|
const char *retrieve_command(THD *thd, bool& is_sql_cmd);
|
|
typedef size_t OFFSET;
|
|
|
|
#define MAX_COMMAND_CHAR_NUMBERS 40
|
|
#define MAX_COM_STATUS_VARS_RECORDS 512
|
|
|
|
// mysql max identifier is 64 so 2*64 + . and null
|
|
#define MAX_OBJECT_CHAR_NUMBERS 131
|
|
#define MAX_USER_CHAR_NUMBERS 32
|
|
#define MAX_NUM_OBJECT_ELEM 256
|
|
#define MAX_NUM_USER_ELEM 256
|
|
|
|
/**
|
|
* The struct used to hold offsets. We should have one per version.
|
|
*/
|
|
typedef struct ThdOffsets {
|
|
const char *version;
|
|
const char *md5digest;
|
|
OFFSET query_id;
|
|
OFFSET thread_id;
|
|
OFFSET main_security_ctx;
|
|
OFFSET command;
|
|
OFFSET lex;
|
|
OFFSET lex_comment;
|
|
OFFSET sec_ctx_user;
|
|
OFFSET sec_ctx_host;
|
|
OFFSET sec_ctx_ip;
|
|
OFFSET sec_ctx_priv_user;
|
|
OFFSET db;
|
|
OFFSET killed;
|
|
OFFSET client_capabilities;
|
|
OFFSET pfs_connect_attrs;
|
|
OFFSET pfs_connect_attrs_length;
|
|
OFFSET pfs_connect_attrs_cs;
|
|
OFFSET net;
|
|
OFFSET lex_m_sql_command;
|
|
OFFSET uninstall_cmd_comment;
|
|
OFFSET found_rows;
|
|
OFFSET sent_row_count;
|
|
OFFSET row_count_func;
|
|
OFFSET stmt_da;
|
|
OFFSET da_status;
|
|
OFFSET da_sql_errno;
|
|
OFFSET view_tables;
|
|
} ThdOffsets;
|
|
|
|
/*
|
|
* The offsets array
|
|
*/
|
|
extern const ThdOffsets thd_offsets_arr[];
|
|
extern const size_t thd_offsets_arr_size;
|
|
|
|
/*
|
|
* On success, the number of bytes written are returned (zero indicates nothing was written). On error, -1 is returned,
|
|
*/
|
|
typedef ssize_t (*audit_write_func)(const char *, size_t);
|
|
|
|
|
|
/**
|
|
* Interface for an io writer
|
|
*/
|
|
class IWriter {
|
|
public:
|
|
virtual ~IWriter() {}
|
|
// return negative on fail
|
|
virtual ssize_t write(const char *data, size_t size) = 0;
|
|
virtual ssize_t write_no_lock(const char *str, size_t size) = 0;
|
|
// return 0 on success
|
|
virtual int open(const char *io_dest, bool log_errors) = 0;
|
|
virtual void close() = 0;
|
|
};
|
|
|
|
class ThdSesData {
|
|
public:
|
|
// enum indicating from where the object list came from
|
|
enum ObjectIterType { OBJ_NONE, OBJ_DB, OBJ_QUERY_CACHE, OBJ_TABLE_LIST };
|
|
// enum indicating source of statement
|
|
typedef enum { SOURCE_GENERAL, SOURCE_QUERY_CACHE } StatementSource;
|
|
ThdSesData(THD *pTHD, StatementSource source = SOURCE_GENERAL);
|
|
THD *getTHD() const { return m_pThd;}
|
|
const char *getCmdName() const { return m_CmdName; }
|
|
void setCmdName(const char *cmd) { m_CmdName = cmd; }
|
|
const char *getUserName() { return m_UserName; }
|
|
const unsigned long getPeerPid() const;
|
|
const char *getAppName() const;
|
|
const char *getOsUser() const;
|
|
const int getPort() const { return m_port; }
|
|
const StatementSource getStatementSource() const { return m_source; }
|
|
void storeErrorCode();
|
|
void setErrorCode(uint code) { m_errorCode = code; m_setErrorCode = true; }
|
|
bool getErrorCode(uint & code) const { code = m_errorCode; return m_setErrorCode; }
|
|
/**
|
|
* Start fetching objects. Return true if there are objects available.
|
|
*/
|
|
bool startGetObjects();
|
|
/**
|
|
* Get next object. Return true if populated. False if there isn't an
|
|
* object available.
|
|
* Will point the passed pointers to point to db, name and type.
|
|
* obj_type is optional and may be null.
|
|
*/
|
|
bool getNextObject(const char **db_name, const char **obj_name, const char **obj_type);
|
|
|
|
private:
|
|
THD *m_pThd;
|
|
const char *m_CmdName;
|
|
const char *m_UserName;
|
|
bool m_isSqlCmd;
|
|
enum ObjectIterType m_objIterType;
|
|
// pointer for iterating tables
|
|
TABLE_LIST *m_tables;
|
|
// indicator if we are at the first table
|
|
bool m_firstTable;
|
|
// used for query cache iter
|
|
QueryTableInf *m_tableInf;
|
|
int m_index;
|
|
|
|
// Statement source
|
|
StatementSource m_source;
|
|
|
|
PeerInfo *m_peerInfo;
|
|
|
|
int m_port; // TCP port of remote side
|
|
|
|
uint m_errorCode;
|
|
bool m_setErrorCode;
|
|
|
|
protected:
|
|
ThdSesData(const ThdSesData&);
|
|
ThdSesData &operator =(const ThdSesData&);
|
|
};
|
|
|
|
/**
|
|
* Base for audit formatter
|
|
*/
|
|
class Audit_formatter {
|
|
public:
|
|
virtual ~Audit_formatter() {}
|
|
|
|
/**
|
|
* static offsets to use for fetching THD data.
|
|
* Set by the audit plugin during startup.
|
|
*/
|
|
static ThdOffsets thd_offsets;
|
|
|
|
/**
|
|
* Format an audit event from the passed THD.
|
|
* Will write out its output using the audit_write_func.
|
|
*
|
|
* @return -1 on a failure
|
|
*/
|
|
virtual ssize_t event_format(ThdSesData *pThdData, IWriter *writer) = 0;
|
|
/**
|
|
* Format a message when handler is started
|
|
* @return -1 on a failure
|
|
*/
|
|
virtual ssize_t start_msg_format(IWriter *writer) { return 0; }
|
|
/**
|
|
* Format a message when handler is stopped
|
|
* @return -1 on a failure
|
|
*/
|
|
virtual ssize_t stop_msg_format(IWriter *writer) { return 0; }
|
|
|
|
static const char *retrieve_object_type(TABLE_LIST *pObj);
|
|
#if defined(MARIADB_BASE_VERSION) || MYSQL_VERSION_ID < 80000
|
|
static QueryTableInf *getQueryCacheTableList1(THD *thd);
|
|
#endif
|
|
|
|
// utility functions for fetching thd stuff
|
|
static int thd_client_port(THD *thd);
|
|
|
|
static inline my_thread_id thd_inst_thread_id(THD *thd)
|
|
{
|
|
return *(my_thread_id *) (((unsigned char *) thd)
|
|
+ Audit_formatter::thd_offsets.thread_id);
|
|
}
|
|
static inline query_id_t thd_inst_query_id(THD *thd)
|
|
{
|
|
return *(query_id_t *) (((unsigned char *) thd)
|
|
+ Audit_formatter::thd_offsets.query_id);
|
|
}
|
|
static inline Security_context *thd_inst_main_security_ctx(THD *thd)
|
|
{
|
|
return (Security_context *) (((unsigned char *) thd)
|
|
+ Audit_formatter::thd_offsets.main_security_ctx);
|
|
}
|
|
|
|
static inline const char *thd_db(THD *thd)
|
|
{
|
|
if (! Audit_formatter::thd_offsets.db) // no offsets use compiled in header
|
|
{
|
|
#if !defined(MARIADB_BASE_VERSION) && MYSQL_VERSION_ID < 50709
|
|
return thd->db;
|
|
#elif defined(MARIADB_BASE_VERSION)
|
|
#if MYSQL_VERSION_ID >= 100307
|
|
return thd->db.str;
|
|
#else
|
|
return thd->db;
|
|
#endif
|
|
#else
|
|
return thd->db().str;
|
|
#endif
|
|
}
|
|
return *(const char **) (((unsigned char *) thd)
|
|
+ Audit_formatter::thd_offsets.db);
|
|
}
|
|
|
|
static inline int thd_killed(THD *thd)
|
|
{
|
|
if (! Audit_formatter::thd_offsets.killed) // no offsets use thd_killed function
|
|
{
|
|
return ::thd_killed(thd);
|
|
}
|
|
return *(int *) (((unsigned char *) thd)
|
|
+ Audit_formatter::thd_offsets.killed);
|
|
}
|
|
|
|
static inline const char *thd_inst_main_security_ctx_user(THD *thd)
|
|
{
|
|
Security_context *sctx = thd_inst_main_security_ctx(thd);
|
|
if (! Audit_formatter::thd_offsets.sec_ctx_user) // no offsets use compiled in header
|
|
{
|
|
#if defined(MARIADB_BASE_VERSION) || MYSQL_VERSION_ID < 50709
|
|
return sctx->user;
|
|
#else
|
|
return sctx->user().str;
|
|
#endif
|
|
}
|
|
return *(const char **) (((unsigned char *) sctx)
|
|
+ Audit_formatter::thd_offsets.sec_ctx_user);
|
|
}
|
|
|
|
static inline const char *thd_inst_main_security_ctx_host(THD *thd)
|
|
{
|
|
Security_context *sctx = thd_inst_main_security_ctx(thd);
|
|
// check ip to understand if set, as host is first in the struct and may actually be set to 0
|
|
// we expect to have offsets for both ip and host or for neither of them
|
|
if (! Audit_formatter::thd_offsets.sec_ctx_ip)
|
|
{
|
|
// interface changed in 5.5.34 and 5.6.14 and up host changed to get_host()
|
|
// see: http://bazaar.launchpad.net/~mysql/mysql-server/5.5/revision/4407.1.1/sql/sql_class.h
|
|
#if defined(MARIADB_BASE_VERSION)
|
|
return sctx->host;
|
|
#else
|
|
// MySQL
|
|
#if MYSQL_VERSION_ID < 50534 || (MYSQL_VERSION_ID >= 50600 && MYSQL_VERSION_ID < 50614)
|
|
return sctx->host;
|
|
#elif (MYSQL_VERSION_ID >= 50534 && MYSQL_VERSION_ID < 50600) \
|
|
|| (MYSQL_VERSION_ID >= 50614 && MYSQL_VERSION_ID < 50709)
|
|
return sctx->get_host()->ptr();
|
|
#else
|
|
// interface changed again in 5.7
|
|
return sctx->host().str;
|
|
#endif
|
|
#endif // ! defined(MARIADB_BASE_VERSION)
|
|
}
|
|
return *(const char **) (((unsigned char *) sctx)
|
|
+ Audit_formatter::thd_offsets.sec_ctx_host);
|
|
}
|
|
|
|
static inline const char *thd_inst_main_security_ctx_ip(THD *thd)
|
|
{
|
|
Security_context *sctx = thd_inst_main_security_ctx(thd);
|
|
if (! Audit_formatter::thd_offsets.sec_ctx_ip) // no offsets use compiled in header
|
|
{
|
|
// interface changed in 5.5.34 and 5.6.14 and up host changed to get_ip()
|
|
#if defined(MARIADB_BASE_VERSION)
|
|
return sctx->ip;
|
|
#else
|
|
// MySQL
|
|
#if MYSQL_VERSION_ID < 50534 || (MYSQL_VERSION_ID >= 50600 && MYSQL_VERSION_ID < 50614)
|
|
return sctx->ip;
|
|
#elif (MYSQL_VERSION_ID >= 50534 && MYSQL_VERSION_ID < 50600) \
|
|
|| (MYSQL_VERSION_ID >= 50614 && MYSQL_VERSION_ID < 50709)
|
|
return sctx->get_ip()->ptr();
|
|
#else
|
|
// interface changed again in 5.7
|
|
return sctx->ip().str;
|
|
#endif
|
|
#endif // ! defined(MARIADB_BASE_VERSION)
|
|
}
|
|
return *(const char **) (((unsigned char *) sctx)
|
|
+ Audit_formatter::thd_offsets.sec_ctx_ip);
|
|
}
|
|
|
|
static inline const char *thd_inst_main_security_ctx_priv_user(THD *thd)
|
|
{
|
|
Security_context *sctx = thd_inst_main_security_ctx(thd);
|
|
if (! Audit_formatter::thd_offsets.sec_ctx_priv_user) // no offsets use compiled in header
|
|
{
|
|
#if defined(MARIADB_BASE_VERSION) || MYSQL_VERSION_ID < 50709
|
|
return sctx->priv_user;
|
|
#else
|
|
return sctx->priv_user().str;
|
|
#endif
|
|
}
|
|
// in 5.5 and up priv_user is an array (char priv_user[USERNAME_LENGTH])
|
|
return (const char *) (((unsigned char *) sctx)
|
|
+ Audit_formatter::thd_offsets.sec_ctx_priv_user);
|
|
}
|
|
|
|
static inline int thd_inst_command(THD *thd)
|
|
{
|
|
return *(int *) (((unsigned char *) thd) + Audit_formatter::thd_offsets.command);
|
|
}
|
|
|
|
static inline LEX *thd_lex(THD *thd)
|
|
{
|
|
return *(LEX **) (((unsigned char *) thd) + Audit_formatter::thd_offsets.lex);
|
|
}
|
|
|
|
#if !defined(MARIADB_BASE_VERSION) && MYSQL_VERSION_ID >= 50709
|
|
//in mysql 5.7 capabilities flag moved to protocol. We use the capabilities offset to point to m_protocol
|
|
//and get from protocol the capabilities flag
|
|
static inline ulong thd_client_capabilities(THD *thd)
|
|
{
|
|
if (! Audit_formatter::thd_offsets.client_capabilities)
|
|
{
|
|
//no offsets - return 0
|
|
return 0;
|
|
}
|
|
Protocol * prot = *(Protocol **) (((unsigned char *) thd) + Audit_formatter::thd_offsets.client_capabilities);
|
|
if(!prot)
|
|
{
|
|
return 0;
|
|
}
|
|
return prot->get_client_capabilities();
|
|
}
|
|
#else
|
|
static inline ulong thd_client_capabilities(THD *thd)
|
|
{
|
|
if (! Audit_formatter::thd_offsets.client_capabilities)
|
|
{
|
|
//no offsets - return 0
|
|
return 0;
|
|
}
|
|
return *(ulong *) (((unsigned char *) thd) + Audit_formatter::thd_offsets.client_capabilities);
|
|
}
|
|
#endif
|
|
|
|
static inline const char * pfs_connect_attrs(const void * pfs)
|
|
{
|
|
if (! Audit_formatter::thd_offsets.pfs_connect_attrs || pfs == NULL)
|
|
{
|
|
//no offsets - return null
|
|
return NULL;
|
|
}
|
|
const char **pfs_pointer = (const char **) (((unsigned char *) pfs) + Audit_formatter::thd_offsets.pfs_connect_attrs);
|
|
|
|
return *pfs_pointer;
|
|
}
|
|
|
|
static inline uint pfs_connect_attrs_length(const void * pfs)
|
|
{
|
|
if (! Audit_formatter::thd_offsets.pfs_connect_attrs_length || pfs == NULL)
|
|
{
|
|
//no offsets - return 0
|
|
return 0;
|
|
}
|
|
return *(uint *) (((unsigned char *) pfs) + Audit_formatter::thd_offsets.pfs_connect_attrs_length);
|
|
}
|
|
|
|
static inline const CHARSET_INFO * pfs_connect_attrs_cs(const void * pfs)
|
|
{
|
|
if (! Audit_formatter::thd_offsets.pfs_connect_attrs_cs || pfs == NULL)
|
|
{
|
|
//no offsets - return null
|
|
return NULL;
|
|
}
|
|
#if (!defined(MARIADB_BASE_VERSION) && MYSQL_VERSION_ID >= 50600) || (defined(MARIADB_BASE_VERSION) && MYSQL_VERSION_ID >= 100010)
|
|
/**
|
|
* m_session_connect_attrs_cs changed to: m_session_connect_attrs_cs_number
|
|
* in 5.6.15 and up, 5.7 and mariadb 10.0.11 and up, and 10.1.
|
|
* see: storage/perfschema/table_session_connect.cc
|
|
* and: storage/perfschema/pfs_instr.h
|
|
*/
|
|
static bool first = true;
|
|
static int major, minor, patch;
|
|
|
|
if (first)
|
|
{
|
|
sscanf(server_version, "%d.%d.%d", & major, & minor, & patch);
|
|
// sql_print_information("Audit_plugin: extracted version: %d.%d.%d",
|
|
// major, minor, patch);
|
|
}
|
|
|
|
if ( ( major == 5 && ( (minor == 6 && patch >= 15) || minor >= 7) ) || (major == 8) // MySQL
|
|
|| ( major == 10 && ( (minor == 0 && patch >= 11) || minor >= 1) ) || (major >= 11) ) // MariaDB
|
|
{
|
|
uint cs_number = *(uint *) (((unsigned char *) pfs) + Audit_formatter::thd_offsets.pfs_connect_attrs_cs);
|
|
if (!cs_number)
|
|
{
|
|
return NULL;
|
|
|
|
}
|
|
return get_charset(cs_number, MYF(0));
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
return *(const CHARSET_INFO **) (((unsigned char *) pfs) + Audit_formatter::thd_offsets.pfs_connect_attrs_cs);
|
|
}
|
|
}
|
|
|
|
static inline int thd_client_fd(THD *thd)
|
|
{
|
|
if (! Audit_formatter::thd_offsets.net)
|
|
{
|
|
return -1;
|
|
}
|
|
NET *net = ((NET *) (((unsigned char *) thd)
|
|
+ Audit_formatter::thd_offsets.net));
|
|
// get the socket for the peer
|
|
int sock = -1;
|
|
if (net->vio != NULL) // MySQL 5.7.17 - this can happen. :-(
|
|
{
|
|
#if MYSQL_VERSION_ID < 50600
|
|
sock = net->vio->sd;
|
|
#else
|
|
sock = net->vio->mysql_socket.fd;
|
|
#endif
|
|
}
|
|
|
|
return sock;
|
|
}
|
|
|
|
static inline ulonglong thd_found_rows(THD *thd)
|
|
{
|
|
if (Audit_formatter::thd_offsets.found_rows == 0)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
ulonglong *rows = ((ulonglong *) (((unsigned char *) thd)
|
|
+ Audit_formatter::thd_offsets.found_rows));
|
|
|
|
return *rows;
|
|
}
|
|
|
|
static inline unsigned long thd_sent_row_count(THD *thd)
|
|
{
|
|
if (Audit_formatter::thd_offsets.sent_row_count == 0)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
ha_rows *rows = ((ha_rows *) (((unsigned char *) thd)
|
|
+ Audit_formatter::thd_offsets.sent_row_count));
|
|
|
|
return (unsigned long) *rows;
|
|
}
|
|
|
|
static inline longlong thd_row_count_func(THD *thd)
|
|
{
|
|
if (Audit_formatter::thd_offsets.row_count_func == 0)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
longlong *rows = ((longlong *) (((unsigned char *) thd)
|
|
+ Audit_formatter::thd_offsets.row_count_func));
|
|
|
|
return *rows;
|
|
}
|
|
|
|
static inline bool thd_error_code(THD *thd, uint & code)
|
|
{
|
|
#if MYSQL_VERSION_ID >= 50534
|
|
|
|
if ( Audit_formatter::thd_offsets.stmt_da == 0 ||
|
|
Audit_formatter::thd_offsets.da_status == 0 ||
|
|
Audit_formatter::thd_offsets.da_sql_errno == 0 )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
Diagnostics_area **stmt_da = ((Diagnostics_area **) (((unsigned char *) thd)
|
|
+ Audit_formatter::thd_offsets.stmt_da));
|
|
|
|
enum Diagnostics_area::enum_diagnostics_status *status =
|
|
((enum Diagnostics_area::enum_diagnostics_status *) (((unsigned char *) (*stmt_da))
|
|
+ Audit_formatter::thd_offsets.da_status));
|
|
|
|
uint *sql_errno = ((uint *) (((unsigned char *) (*stmt_da))
|
|
+ Audit_formatter::thd_offsets.da_sql_errno));
|
|
|
|
if (*status == Diagnostics_area::DA_OK ||
|
|
*status == Diagnostics_area::DA_EOF )
|
|
{
|
|
code = 0;
|
|
return true;
|
|
}
|
|
else if (*status == Diagnostics_area::DA_ERROR)
|
|
{
|
|
code = *sql_errno;
|
|
return true;
|
|
}
|
|
else // DA_EMPTY, DA_DISABLE
|
|
{
|
|
return false;
|
|
}
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
#if !defined(MARIADB_BASE_VERSION) && MYSQL_VERSION_ID >= 50709
|
|
static inline Sql_cmd_uninstall_plugin* lex_sql_cmd(LEX *lex)
|
|
{
|
|
return *(Sql_cmd_uninstall_plugin **) (((unsigned char *) lex) + Audit_formatter::thd_offsets.lex_m_sql_command);
|
|
}
|
|
#endif
|
|
|
|
// we don't use get_db_name() as when we call it view may be not null
|
|
// and it may return an invalid value for view_db
|
|
static inline const char *table_get_db_name(TABLE_LIST *table)
|
|
{
|
|
#if defined(MARIADB_BASE_VERSION) && MYSQL_VERSION_ID >= 100307
|
|
return table->db.str;
|
|
#else
|
|
return table->db;
|
|
#endif
|
|
}
|
|
|
|
static inline const char *table_get_name(TABLE_LIST *table)
|
|
{
|
|
#if defined(MARIADB_BASE_VERSION) && MYSQL_VERSION_ID >= 100307
|
|
return table->table_name.str;
|
|
#else
|
|
return table->table_name;
|
|
#endif
|
|
}
|
|
|
|
static inline bool table_is_view(TABLE_LIST *table)
|
|
{
|
|
if (!Audit_formatter::thd_offsets.view_tables)
|
|
return table->view_tables != 0;
|
|
List<TABLE_LIST> **view_tables = (List<TABLE_LIST> **)((char*)table + Audit_formatter::thd_offsets.view_tables);
|
|
return *view_tables;
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Format the audit even in json format
|
|
*/
|
|
class Audit_json_formatter: public Audit_formatter {
|
|
public:
|
|
static const char *DEF_MSG_DELIMITER;
|
|
|
|
Audit_json_formatter()
|
|
: m_write_start_msg(true),
|
|
m_write_sess_connect_attrs(true),
|
|
m_write_client_capabilities(false),
|
|
m_write_socket_creds(true),
|
|
m_perform_password_masking(NULL),
|
|
m_msg_delimiter(NULL),
|
|
m_password_mask_regex_compiled(false),
|
|
m_password_mask_regex_preg(NULL)
|
|
{
|
|
|
|
}
|
|
|
|
virtual ~Audit_json_formatter()
|
|
{
|
|
if (m_password_mask_regex_preg)
|
|
{
|
|
m_password_mask_regex_compiled = false;
|
|
pcre_free(m_password_mask_regex_preg);
|
|
m_password_mask_regex_preg = NULL;
|
|
}
|
|
}
|
|
|
|
virtual ssize_t event_format(ThdSesData *pThdData, IWriter *writer);
|
|
virtual ssize_t start_msg_format(IWriter *writer);
|
|
|
|
/**
|
|
* Utility method used to compile a regex program.
|
|
* Will compile and log errors if necessary.
|
|
* Return null if fails
|
|
*/
|
|
static pcre *regex_compile(const char *str);
|
|
|
|
/**
|
|
* Compile password masking regex
|
|
* Return true on success
|
|
*/
|
|
bool compile_password_masking_regex(const char *str);
|
|
|
|
/**
|
|
* Boolean indicating if to log start msg.
|
|
* Public so sysvar can update.
|
|
*/
|
|
my_bool m_write_start_msg;
|
|
|
|
/**
|
|
* include session oonnect attributes
|
|
* Public so sysvar can update
|
|
*/
|
|
my_bool m_write_sess_connect_attrs;
|
|
|
|
/**
|
|
* include client capabilities
|
|
* Public for sysvar
|
|
*/
|
|
my_bool m_write_client_capabilities;
|
|
|
|
/**
|
|
* include socket credentials from Unix Domain Socket
|
|
* Public for sysvar
|
|
*/
|
|
my_bool m_write_socket_creds;
|
|
|
|
/**
|
|
* Callback function to determine if password masking should be performed
|
|
*/
|
|
my_bool (*m_perform_password_masking)(const char *cmd);
|
|
|
|
/**
|
|
* Message delimiter. Should point to a valid json string
|
|
* (supporting the json escapping format).
|
|
* Will only be checked at the start. Public so can be set by sysvar.
|
|
*
|
|
* We only support a delimiter up to 32 chars
|
|
*/
|
|
char *m_msg_delimiter;
|
|
|
|
protected:
|
|
|
|
Audit_json_formatter& operator =(const Audit_json_formatter& b);
|
|
Audit_json_formatter(const Audit_json_formatter& );
|
|
|
|
/**
|
|
* Boolean indicating if password masking regex is compiled
|
|
*/
|
|
my_bool m_password_mask_regex_compiled;
|
|
|
|
/**
|
|
* Regex used for password masking
|
|
*/
|
|
pcre *m_password_mask_regex_preg;
|
|
};
|
|
|
|
/**
|
|
* Base class for audit handlers. Provides basic locking setup.
|
|
*/
|
|
class Audit_handler {
|
|
public:
|
|
static const size_t MAX_AUDIT_HANDLERS_NUM = 4;
|
|
static const size_t JSON_FILE_HANDLER = 1;
|
|
static const size_t JSON_SOCKET_HANDLER = 3;
|
|
|
|
static Audit_handler *m_audit_handler_list[];
|
|
|
|
/**
|
|
* Will iterate the handler list and log using each handler
|
|
*/
|
|
static void log_audit_all(ThdSesData *pThdData);
|
|
|
|
/**
|
|
* Will iterate the handler list and stop all handlers
|
|
*/
|
|
static void stop_all();
|
|
|
|
Audit_handler() :
|
|
m_formatter()
|
|
,m_initialized()
|
|
,m_enabled()
|
|
,m_failed()
|
|
,m_log_io_errors(true)
|
|
,m_print_offset_err(true)
|
|
{
|
|
}
|
|
|
|
virtual ~Audit_handler()
|
|
{
|
|
if (m_initialized)
|
|
{
|
|
rwlock_destroy(&LOCK_audit);
|
|
pthread_mutex_destroy(&LOCK_io);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Should be called to initialize.
|
|
* We don't init in constructor in order to provide indication if
|
|
* pthread stuff failed init.
|
|
*
|
|
* @frmt the formatter to use in this handler (does not manage
|
|
* destruction of this object)
|
|
* @return 0 on success
|
|
*/
|
|
int init(Audit_formatter *frmt)
|
|
{
|
|
m_formatter = frmt;
|
|
if (m_initialized)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
int res = my_rwlock_init(&LOCK_audit, NULL);
|
|
if (res)
|
|
{
|
|
return res;
|
|
}
|
|
|
|
res = pthread_mutex_init(&LOCK_io, MY_MUTEX_INIT_SLOW);;
|
|
if (res)
|
|
{
|
|
return res;
|
|
}
|
|
|
|
m_initialized = true;
|
|
return res;
|
|
}
|
|
|
|
bool is_init()
|
|
{
|
|
return m_initialized;
|
|
}
|
|
|
|
void set_enable(bool val);
|
|
|
|
bool is_enabled()
|
|
{
|
|
return m_enabled;
|
|
}
|
|
|
|
/**
|
|
* will close and start the handler
|
|
*/
|
|
void flush();
|
|
|
|
/**
|
|
* Will get relevant shared lock and call internal method of handler
|
|
*/
|
|
void log_audit(ThdSesData *pThdData);
|
|
|
|
/**
|
|
* Public so can be configured via sysvar
|
|
*/
|
|
unsigned int m_retry_interval;
|
|
|
|
protected:
|
|
Audit_formatter *m_formatter;
|
|
virtual void handler_start();
|
|
// wiil call internal method and set failed as needed
|
|
bool handler_start_nolock();
|
|
virtual void handler_stop();
|
|
virtual bool handler_start_internal() = 0;
|
|
virtual void handler_stop_internal() = 0;
|
|
virtual bool handler_log_audit(ThdSesData *pThdData) = 0;
|
|
bool m_initialized;
|
|
bool m_enabled;
|
|
bool m_failed;
|
|
bool m_log_io_errors;
|
|
time_t m_last_retry_sec_ts;
|
|
inline void set_failed()
|
|
{
|
|
time(&m_last_retry_sec_ts);
|
|
m_failed = true;
|
|
m_log_io_errors = false;
|
|
}
|
|
inline bool is_failed_now()
|
|
{
|
|
return m_failed && (m_retry_interval < 0 ||
|
|
difftime(time(NULL), m_last_retry_sec_ts) > m_retry_interval);
|
|
}
|
|
// override default assignment and copy to protect against
|
|
// creating additional instances
|
|
Audit_handler & operator=(const Audit_handler&);
|
|
Audit_handler(const Audit_handler&);
|
|
// lock io
|
|
pthread_mutex_t LOCK_io;
|
|
private:
|
|
// bool indicating if to print offset errors to log or not
|
|
bool m_print_offset_err;
|
|
// audit (enable) lock
|
|
rw_lock_t LOCK_audit;
|
|
inline void lock_shared()
|
|
{
|
|
rw_rdlock(&LOCK_audit);
|
|
}
|
|
inline void lock_exclusive()
|
|
{
|
|
rw_wrlock(&LOCK_audit);
|
|
}
|
|
inline void unlock()
|
|
{
|
|
rw_unlock(&LOCK_audit);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Base class for handler which have io and need a lock
|
|
*/
|
|
class Audit_io_handler: public Audit_handler, public IWriter {
|
|
public:
|
|
Audit_io_handler()
|
|
: m_io_dest(NULL), m_io_type(NULL)
|
|
{
|
|
}
|
|
|
|
virtual ~Audit_io_handler()
|
|
{
|
|
}
|
|
|
|
|
|
/**
|
|
* target we write to (socket/file). Public so we update via sysvar
|
|
*/
|
|
char *m_io_dest;
|
|
|
|
inline ssize_t write(const char *data, size_t size)
|
|
{
|
|
pthread_mutex_lock(&LOCK_io);
|
|
ssize_t res = write_no_lock(data, size);
|
|
pthread_mutex_unlock(&LOCK_io); //release the IO lock
|
|
return res;
|
|
}
|
|
|
|
protected:
|
|
/**
|
|
* Will format using the writer
|
|
*/
|
|
virtual bool handler_log_audit(ThdSesData *pThdData);
|
|
virtual bool handler_start_internal();
|
|
virtual void handler_stop_internal();
|
|
// used for logging messages
|
|
const char *m_io_type;
|
|
};
|
|
|
|
class Audit_file_handler: public Audit_io_handler {
|
|
public:
|
|
|
|
Audit_file_handler() :
|
|
m_sync_period(0)
|
|
, m_bufsize(0)
|
|
, m_log_file(NULL)
|
|
, m_sync_counter(0)
|
|
{
|
|
m_io_type = "file";
|
|
}
|
|
|
|
virtual ~Audit_file_handler()
|
|
{
|
|
}
|
|
|
|
/**
|
|
* The period to use for syncing to the file system. 0 means we don't sync.
|
|
* 1 means each write we sync. Larger than 1 means every sync_period we sync.
|
|
*
|
|
* We leave this public so the mysql sysvar function can update this variable directly.
|
|
*/
|
|
unsigned int m_sync_period;
|
|
|
|
/**
|
|
* The buf size used by the file stream. 0 = use default,
|
|
* negative or 1 = no buffering
|
|
*/
|
|
long m_bufsize;
|
|
|
|
/**
|
|
* Write function we pass to formatter
|
|
*/
|
|
ssize_t write_no_lock(const char *data, size_t size);
|
|
|
|
void close();
|
|
|
|
int open(const char *io_dest, bool m_log_errors);
|
|
// static void print_sleep(THD *thd, int delay_ms);
|
|
protected:
|
|
// override default assignment and copy to protect against creating
|
|
// additional instances
|
|
Audit_file_handler & operator=(const Audit_file_handler&);
|
|
Audit_file_handler(const Audit_file_handler&);
|
|
|
|
FILE *m_log_file;
|
|
// the period to use for syncing
|
|
unsigned int m_sync_counter;
|
|
};
|
|
|
|
class Audit_socket_handler: public Audit_io_handler {
|
|
public:
|
|
|
|
Audit_socket_handler() :
|
|
m_connect_timeout(1)
|
|
, m_write_timeout()
|
|
, m_vio()
|
|
, m_log_with_error_severity()
|
|
{
|
|
m_io_type = "socket";
|
|
}
|
|
|
|
virtual ~Audit_socket_handler()
|
|
{
|
|
}
|
|
|
|
|
|
/**
|
|
* Connect timeout in secconds
|
|
*/
|
|
unsigned int m_connect_timeout;
|
|
|
|
/**
|
|
* Write function we pass to formatter
|
|
*/
|
|
ssize_t write_no_lock(const char *data, size_t size);
|
|
|
|
void close();
|
|
|
|
int open(const char *io_dest, bool log_errors);
|
|
|
|
unsigned long m_write_timeout; // write timeout in microseconds
|
|
protected:
|
|
// override default assignment and copy to protect against creating additional instances
|
|
Audit_socket_handler & operator=(const Audit_socket_handler&);
|
|
Audit_socket_handler(const Audit_socket_handler&);
|
|
|
|
// Vio we write to
|
|
// define as void* so we don't access members directly
|
|
void *m_vio;
|
|
|
|
// log using error severity only the second time same issue occurs
|
|
bool m_log_with_error_severity;
|
|
};
|
|
|
|
#endif /* AUDIT_HANDLER_H_ */
|