diff --git a/source3/modules/vfs_full_audit.c b/source3/modules/vfs_full_audit.c index 0f6de79..2e2ad59 100644 --- a/source3/modules/vfs_full_audit.c +++ b/source3/modules/vfs_full_audit.c @@ -7,6 +7,7 @@ * Copyright (C) John H Terpstra, 2003 * Copyright (C) Stefan (metze) Metzmacher, 2003 * Copyright (C) Volker Lendecke, 2004 + * Copyright (C) Adam Nielsen, 2009 (MySQL support) * * 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 @@ -33,6 +34,12 @@ * full_audit:prefix = %u|%I * full_audit:success = open opendir * full_audit:failure = all + * full_audit:dest = syslog mysql + * full_audit:myhost = localhost + * full_audit:myport = 1234 + * full_audit:myuser = samba + * full_audit:mypass = secret + * full_audit:mydb = sambadb * * vfs op can be "all" which means log all operations. * vfs op can be "none" which means no logging. @@ -54,8 +61,26 @@ * * failure: A list of VFS operations for which failure to complete should be * logged. Defaults to logging everything. + * + * dest: Destination for logging messages, valid values are "syslog" and + * "mysql". Defaults to "syslog". Separate multiple values with a space to + * log the same events to different places. + * + * The following my* options are only used when "mysql" is included in the + * "dest" option: + * + * myhost: Hostname or IP of MySQL server (default is "localhost") + * myport: TCP port (optional, defaults to MySQL standard) + * myuser: MySQL username + * mypass: MySQL password + * mydb : MySQL database name (default to "samba") */ +#define USE_MYSQL + +#ifdef USE_MYSQL +#include +#endif #include "includes.h" @@ -64,8 +89,37 @@ static int vfs_full_audit_debug_level = DBGC_VFS; struct vfs_full_audit_private_data { struct bitmap *success_ops; struct bitmap *failure_ops; + bool use_syslog; +#ifdef USE_MYSQL + bool use_mysql; + MYSQL *mysql; + MYSQL_STMT *mysql_insert; + MYSQL_BIND mysql_insert_values[10]; +#endif }; +#define MYSQL_INSERT_STMT "INSERT INTO `audit` (`share`, `ip`, `unix_name`, " \ + "`connectpath`, `sanitized_username`, `sam_account`, `op`, `op_msg`, " \ + "`utok.gid`, `errno`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" + +/* +USE `samba`; +CREATE TABLE `audit` ( + `id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + `when` TIMESTAMP DEFAULT NOW() COMMENT 'Time the operation occurred', + `share` VARCHAR(255) COMMENT 'Share/service name', + `ip` VARCHAR(255) COMMENT 'IP address of connecting user', + `unix_name` VARCHAR(255) COMMENT 'Local username user has connected as', + `connectpath` VARCHAR(255) COMMENT 'Local path', + `sanitized_username` VARCHAR(255) COMMENT 'Remote username', + `sam_account` VARCHAR(255) COMMENT 'Remote machine name', + `op` VARCHAR(255) COMMENT 'Type of operation performed', + `op_msg` VARCHAR(255) COMMENT 'Further operation-specific details', + `utok.gid` VARCHAR(255) COMMENT 'Local group ID', + `errno` INT COMMENT 'POSIX error code (0 == success)' +) ENGINE = MYISAM; +*/ + #undef DBGC_CLASS #define DBGC_CLASS vfs_full_audit_debug_level @@ -400,14 +454,8 @@ static char *audit_prefix(TALLOC_CTX *ctx, connection_struct *conn) return result; } -static bool log_success(vfs_handle_struct *handle, vfs_op_type op) +static bool log_success(struct vfs_full_audit_private_data *pd, vfs_op_type op) { - struct vfs_full_audit_private_data *pd = NULL; - - SMB_VFS_HANDLE_GET_DATA(handle, pd, - struct vfs_full_audit_private_data, - return True); - if (pd->success_ops == NULL) { return True; } @@ -415,14 +463,8 @@ static bool log_success(vfs_handle_struct *handle, vfs_op_type op) return bitmap_query(pd->success_ops, op); } -static bool log_failure(vfs_handle_struct *handle, vfs_op_type op) +static bool log_failure(struct vfs_full_audit_private_data *pd, vfs_op_type op) { - struct vfs_full_audit_private_data *pd = NULL; - - SMB_VFS_HANDLE_GET_DATA(handle, pd, - struct vfs_full_audit_private_data, - return True); - if (pd->failure_ops == NULL) return True; @@ -484,6 +526,95 @@ static void init_bitmap(struct bitmap **bm, const char **ops) } } +#ifdef USE_MYSQL +/* MySQL logging should be disabled if this function returns false */ +static bool init_mysql(vfs_handle_struct *handle, struct vfs_full_audit_private_data *pd) +{ + const char *myhost, *mydb; + const char *myuser, *mypass; + int myport; + + myhost = lp_parm_const_string(SNUM(handle->conn), "full_audit", + "myhost", "localhost"); + myport = lp_parm_int(SNUM(handle->conn), "full_audit", + "myport", 0); + myuser = lp_parm_const_string(SNUM(handle->conn), "full_audit", + "myuser", NULL); + mypass = lp_parm_const_string(SNUM(handle->conn), "full_audit", + "mypass", NULL); + mydb = lp_parm_const_string(SNUM(handle->conn), "full_audit", + "mydb", "samba"); + + pd->mysql = mysql_init(NULL); + if (pd->mysql == NULL) { + DEBUG(0,("Unable to initialise MySQL\n")); + return false; + } + + if (!mysql_real_connect(pd->mysql, myhost, myuser, mypass, mydb, myport, NULL, 0)) { + DEBUG(0,("Unable to connect to MySQL: [%u] %s\n", + mysql_errno(pd->mysql), mysql_error(pd->mysql))); + mysql_close(pd->mysql); + return false; + } + + pd->mysql_insert = mysql_stmt_init(pd->mysql); + if (!pd->mysql_insert) { + DEBUG(0,("mysql_stmt_init() failed, logging to MySQL disabled\n")); + mysql_close(pd->mysql); + return false; + } + + if (mysql_stmt_prepare(pd->mysql_insert, MYSQL_INSERT_STMT, strlen(MYSQL_INSERT_STMT))) { + DEBUG(0,("Unable to prepare MySQL INSERT statement, " + "logging to MySQL disabled: %s\n", + mysql_stmt_error(pd->mysql_insert))); + mysql_close(pd->mysql); + return false; + } + + /* Preset some values in the INSERT query structure used in do_log() */ + memset(pd->mysql_insert_values, 0, sizeof(pd->mysql_insert_values)); + + pd->mysql_insert_values[0].buffer_type = MYSQL_TYPE_STRING; + pd->mysql_insert_values[1].buffer_type = MYSQL_TYPE_STRING; + pd->mysql_insert_values[2].buffer_type = MYSQL_TYPE_STRING; + pd->mysql_insert_values[3].buffer_type = MYSQL_TYPE_STRING; + pd->mysql_insert_values[4].buffer_type = MYSQL_TYPE_STRING; + pd->mysql_insert_values[5].buffer_type = MYSQL_TYPE_STRING; + pd->mysql_insert_values[6].buffer_type = MYSQL_TYPE_STRING; + pd->mysql_insert_values[7].buffer_type = MYSQL_TYPE_STRING; + pd->mysql_insert_values[8].buffer_type = MYSQL_TYPE_LONG; + pd->mysql_insert_values[9].buffer_type = MYSQL_TYPE_LONG; + + return true; +} +#endif + +static void init_destinations(struct vfs_full_audit_private_data *pd, const char **dests) +{ + + while (*dests != NULL) { + + if (strequal(*dests, "syslog")) { + pd->use_syslog = true; + } else if (strequal(*dests, "mysql")) { +#ifdef USE_MYSQL + pd->use_mysql = true; +#else + DEBUG(0, ("MySQL support not compiled in, cannot log " + "audit data to MySQL\n")); +#endif + } else { + DEBUG(0, ("Unknown full_audit log destination %s\n", + *dests)); + } + + dests += 1; + } + +} + static const char *audit_opname(vfs_op_type op) { if (op >= SMB_VFS_OP_LAST) @@ -511,17 +642,21 @@ static void do_log(vfs_op_type op, bool success, vfs_handle_struct *handle, va_list ap; char *op_msg = NULL; int priority; + struct vfs_full_audit_private_data *pd = NULL; +#ifdef USE_MYSQL + int i; + int my_errno; +#endif - if (success && (!log_success(handle, op))) - goto out; + SMB_VFS_HANDLE_GET_DATA(handle, pd, + struct vfs_full_audit_private_data, + return); - if (!success && (!log_failure(handle, op))) + if (success && (!log_success(pd, op))) goto out; - if (success) - fstrcpy(err_msg, "ok"); - else - fstr_sprintf(err_msg, "fail (%s)", strerror(errno)); + if (!success && (!log_failure(pd, op))) + goto out; va_start(ap, format); op_msg = talloc_vasprintf(talloc_tos(), format, ap); @@ -531,20 +666,57 @@ static void do_log(vfs_op_type op, bool success, vfs_handle_struct *handle, goto out; } - /* - * Specify the facility to interoperate with other syslog callers - * (smbd for example). - */ - priority = audit_syslog_priority(handle) | - audit_syslog_facility(handle); + if (pd->use_syslog) { + if (success) + fstrcpy(err_msg, "ok"); + else + fstr_sprintf(err_msg, "fail (%s)", strerror(errno)); + + /* + * Specify the facility to interoperate with other syslog + * callers (smbd for example). + */ + priority = audit_syslog_priority(handle) | + audit_syslog_facility(handle); + + audit_pre = audit_prefix(talloc_tos(), handle->conn); + syslog(priority, "%s|%s|%s|%s\n", + audit_pre ? audit_pre : "", + audit_opname(op), err_msg, op_msg); + TALLOC_FREE(audit_pre); + } - audit_pre = audit_prefix(talloc_tos(), handle->conn); - syslog(priority, "%s|%s|%s|%s\n", - audit_pre ? audit_pre : "", - audit_opname(op), err_msg, op_msg); +#ifdef USE_MYSQL + if (pd->use_mysql) { + pd->mysql_insert_values[0].buffer = lp_servicename(SNUM(handle->conn)); + pd->mysql_insert_values[1].buffer = handle->conn->client_address; + pd->mysql_insert_values[2].buffer = handle->conn->server_info->unix_name; + pd->mysql_insert_values[3].buffer = handle->conn->connectpath; + pd->mysql_insert_values[4].buffer = handle->conn->server_info->sanitized_username; + pd->mysql_insert_values[5].buffer = (char *)pdb_get_domain(handle->conn->server_info->sam_account); + pd->mysql_insert_values[6].buffer = (char *)audit_opname(op); + pd->mysql_insert_values[7].buffer = op_msg; + /* non-char variables are last so we don't strlen() them */ + pd->mysql_insert_values[8].buffer = &handle->conn->server_info->utok.gid; + pd->mysql_insert_values[9].buffer = &my_errno; + for (i = 0; i < 8; i++) { /* This loop is meant to stop at i==7 */ + pd->mysql_insert_values[i].buffer_length = + strlen(pd->mysql_insert_values[i].buffer); + } + /* Need to do this because errno is not always 0 on success */ + if (success) my_errno = 0; else my_errno = errno; + + if (mysql_stmt_bind_param(pd->mysql_insert, pd->mysql_insert_values)) { + DEBUG(0,("Unable to bind values to MySQL INSERT statement: %s\n", + mysql_stmt_error(pd->mysql_insert))); + } else if (mysql_stmt_execute(pd->mysql_insert)) { + DEBUG(0,("Unable to INSERT into MySQL: %s\n", + mysql_stmt_error(pd->mysql_insert))); + } + } +#endif out: - TALLOC_FREE(audit_pre); TALLOC_FREE(op_msg); TALLOC_FREE(tmp_do_log_ctx); @@ -589,6 +761,11 @@ static void free_private_data(void **p_data) if (pd->failure_ops) { bitmap_free(pd->failure_ops); } +#ifdef USE_MYSQL + if (pd->use_mysql) { + mysql_close(pd->mysql); + } +#endif SAFE_FREE(pd); *p_data = NULL; } @@ -603,6 +780,7 @@ static int smb_full_audit_connect(vfs_handle_struct *handle, struct vfs_full_audit_private_data *pd = NULL; const char *none[] = { NULL }; const char *all [] = { "all" }; + const char *dest_default[] = { "syslog" }; if (!handle) { return -1; @@ -614,10 +792,23 @@ static int smb_full_audit_connect(vfs_handle_struct *handle, } ZERO_STRUCTP(pd); + init_destinations(pd, lp_parm_string_list(SNUM(handle->conn), + "full_audit", "dest", dest_default)); + #ifndef WITH_SYSLOG - openlog("smbd_audit", 0, audit_syslog_facility(handle)); + if (pd->use_syslog) { + openlog("smbd_audit", 0, audit_syslog_facility(handle)); + } #endif +#ifdef USE_MYSQL + if (pd->use_mysql) { + if (!init_mysql(handle, pd)) { + /* Just ignore MySQL if it couldn't be initialised */ + pd->use_mysql = false; + } + } +#endif init_bitmap(&pd->success_ops, lp_parm_string_list(SNUM(handle->conn), "full_audit", "success", none));