maria-developers team mailing list archive
-
maria-developers team
-
Mailing list archive
-
Message #05742
Re: MDEV-4472 (audit plugin)
Hi, Alexey!
On Jun 21, Alexey Botchkov wrote:
> Hi, Sergei.
>
> Here is the plugin code for you to check out.
> It constists of a single new file, so i send you the link to it.
> http://myoffice.izhnet.ru/~alex/server_audit/server_audit.c
See my review attached. Comments are marked by // at the start of the
line.
> What's missing in it as i'm not sure how it can be done:
> - No server host/ip in the log. How get that information?
What server host/ip?
> - SYSLOG logging.
> The SysLog i knew was declared in the syslog.h. That's the
> interface talking to the
> local syslogd instance.
> If it's what we need to use, then i have no idea how to
> specify the Syslog server IP for that
> daemon (which is required by the spec). I see nothing in the
> API about it and i thought
> it should be configured with the daemon's cfg file or
> something like that.
Right. So don't implement that, just use openlog/syslog/closelog.
Regards,
Sergei
/* Copyright (C) 2012 Monty Program Ab.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
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 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <my_global.h>
// try to avoid including my_global.h
// if possible
#include <typelib.h>
#include <mysql/plugin.h>
#include <mysql/plugin_audit.h>
#include "../sql_errlog/sql_logger.cc"
// make logger a real service, remove it from both audit plugins
// and in here make, like
// #ifdef COMPILE_FOR_MYSQL
// #include "../../mysys/logger.c"
// #define MYSQL_SERVICE_LOGGER_INCLUDED
// #endif
/*
Disable __attribute__() on non-gcc compilers.
*/
#if !defined(__attribute__) && !defined(__GNUC__)
#define __attribute__(A)
#endif
// let's move it to plugin.h, in a separate changeset
#ifdef _WIN32
#define localtime_r(a, b) localtime_s(b, a)
#endif /*WIN32*/
/*
rate 0 means the logging was disabled.
*/
static char *incl_dml_users, *excl_dml_users,
*incl_ddl_users, *excl_ddl_users,
*syslog_server, *file_path;
static ulong output_type= 0;
static unsigned long long file_rotate_size;
static unsigned int rotations;
static char rotate;
static char logging;
static char ddl_user_buffer[1024];
static char dml_user_buffer[1024];
static char servhost[16];
static size_t servhost_len;
static unsigned int count;
static void update_incl_dml_users(MYSQL_THD thd, struct st_mysql_sys_var *var,
void *var_ptr, const void *save);
static void update_excl_dml_users(MYSQL_THD thd, struct st_mysql_sys_var *var,
void *var_ptr, const void *save);
static void update_incl_ddl_users(MYSQL_THD thd, struct st_mysql_sys_var *var,
void *var_ptr, const void *save);
static void update_excl_ddl_users(MYSQL_THD thd, struct st_mysql_sys_var *var,
void *var_ptr, const void *save);
static void update_output_type(MYSQL_THD thd, struct st_mysql_sys_var *var,
void *var_ptr, const void *save);
static int check_syslog_server(MYSQL_THD thd, struct st_mysql_sys_var *var,
void *save, struct st_mysql_value *value);
static void update_syslog_server(MYSQL_THD thd, struct st_mysql_sys_var *var,
void *var_ptr, const void *save);
#ifdef TO_ENABLE_LATER
static int check_file_path(MYSQL_THD thd, struct st_mysql_sys_var *var,
void *save, struct st_mysql_value *value);
static void update_file_path(MYSQL_THD thd, struct st_mysql_sys_var *var,
void *var_ptr, const void *save);
static void switch_logging(MYSQL_THD thd, struct st_mysql_sys_var *var,
void *var_ptr, const void *save);
#endif /*TO_ENABLE_LATER*/
// I wouldn't bother, and would make this setting (logging destination)
// read-only
static void rotate_log(MYSQL_THD thd, struct st_mysql_sys_var *var,
void *var_ptr, const void *save);
static MYSQL_SYSVAR_STR(incl_dml_users, incl_dml_users, PLUGIN_VAR_RQCMDARG,
"Comma separated list of users to monitor.",
NULL, update_incl_dml_users, NULL);
static MYSQL_SYSVAR_STR(excl_dml_users, excl_dml_users, PLUGIN_VAR_RQCMDARG,
"Comma separated list of users to exclude from auditing.",
NULL, update_excl_dml_users, NULL);
static MYSQL_SYSVAR_STR(incl_ddl_users, incl_ddl_users, PLUGIN_VAR_RQCMDARG,
"Comma separated list of users to monitor.",
NULL, update_incl_ddl_users, "");
static MYSQL_SYSVAR_STR(excl_ddl_users, excl_ddl_users, PLUGIN_VAR_RQCMDARG,
"Comma separated list of users to exclude from auditing.",
NULL, update_excl_ddl_users, "");
#define OUTPUT_NULL 0
#define OUTPUT_SYSLOG 1
#define OUTPUT_FILE 2
const char *output_type_names[]= { "null", "syslog", "file", 0 };
TYPELIB output_typelib=
{
array_elements(output_type_names) - 1, "output_typelib",
output_type_names, NULL
};
static MYSQL_SYSVAR_ENUM(output_type, output_type, PLUGIN_VAR_RQCMDARG,
"Desired output type. Possible values - 'syslog', 'file'"
" or 'null' as no output.", 0, update_output_type, 0,
&output_typelib);
// why did you do a 'null' output type?
static MYSQL_SYSVAR_STR(syslog_server, syslog_server,
PLUGIN_VAR_RQCMDARG,
"IP address of the syslog server.",
check_syslog_server, update_syslog_server, "null");
static MYSQL_SYSVAR_STR(file_path, file_path,
PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_MEMALLOC,
"Path to the log file.", NULL, NULL, "null");
static MYSQL_SYSVAR_ULONGLONG(file_rotate_size, file_rotate_size,
PLUGIN_VAR_RQCMDARG, "Maximum size of the log to start the rotation.",
NULL, NULL,
1000000, 100, ((long long) 0x7FFFFFFFFFFFFFFFLL), 1);
static MYSQL_SYSVAR_UINT(rotations, rotations,
PLUGIN_VAR_READONLY, "Number of rotations before log is removed.",
NULL, NULL, 9, 1, 999, 1);
static MYSQL_SYSVAR_BOOL(rotate, rotate,
PLUGIN_VAR_OPCMDARG, "Force log rotation", NULL, rotate_log,
0);
static MYSQL_SYSVAR_BOOL(logging, logging,
PLUGIN_VAR_OPCMDARG, "Turn on/off the logging.", NULL, NULL, 0);
static struct st_mysql_sys_var* vars[] = {
MYSQL_SYSVAR(incl_dml_users),
MYSQL_SYSVAR(excl_dml_users),
MYSQL_SYSVAR(incl_ddl_users),
MYSQL_SYSVAR(excl_ddl_users),
MYSQL_SYSVAR(output_type),
MYSQL_SYSVAR(syslog_server),
MYSQL_SYSVAR(file_path),
MYSQL_SYSVAR(file_rotate_size),
MYSQL_SYSVAR(rotations),
MYSQL_SYSVAR(rotate),
MYSQL_SYSVAR(logging),
NULL
};
/* Here we set the limitation of 255 on the number of users */
/* we can watch simultaneously. The size is convenient as it */
/* fits one byte. And seems to be enough for the user. */
// please follow our coding (in particular, commenting) style
#define USERLIST_SIZE_LIMIT 0xFF
typedef struct server_audit_user_rec
{
int hash_same;
const char *user_name;
int username_len;
} SERVER_AUDIT_USER_REC;
typedef struct server_audit_user_hash
{
unsigned char table[0x10000];
struct server_audit_user_rec userlist[USERLIST_SIZE_LIMIT];
int n_users;
} SERVER_AUDIT_USER_HASH;
// why? why did you invent your own hash, while we have a perfectly
// usable hash in the server already?
static unsigned int user_hash_key(const char *s, int len)
{
unsigned int key= 0;
const char *end= s+len-1;
while (s<end)
{
key^= (s[0]<<8) + s[1];
s+= 2;
}
if (s == end)
key^= s[0]<<8;
return key;
}
static void init_user_hash(struct server_audit_user_hash *h)
{
bzero(h, sizeof(struct server_audit_user_hash));
}
static int user_hash_add(struct server_audit_user_hash *h,
const char *uname, int uname_len)
{
struct server_audit_user_rec *cur_rec= h->userlist + (h->n_users++);
unsigned int hkey= user_hash_key(uname, uname_len);
if (h->n_users > USERLIST_SIZE_LIMIT)
return 1;
cur_rec->hash_same= h->table[hkey];
h->table[hkey]= h->n_users;
cur_rec->user_name= uname;
cur_rec->username_len= uname_len;
return 0;
}
static int user_hash_lookup(struct server_audit_user_hash *h,
const char *uname, int uname_len)
{
unsigned int hkey= user_hash_key(uname, uname_len);
int cur_n_user;
struct server_audit_user_rec *cur_rec;
if (!h->table[hkey])
return 0;
cur_n_user= h->table[hkey];
do
{
cur_rec= h->userlist + (cur_n_user-1);
if (uname_len == cur_rec->username_len &&
strncmp(uname, cur_rec->user_name, uname_len) == 0)
return 1;
} while ((cur_n_user= cur_rec->hash_same));
return 0;
}
static int user_hash_fill(struct server_audit_user_hash *h,
const char *users)
{
const char *cur_uname;
init_user_hash(h);
while (*users)
{
while (*users == ' ')
users++;
if (!*users)
return 0;
cur_uname= users;
while (*users && *users != ' ' && *users != ',')
users++;
if (user_hash_add(h, cur_uname, users - cur_uname))
return 1;
while (*users && *users != ',')
users++;
if (!*users)
break;
users++;
}
return 0;
}
static void error_header()
{
struct tm tm_time;
time_t curtime;
(void) time(&curtime);
(void) localtime_r(&curtime, &tm_time);
(void) fprintf(stderr,"%02d%02d%02d %2d:%02d:%02d server_audit: ",
tm_time.tm_year % 100, tm_time.tm_mon + 1,
tm_time.tm_mday, tm_time.tm_hour, tm_time.tm_min, tm_time.tm_sec);
}
static LOGGER_HANDLE *logfile;
static struct server_audit_user_hash ddl_user_hash;
static struct server_audit_user_hash dml_user_hash;
static int write_log(const char *message, size_t len)
{
if (output_type == OUTPUT_FILE)
return logger_write(logfile, message, len);
return 0;
}
// better extend the logger service to support syslog
static size_t log_header(char *message, size_t message_len,
time_t *ts,
const char *serverhost, unsigned int serverhost_len,
const char *username, unsigned int username_len,
const char *host, unsigned int hostlen,
unsigned int connection_id, const char *operation)
{
struct tm tm_time;
(void) localtime_r(ts, &tm_time);
return my_snprintf(message, message_len,
"%04d%02d%02d %02d:%02d:%02d,%.*s,%.*s,%.*s,%d,%s",
tm_time.tm_year+1900, tm_time.tm_mon+1, tm_time.tm_mday,
tm_time.tm_hour, tm_time.tm_min, tm_time.tm_sec,
serverhost_len, serverhost,
username_len, username,
hostlen, host,
connection_id, operation);
}
static int log_connection(const struct mysql_event_connection *event,
const char *type)
{
time_t ctime;
size_t csize;
char message[1024];
(void) time(&ctime);
csize= log_header(message, sizeof(message)-1, &ctime,
servhost, servhost_len,
event->user, event->user_length,
event->host, event->host_length,
event->thread_id, type);
message[csize]= '\n';
return write_log(message, csize + 1);
}
static int log_statement(const struct mysql_event_general *event,
const char *type)
{
size_t csize;
char message[1024];
time_t ev_time= (time_t) event->general_time;
csize= log_header(message, sizeof(message)-1, &ev_time,
servhost, servhost_len,
event->general_user, event->general_user_length,
"", 0, event->general_thread_id, type);
csize+= my_snprintf(message+csize, sizeof(message) - 1 - csize,
",%.*s", event->general_query_length, event->general_query);
message[csize]= '\n';
return write_log(message, csize + 1);
}
static int log_table(const struct mysql_event_table *event, const char *type)
{
size_t csize;
char message[1024];
time_t ctime;
(void) time(&ctime);
csize= log_header(message, sizeof(message)-1, &ctime,
servhost, servhost_len,
event->user, strlen(event->user),
event->host, strlen(event->host),
event->thread_id, type);
csize+= my_snprintf(message+csize, sizeof(message) - 1 - csize,
",%.*s,%.*s",event->database_length, event->database,
event->table_length, event->table);
// note that for RENAME you need to log both old and new table names
message[csize]= '\n';
return write_log(message, csize + 1);
}
static int ddl_log_user(const char *name, size_t len)
{
if (incl_ddl_users)
return user_hash_lookup(&ddl_user_hash, name, len);
if (excl_ddl_users)
return user_hash_lookup(&ddl_user_hash, name, len) == 0;
return 1;
}
static int dml_log_user(const char *name, size_t len)
{
if (incl_dml_users)
return user_hash_lookup(&dml_user_hash, name, len);
if (excl_dml_users)
return user_hash_lookup(&dml_user_hash, name, len) == 0;
return 1;
}
static void auditing(MYSQL_THD thd __attribute__((unused)),
unsigned int event_class,
const void *ev)
{
if (!logging)
return;
if (event_class == MYSQL_AUDIT_GENERAL_CLASS)
{
const struct mysql_event_general *event =
(const struct mysql_event_general *) ev;
switch (event->event_subclass)
{
case MYSQL_AUDIT_GENERAL_LOG:
log_statement(event, "QUERY");
break;
case MYSQL_AUDIT_GENERAL_ERROR:
break;
case MYSQL_AUDIT_GENERAL_RESULT:
break;
case MYSQL_AUDIT_GENERAL_STATUS:
break;
default:
count++;
}
}
else if (event_class == MYSQL_AUDIT_TABLE_CLASS)
{
const struct mysql_event_table *event =
(const struct mysql_event_table *) ev;
switch (event->event_subclass)
{
case MYSQL_AUDIT_TABLE_LOCK:
if (dml_log_user(event->user, strlen(event->user)))
log_table(event, "LOCK");
break;
case MYSQL_AUDIT_TABLE_CREATE:
if (ddl_log_user(event->user, strlen(event->user)))
log_table(event, "CREATE");
break;
case MYSQL_AUDIT_TABLE_DROP:
if (ddl_log_user(event->user, strlen(event->user)))
log_table(event, "DROP");
break;
case MYSQL_AUDIT_TABLE_RENAME:
if (ddl_log_user(event->user, strlen(event->user)))
log_table(event, "RENAME");
break;
case MYSQL_AUDIT_TABLE_ALTER:
if (ddl_log_user(event->user, strlen(event->user)))
log_table(event, "ALTER");
break;
default:
count++;
}
}
else if (event_class == MYSQL_AUDIT_CONNECTION_CLASS)
{
const struct mysql_event_connection *event =
(const struct mysql_event_connection *) ev;
switch (event->event_subclass)
{
case MYSQL_AUDIT_CONNECTION_CONNECT:
log_connection(event, "CONNECT");
break;
case MYSQL_AUDIT_CONNECTION_DISCONNECT:
log_connection(event, "DISCONNECT");
break;
case MYSQL_AUDIT_CONNECTION_CHANGE_USER:
log_connection(event, "CHANGEUSER");
break;
default:
count++;
}
}
}
static int server_audit_init(void *p __attribute__((unused)))
{
#ifdef DUMMY
struct hostent* localHost;
char* localIP;
localHost= gethostbyname("localhost");
localIP= inet_ntoa (*(struct in_addr *)*localHost->h_addr_list);
#endif /*DUMMY*/
// what?
incl_dml_users= excl_dml_users= NULL;
incl_ddl_users= excl_ddl_users= NULL;
syslog_server= file_path= NULL;
init_logger_mutexes();
strcpy(servhost, "127.0.0.1");
servhost_len= strlen(servhost);
error_header();
fprintf(stderr, "STARTED\n");
// really? I thought you need to log it in the log
// preferrably with the plugin version, and may be the server version too
count= 0;
return 0;
}
static int server_audit_deinit(void *p __attribute__((unused)))
{
if (output_type == OUTPUT_FILE)
logger_close(logfile);
/* syslog implementation */
/* else if (output_type == OUTPUT_SYSLOG) */
error_header();
fprintf(stderr, "STOPPED\n");
return 0;
}
static void rotate_log(MYSQL_THD thd __attribute__((unused)),
struct st_mysql_sys_var *var __attribute__((unused)),
void *var_ptr __attribute__((unused)),
const void *save __attribute__((unused)))
{
if (output_type == OUTPUT_FILE)
(void) logger_rotate(logfile);
}
static struct st_mysql_audit mysql_descriptor =
{
MYSQL_AUDIT_INTERFACE_VERSION,
NULL,
auditing,
{ MYSQL_AUDIT_GENERAL_CLASSMASK | MYSQL_AUDIT_CONNECTION_CLASSMASK }
};
mysql_declare_plugin(server_auditing)
{
MYSQL_AUDIT_PLUGIN,
&mysql_descriptor,
"SERVER_AUDIT",
"Alexey Botchkov",
"Audit the server activity.",
PLUGIN_LICENSE_GPL,
server_audit_init,
server_audit_deinit,
0x0100,
NULL,
vars,
NULL,
0
}
mysql_declare_plugin_end;
static struct st_mysql_audit maria_descriptor =
{
MYSQL_AUDIT_INTERFACE_VERSION,
NULL,
auditing,
{ MYSQL_AUDIT_TABLE_CLASSMASK | MYSQL_AUDIT_CONNECTION_CLASSMASK }
// no MYSQL_AUDIT_GENERAL_CLASSMASK here?
};
maria_declare_plugin(server_auditing)
{
MYSQL_AUDIT_PLUGIN,
&maria_descriptor,
"SERVER_AUDIT",
"Alexey Botchkov",
"Audit the server activity.",
PLUGIN_LICENSE_GPL,
server_audit_init,
server_audit_deinit,
0x0100,
NULL,
vars,
"1.0",
MariaDB_PLUGIN_MATURITY_ALPHA
}
maria_declare_plugin_end;
static void update_incl_dml_users(MYSQL_THD thd __attribute__((unused)),
struct st_mysql_sys_var *var __attribute__((unused)),
void *var_ptr __attribute__((unused)), const void *save)
{
strncpy(dml_user_buffer, *(const char **) save, sizeof(dml_user_buffer));
// these callbacks aren't called when an initial value of the option
// is set on the command line. please, make sure you have this use case covered
incl_dml_users= dml_user_buffer;
excl_dml_users= NULL;
user_hash_fill(&dml_user_hash, incl_dml_users);
}
static void update_excl_dml_users(MYSQL_THD thd __attribute__((unused)),
struct st_mysql_sys_var *var __attribute__((unused)),
void *var_ptr __attribute__((unused)), const void *save)
{
strncpy(dml_user_buffer, *(const char **) save, sizeof(dml_user_buffer));
excl_dml_users= dml_user_buffer;
incl_dml_users= NULL;
user_hash_fill(&dml_user_hash, excl_dml_users);
}
static void update_incl_ddl_users(MYSQL_THD thd __attribute__((unused)),
struct st_mysql_sys_var *var __attribute__((unused)),
void *var_ptr __attribute__((unused)), const void *save)
{
strncpy(ddl_user_buffer, *(const char **) save, sizeof(ddl_user_buffer));
incl_ddl_users= ddl_user_buffer;
excl_ddl_users= NULL;
user_hash_fill(&ddl_user_hash, incl_ddl_users);
}
static void update_excl_ddl_users(MYSQL_THD thd __attribute__((unused)),
struct st_mysql_sys_var *var __attribute__((unused)),
void *var_ptr __attribute__((unused)), const void *save)
{
strncpy(ddl_user_buffer, *(const char **) save, sizeof(ddl_user_buffer));
excl_ddl_users= ddl_user_buffer;
incl_ddl_users= NULL;
user_hash_fill(&ddl_user_hash, excl_ddl_users);
}
static void update_output_type(MYSQL_THD thd __attribute__((unused)),
struct st_mysql_sys_var *var __attribute__((unused)),
void *var_ptr __attribute__((unused)), const void *save)
{
ulong new_output_type= *((ulong *) save);
if (output_type == new_output_type)
return;
if (output_type == OUTPUT_FILE)
{
logger_close(logfile);
}
else if (output_type == OUTPUT_SYSLOG)
{
/* syslog implementation */
}
output_type= OUTPUT_NULL;
if (new_output_type == OUTPUT_FILE)
{
logfile= logger_open(file_path, file_rotate_size, rotations);
if (logfile == NULL)
{
error_header();
fprintf(stderr, "Could not create file '%s'\n", file_path);
return;
}
}
else if (new_output_type == OUTPUT_SYSLOG)
{
/* syslog implementation */
return;
}
output_type= new_output_type;
}
static int check_syslog_server(MYSQL_THD thd __attribute__((unused)), struct st_mysql_sys_var *var __attribute__((unused)),
void *save __attribute__((unused)), struct st_mysql_value *value __attribute__((unused)))
{
/* syslog implementation */
return output_type == OUTPUT_SYSLOG;
}
static void update_syslog_server(MYSQL_THD thd __attribute__((unused)), struct st_mysql_sys_var *var __attribute__((unused)),
void *var_ptr __attribute__((unused)), const void *save __attribute__((unused)))
{
/* syslog implementation */
}
#ifdef TO_ENABLE_LATER
static int check_file_path(MYSQL_THD thd __attribute__((unused)), struct st_mysql_sys_var *var __attribute__((unused)),
void *save __attribute__((unused)), struct st_mysql_value *value __attribute__((unused)))
{
return output_type == OUTPUT_FILE;
}
static void update_file_path(MYSQL_THD thd __attribute__((unused)), struct st_mysql_sys_var *var __attribute__((unused)),
void *var_ptr __attribute__((unused)), const void *save __attribute__((unused)))
{
}
static void switch_logging(MYSQL_THD thd __attribute__((unused)), struct st_mysql_sys_var *var __attribute__((unused)),
void *var_ptr __attribute__((unused)), const void *save __attribute__((unused)))
{
}
#endif /*TO_ENABLE_LATER*/
Follow ups
References