[PATCH] Add MySQL support to vfs_full_audit

Adam Nielsen adam.nielsen at uq.edu.au
Fri Aug 28 04:11:05 MDT 2009


Hi all,

I discovered after a recent Samba upgrade that it is no longer possible
to log all Samba activity to a MySQL database.  (There is a VFS module
on SourceForge that I was using but it's Samba 3.0 only, which goes to
show just how old my Samba installation was.)

So to permit my upgrade to go ahead (and because Volker sounded
enthusiastic in samba-users) I have added MySQL support to the
vfs_full_audit module in Samba 3.4, and I attach the patch.  I hope you
consider it worthwhile to include in the main Samba distribution.

The patch is against the source3 module in the latest git (which I
can't fully test as the git version is crashing at the moment, but I
developed the code with Samba 3.4.0 and it works well.)

There are a couple of issues that need to be addressed before the patch
is ready to be applied - namely:

  1. The patch has been designed so that MySQL support can be compiled
in or out to avoid a dependency, but I'm not familiar enough with the
Samba build process to fully integrate this.  (I just added
-lmysqlclient to the Makefile and it worked fine for testing.)  I hope
somebody can help me out in the form of a --with-mysql configure option.

  2. I have a sample PHP script (one file) which provides an easy web
interface to the collected data.  I would like to include this somewhere
in the distribution so that people can easily install it on their
webserver, but I'm not sure where it would fit best.

And of course please tell me if I have gone about this patch in entirely
the wrong way :-)

Many thanks,
Adam.

P.S. Sorry about the patching mess in do_log() - my git-fu isn't good
enough to make it diff nicely...

-----

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 <mysql/mysql.h>
+#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));


More information about the samba-technical mailing list