--- /dev/null
+Add support for logging daemon messages to an SQL database.
+
+After applying this patch you'll need to run autoconf and autoheader to
+generate updated versions of "configure" and "config.h.in".
+
+You'll need to run configure with the --with-ODBC option in order for the
+extended features to be active.
+
+Patch provided by Steve Sether.
+
+(Tweaked by Wayne Davison for rsync-style purposes but not compiled, so if the
+dblog.c file has a compile problem, it's probably my fault...)
+
+
+--- README-ODBC 2004-02-13 17:08:33.000000000 -0800
++++ README-ODBC 2004-04-07 22:42:28.000000000 -0700
+@@ -0,0 +1,80 @@
++This patch adds the following options:
++
++"database logging"
++If set to True, rsync will attempt to connect to
++the specified datasource and write to the named tables.
++Defaults to False.
++
++
++"database datasource"
++Specifies the name of the ODBC data source to use.
++
++"database username"
++The username to use when connecting to the database.
++
++"database password"
++The password to use when connecting to the database.
++
++"transfer table name"
++The name of the transfer table to log to.
++This table contains individual filenames, file sizes, bytes transferred, checksum bytes transferred, operation (send or receive), and a timestamp.
++
++"session table name"
++The name of the session table to log to.
++This table contains the username, module name, module path, ip address, process ID, and a timestamp.
++
++"exit table name"
++The name of the exit table to log to.
++This table contains the total bytes read, total bytes written, total size
++of all files, error code, line the error occured at, file the error occured at
++and the text of the error. (most of which will be blank if the program exited normally).
++
++
++"unique id method"
++Different databases use different methods to get a unique identifier.
++Some databases support sequence objects, and use various forms of the
++nextval command to retrieve a unique identifier from it. Other databases
++support an autonumber field, and support different methds of retrieving
++the ID used in the last insert. Valid values for this option are:
++
++ nextval-postgres
++ uses the syntax of nextval for PostgreSQL databases
++
++ nextval-oracle
++ uses the syntax of nextval for Oracle databases
++
++ nextval-db2
++ uses the syntax of nextval for DB2 databases
++
++ last_insert_id
++ uses the last_insert_id() command for the MySQL databases
++
++ @@IDENTITY
++ uses the @@IDENTITY command for Sybase databases
++
++ custom
++ Define your own method to get a unique identifier. See the
++ "custom unique id select", and the "get custom id before select"
++ parameters.
++
++
++"sequence name"
++If your database supports sequences, list the name of the sequence to use
++for the session unique identifier.
++
++"custom unique id select"
++Only used if you specify the custom method in "unique id method". This
++is a SQL statement to be executed to get a unique ID. This SQL
++statement must return one column with the unique ID to use for
++the session ID. Should be used in concert with the "get custom
++id before select" parameter.
++
++"get custom id before insert"
++This parameter is ignored unless the "unique id method" is set to custom.
++If set to true, the "custom unique id select" statement will
++be executed BEFORE the session row is inserted into the database.
++(as is done when a sequence is used for unique IDs).
++If False the statement will be executed after the session
++row is inserted (as is done when the session ID is automatically generates
++unique IDs).
++Defaults to True.
+--- Makefile.in 10 Feb 2004 17:06:11 -0000 1.98
++++ Makefile.in 8 Apr 2004 05:56:31 -0000
+@@ -32,7 +32,7 @@
+ zlib/inflate.o zlib/inftrees.o zlib/infutil.o zlib/trees.o \
+ zlib/zutil.o zlib/adler32.o
+ OBJS1=rsync.o generator.o receiver.o cleanup.o sender.o exclude.o util.o \
+- main.o checksum.o match.o syscall.o log.o backup.o
++ main.o checksum.o match.o syscall.o log.o backup.o @EXTRA_OBJECT@
+ OBJS2=options.o flist.o io.o compat.o hlink.o token.o uidlist.o socket.o \
+ fileio.o batch.o clientname.o
+ OBJS3=progress.o pipe.o
+--- cleanup.c 27 Jan 2004 08:14:33 -0000 1.21
++++ cleanup.c 8 Apr 2004 05:56:31 -0000
+@@ -138,7 +138,12 @@
+ code = RERR_VANISHED;
+ }
+
+- if (code) log_exit(code, file, line);
++ if (code) {
++ log_exit(code, file, line);
++#ifdef HAVE_LIBODBC
++ db_log_exit(code,file,line);
++#endif
++ }
+
+ if (verbose > 2)
+ rprintf(FINFO,"_exit_cleanup(code=%d, file=%s, line=%d): about to call exit(%d)\n",
+--- clientserver.c 27 Mar 2004 09:44:01 -0000 1.118
++++ clientserver.c 8 Apr 2004 05:56:31 -0000
+@@ -316,6 +316,9 @@
+ exclude_path_prefix = NULL;
+
+ log_init();
++#ifdef HAVE_LIBODBC
++ db_log_open();
++#endif
+
+ if (use_chroot) {
+ /*
+@@ -434,6 +437,9 @@
+ rprintf(FINFO,"rsync %s %s from %s@%s (%s)\n",
+ am_sender?"on":"to",
+ request, auth_user, host, addr);
++#ifdef HAVE_LIBODBC
++ db_log_session();
++#endif
+ } else {
+ rprintf(FINFO,"rsync %s %s from %s (%s)\n",
+ am_sender?"on":"to",
+--- configure.in 24 Mar 2004 21:59:07 -0000 1.188
++++ configure.in 8 Apr 2004 05:56:31 -0000
+@@ -94,6 +94,8 @@
+ [ --with-rsync-path=PATH set default --rsync-path to PATH (default: rsync)],
+ [ RSYNC_PATH="$with_rsync_path" ],
+ [ RSYNC_PATH="rsync" ])
++AC_ARG_WITH(ODBC,
++ [ --with-ODBC compile in support for ODBC database logging])
+
+ AC_DEFINE_UNQUOTED(RSYNC_PATH, "$RSYNC_PATH", [location of rsync on remote machine])
+
+@@ -459,6 +461,14 @@
+ if test x"$with_included_popt" != x"yes"
+ then
+ AC_CHECK_LIB(popt, poptGetContext, , [with_included_popt=yes])
++fi
++
++if test x"$with_ODBC" = x"yes"
++then
++ AC_CHECK_HEADERS(sql.h sqlext.h sqltypes.h)
++ AC_CHECK_LIB(odbc,SQLExecDirect)
++ EXTRA_OBJECT="$EXTRA_OBJECT dblog.o"
++ AC_SUBST(EXTRA_OBJECT)
+ fi
+
+ AC_MSG_CHECKING([whether to use included libpopt])
+--- dblog.c 2004-02-13 17:08:33.000000000 -0800
++++ dblog.c 2004-04-07 23:18:56.000000000 -0700
+@@ -0,0 +1,352 @@
++/*
++ * ODBC Database logging functions
++ *
++ * Written by Steve Sether, April 2004
++ * steve@vellmont.com
++ */
++
++#include "rsync.h"
++
++#ifdef HAVE_SQL_H
++#include <sql.h>
++#else
++#ifdef HAVE_ODBC_SQL_H
++#include <odbc/sql.h>
++#endif
++#endif
++
++#ifdef HAVE_SQLEXT_H
++#include <sqlext.h>
++#else
++#ifdef HAVE_ODBC_SQLEXT_H
++#include <odbc/sqlext.h>
++#endif
++#endif
++
++#ifdef HAVE_SQLTYPES_H
++#include <sqltypes.h>
++#else
++#ifdef HAVE_ODBC_SQLTYPES_H
++#include <odbc/sqltypes.h>
++#endif
++#endif
++
++SQLHENV db_environ_handle; /* Handle ODBC environment */
++long result; /* result of functions */
++SQLHDBC db_handle= NULL; /* database connection handle */
++SQLHSTMT sql_statement_handle; /* SQL statement handle */
++extern int am_sender;
++extern char *auth_user;
++extern int module_id;
++
++char sql_status[10]; /* Status SQL */
++SQLINTEGER V_OD_err, V_OD_rowanz, V_OD_id;
++SQLSMALLINT V_OD_mlen, V_OD_colanz;
++char V_OD_msg[200], V_OD_buffer[200];
++SQLINTEGER session_id;
++
++
++/* This function simply removes invalid characters from the SQL statement
++ * to prevent SQL injection attacks. */
++char *sanitizeSql(const char *input)
++{
++ char *out, *ptr;
++ const char *c;
++
++ if (strlen(input) > ((~(unsigned int)0)>>1)-3)
++ return 0;
++ if (!(out = ptr = new_array(char, strlen(input) * 2 + 1)))
++ return 0;
++
++ for (c = input; *c; c++) {
++ switch (*c) {
++ case '\'':
++ *ptr++ = '\'';
++ *ptr++ = '\'';
++ break;
++ case '\b':
++ *ptr++ = '\\';
++ *ptr++ = 'b';
++ break;
++ case '\n':
++ *ptr++ = '\\';
++ *ptr++ = 'n';
++ break;
++ case '\r':
++ *ptr++ = '\\';
++ *ptr++ = 'r';
++ break;
++ case '\t':
++ *ptr++ = '\\';
++ *ptr++ = 't';
++ break;
++ default:
++ *ptr++ = *c;
++ break;
++ }
++ }
++ *ptr = '\0';
++ return out;
++}
++
++void db_log_open(void)
++{
++ if (lp_database_logging(module_id)) {
++ if (db_handle == NULL) {
++ /* get ODBC environment handle */
++ result = SQLAllocHandle(SQL_HANDLE_ENV,SQL_NULL_HANDLE,&db_environ_handle);
++ if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
++ rprintf(FERROR, "Error: couldn't get database environment handle\n");
++ return;
++ }
++
++ /* Setting database enviroment */
++ result = SQLSetEnvAttr(db_environ_handle, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
++ if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
++ rprintf(FERROR, "Error: couldn't set database environment.\n");
++ SQLFreeHandle(SQL_HANDLE_ENV, db_environ_handle);
++ db_environ_handle = NULL;
++ return;
++ }
++
++ /* Get a database handle */
++ result = SQLAllocHandle(SQL_HANDLE_DBC, db_environ_handle, &db_handle);
++ if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
++ rprintf(FERROR, "Error: couldn't allocate database handle\n");
++ SQLFreeHandle(SQL_HANDLE_ENV, db_environ_handle);
++ db_environ_handle = NULL;
++ return;
++ }
++
++ /* Set connection attributes */
++ SQLSetConnectAttr(db_handle, SQL_LOGIN_TIMEOUT, (SQLPOINTER *)5, 0);
++
++ /* Connect to the database. */
++ result = SQLConnect(db_handle, (SQLCHAR*) lp_database_datasource(module_id), SQL_NTS,
++ (SQLCHAR*) lp_database_username(module_id), SQL_NTS,
++ (SQLCHAR*) lp_database_password(module_id), SQL_NTS);
++
++ if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
++ SQLGetDiagRec(SQL_HANDLE_DBC, db_handle,1,
++ sql_status, &V_OD_err,V_OD_msg,100,&V_OD_mlen);
++ rprintf(FERROR,"Error Connecting to Database %s\n",V_OD_msg);
++ SQLFreeHandle(SQL_HANDLE_DBC,db_handle);
++ db_handle = NULL;
++ SQLFreeHandle(SQL_HANDLE_ENV, db_environ_handle);
++ db_environ_handle = NULL;
++ return;
++ }
++ rprintf(FLOG,"Connected to database!\n");
++
++ /* get SQL statement handle */
++ result = SQLAllocHandle(SQL_HANDLE_STMT, db_handle, &sql_statement_handle);
++ if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
++ SQLGetDiagRec(SQL_HANDLE_DBC, db_handle,1, sql_status,&V_OD_err,V_OD_msg,100,&V_OD_mlen);
++ rprintf(FERROR,"Error in allocating SQL statement handle %s\n",V_OD_msg);
++ SQLDisconnect(db_handle);
++ SQLFreeHandle(SQL_HANDLE_DBC,db_handle);
++ db_handle = NULL;
++ SQLFreeHandle(SQL_HANDLE_ENV, db_environ_handle);
++ db_environ_handle = NULL;
++ return;
++ }
++ } else {
++ rprintf(FERROR,"Already connected to database\n");
++ }
++ }
++}
++
++void db_log_close()
++{
++ if (lp_database_logging(module_id)) {
++ if (sql_statement_handle != NULL) {
++ /* free the statement handle first */
++ SQLFreeHandle(SQL_HANDLE_STMT,sql_statement_handle);
++ sql_statement_handle = NULL;
++ } else {
++ rprintf(FERROR,"No sql statement handle to close\n");
++ }
++ if (db_handle != NULL) {
++ /* disconnect, and free the database handle. */
++ SQLDisconnect(db_handle);
++ SQLFreeHandle(SQL_HANDLE_DBC,db_handle);
++ db_handle = NULL;
++ } else {
++ rprintf(FERROR,"Database already closed");
++ }
++ if (db_environ_handle != NULL) {
++ /* free the environment handle */
++ SQLFreeHandle(SQL_HANDLE_ENV, db_environ_handle);
++ db_environ_handle = NULL;
++ } else {
++ rprintf(FERROR,"No environment handle to close");
++ }
++ }
++}
++
++static long get_unique_session_id()
++{
++ long unique;
++ char strSqlStatement[1024];
++
++ if (db_handle != NULL) {
++ /* choose the appropriate select statement based upon which DBMS we're using.
++ * different datbases use different methods to get a unique ID. Some use a counter
++ * object (sequence), others use an auto increment datatype and have a method
++ * to get the last ID inserted using this connection. */
++ if (strcmp(lp_unique_id_method(module_id),"nextval-postgresql") == 0) {
++ snprintf(strSqlStatement,sizeof(strSqlStatement),"SELECT NEXTVAL('%s');",lp_sequence_name(module_id));
++ } else if (strcmp(lp_unique_id_method(module_id),"nextval-oracle") == 0) {
++ snprintf(strSqlStatement,sizeof(strSqlStatement),"SELECT %s.NEXTVAL FROM dual;",lp_sequence_name(module_id));
++ } else if (strcmp(lp_unique_id_method(module_id),"nextval-db2") == 0) {
++ snprintf(strSqlStatement,sizeof(strSqlStatement),"VALUES NEXTVAL FOR %s;",lp_sequence_name(module_id));
++ } else if (strcmp(lp_unique_id_method(module_id),"last_insert_id") == 0) { /* MySql */
++ snprintf(strSqlStatement,sizeof(strSqlStatement),"SELECT LAST_INSERT_ID()");
++ } else if (strcmp(lp_unique_id_method(module_id),"@@IDENTITY") == 0) { /* Sybase */
++ snprintf(strSqlStatement,sizeof(strSqlStatement),"SELECT @@IDENTITY");
++ } else if (strcmp(lp_unique_id_method(module_id),"custom") == 0){ /* Users custom statement */
++ snprintf(strSqlStatement,sizeof(strSqlStatement),lp_custom_unique_id_select(module_id));
++ }
++
++ /* bind the 1st column to unique */
++ SQLBindCol(sql_statement_handle,1,SQL_C_LONG,&unique,150,&V_OD_err);
++ /* execute the SQL statement */
++ result = SQLExecDirect(sql_statement_handle,strSqlStatement,SQL_NTS);
++ if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
++ SQLGetDiagRec(SQL_HANDLE_DBC, db_handle,1, sql_status,&V_OD_err,V_OD_msg,100,&V_OD_mlen);
++ rprintf(FERROR,"Error at get_sequence: Error in Select! %s %s\n",strSqlStatement,V_OD_msg);
++ } else {
++ result = SQLFetch(sql_statement_handle);
++ if (result != SQL_NO_DATA && unique != 0) {
++ rprintf(FINFO,"Got unique sequence! %ld\n",unique);
++ } else {
++ rprintf(FERROR,"Error at get_sequence: Didn't get unique session ID\n");
++ }
++ /* Close the cursor so the statement can be re-used */
++ result = SQLFreeStmt(sql_statement_handle,SQL_CLOSE);
++ if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
++ SQLGetDiagRec(SQL_HANDLE_DBC, db_handle,1, sql_status,&V_OD_err,V_OD_msg,100,&V_OD_mlen);
++ rprintf(FERROR,"Error at get_sequence: Error in closing SQL statement handle %s\n",V_OD_msg);
++ return unique;
++ }
++ return unique;
++ }
++ }
++ rprintf(FERROR,"Error at get_sequence: Not connected to database\n");
++ return -1;
++}
++
++
++void db_log_session()
++{
++ char strSqlStatement[1024];
++ int gotSessionID = 0;
++ if (lp_database_logging(module_id)) {
++ /* if we're using a sequence via the nextval command to get a unique ID, we need to get it before
++ * we do the insert. We also get the unique ID now if custom, and get_custom_id_before_insert is set. */
++ if (strcmp(lp_unique_id_method(module_id),"nextval-postgresql") == 0
++ || strcmp(lp_unique_id_method(module_id),"nextval-oracle") == 0
++ || strcmp(lp_unique_id_method(module_id),"nextval-db2") == 0
++ || (strcmp(lp_unique_id_method(module_id),"custom") == 0
++ && lp_get_custom_id_before_insert(module_id))) {
++ session_id = get_unique_session_id();
++ gotSessionID = 1;
++ snprintf(strSqlStatement,sizeof(strSqlStatement),"INSERT INTO %s (id, date, ip_address, username, module_name, module_path, process_id) VALUES ('%ld', '%s', '%s', '%s','%s','%s','%d');",lp_session_table_name(module_id),session_id,timestring(time(NULL)),client_addr(0),auth_user,lp_name(module_id),lp_path(module_id),getpid());
++ } else {
++ /* Otherwise the ID gets created automatically, and we get the ID it used after the insert. */
++ snprintf(strSqlStatement,sizeof(strSqlStatement),"INSERT INTO %s (date, ip_address, username, module_name, module_path, process_id) VALUES ('%s', '%s', '%s', '%s','%s','%d');",lp_session_table_name(module_id),timestring(time(NULL)),client_addr(0),auth_user,lp_name(module_id),lp_path(module_id),getpid());
++ }
++
++ /* Insert the new session into the database */
++ result = SQLExecDirect(sql_statement_handle,strSqlStatement,SQL_NTS);
++ if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
++ SQLGetDiagRec(SQL_HANDLE_DBC, db_handle,1, sql_status,&V_OD_err,V_OD_msg,100,&V_OD_mlen);
++ rprintf(FERROR,"Error at db_log_session: Error in Insert %s %s\n",strSqlStatement,V_OD_msg);
++ }
++
++ /* close the cursor so the statement handle can be re-used. */
++ result = SQLFreeStmt(sql_statement_handle,SQL_CLOSE);
++ if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
++ SQLGetDiagRec(SQL_HANDLE_DBC, db_handle,1, sql_status,&V_OD_err,V_OD_msg,100,&V_OD_mlen);
++ rprintf(FERROR,"Error in resetting SQL statement handle %s\n",V_OD_msg);
++ }
++ /* get the session ID for databases that give the unique ID after an insert */
++ if (gotSessionID == 0) {
++ session_id = get_unique_session_id();
++ }
++ }
++ else {
++ rprintf(FERROR,"Error at db_log_session: Not connected to database!\n");
++ }
++}
++
++void db_log_transfer(struct file_struct *file,struct stats *initial_stats,char *operation)
++{
++ extern struct stats stats;
++ char strSqlStatement[1024];
++ char strFilePath[255];
++ char strFileName[255];
++ char strFileSize[255];
++ int64 intBytesTransferred;
++ int64 intCheckSumBytes;
++
++ if (lp_database_logging(module_id)) {
++ if (db_handle != NULL) {
++ snprintf(strFileName,sizeof(strFileName), "%s",f_name(file));
++ snprintf(strFilePath, sizeof(strFilePath), "%s", file->basedir?file->basedir:"");
++ snprintf(strFileSize,sizeof(strFileSize),"%.0f", (double)file->length);
++ if (am_sender) {
++ intBytesTransferred = stats.total_written - initial_stats->total_written;
++ } else {
++ intBytesTransferred = stats.total_read - initial_stats->total_read;
++ }
++
++ if (!am_sender) {
++ intCheckSumBytes = stats.total_written - initial_stats->total_written;
++ } else {
++ intCheckSumBytes = stats.total_read - initial_stats->total_read;
++ }
++
++ snprintf(strSqlStatement,sizeof(strSqlStatement),"INSERT INTO %s (session_id,date,file_path, file_name, file_size, bytes_transferred, checksum_bytes_transferred, operation) VALUES ('%ld','%s','%s','%s','%s','%Ld','%Ld','%s');",lp_transfer_table_name(module_id),session_id,timestring(time(NULL)),sanitizeSql(strFilePath),sanitizeSql(strFileName),strFileSize,intBytesTransferred,intCheckSumBytes,operation);
++ result = SQLExecDirect(sql_statement_handle,strSqlStatement,SQL_NTS);
++ if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
++ SQLGetDiagRec(SQL_HANDLE_DBC, db_handle,1, sql_status,&V_OD_err,V_OD_msg,100,&V_OD_mlen);
++ rprintf(FERROR,"Error at db_log_transfer: Error in Insert %s %s\n",strSqlStatement,V_OD_msg);
++ if (result == SQL_INVALID_HANDLE)
++ rprintf(FERROR,"INVALID HANDLE\n");
++ }
++ } else {
++ rprintf(FERROR,"Error at db_log_transfer: Not connected to database!\n");
++ }
++ }
++}
++
++
++void db_log_exit(int code, const char *file, int line)
++{
++ char strSqlStatement[2048];
++ const char *error_text;
++ extern struct stats stats;
++ if (db_handle != NULL) {
++ if (lp_database_logging(module_id)) {
++ if (code != 0) {
++ error_text = rerr_name(code);
++ if (!error_text) {
++ error_text = "unexplained error";
++ }
++ } else {
++ error_text = "";
++ }
++ snprintf(strSqlStatement,sizeof(strSqlStatement),"INSERT INTO %s (session_id, date, total_bytes_written,total_bytes_read,total_size,error_text,error_code,error_file,error_line) VALUES ('%ld','%s','%Ld','%Ld','%Ld','%s','%d','%s','%d');",lp_exit_table_name(module_id),session_id,timestring(time(NULL)),stats.total_written,stats.total_read,stats.total_size,error_text,code,file,line);
++
++ result = SQLExecDirect(sql_statement_handle,strSqlStatement,SQL_NTS);
++
++ if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
++ SQLGetDiagRec(SQL_HANDLE_DBC, db_handle,1, sql_status,&V_OD_err,V_OD_msg,100,&V_OD_mlen);
++ rprintf(FERROR,"Error at db_log_exit: Error in Insert %s %s\n",strSqlStatement,V_OD_msg);
++ }
++ }
++ } else {
++ rprintf(FERROR,"Error at db_log_exit: Not connected to database!\n");
++ }
++}
+--- dblog-tables-mysql.sql 2004-02-13 17:08:33.000000000 -0800
++++ dblog-tables-mysql.sql 2004-04-07 23:00:13.000000000 -0700
+@@ -0,0 +1,43 @@
++drop table transfer;
++drop table exit;
++drop table session;
++
++CREATE TABLE session (
++ id int auto_increment NOT NULL,
++ date timestamp NOT NULL,
++ ip_address varchar(15) NOT NULL,
++ username varchar(20) NOT NULL,
++ module_name varchar(20) NOT NULL,
++ module_path varchar(255) NOT NULL,
++ process_id int NOT NULL,
++ Primary Key (id)
++);
++
++CREATE TABLE transfer (
++ id int auto_increment NOT NULL,
++ session_id int NOT NULL,
++ date timestamp NOT NULL,
++ file_path varchar(255) NOT NULL,
++ file_name varchar(255) NOT NULL,
++ file_size bigint NOT NULL,
++ bytes_transferred bigint NOT NULL,
++ checksum_bytes_transferred bigint NOT NULL,
++ operation varchar(20),
++ Primary Key (id),
++ foreign key (session_id) references session (id)
++);
++
++CREATE TABLE exit (
++ id int auto_increment NOT NULL,
++ session_id int NOT NULL,
++ date timestamp NOT NULL,
++ total_bytes_written bigint NOT NULL,
++ total_bytes_read bigint NOT NULL,
++ total_size bigint NOT NULL,
++ error_text varchar(128) NOT NULL,
++ error_code int NOT NULL,
++ error_file varchar(64) NOT NULL,
++ error_line int NOT NULL,
++ Primary Key (id),
++ foreign key (session_id) references session (id)
++);
+--- dblog-tables-postgresql.sql 2004-02-13 17:08:33.000000000 -0800
++++ dblog-tables-postgresql.sql 2004-04-07 22:59:55.000000000 -0700
+@@ -0,0 +1,45 @@
++drop table transfer;
++drop table exit;
++drop table session;
++drop sequence session_id_seq;
++create sequence session_id_seq;
++
++CREATE TABLE "session" (
++ "id" int NOT NULL,
++ "date" timestamp NOT NULL default now(),
++ "ip_address" varchar(15) NOT NULL,
++ "username" varchar(20) NOT NULL,
++ "module_name" varchar(20) NOT NULL,
++ "module_path" varchar(255) NOT NULL,
++ "process_id" int NOT NULL,
++ Primary Key (id)
++);
++
++CREATE TABLE "transfer" (
++ "id" serial NOT NULL,
++ "session_id" int NOT NULL,
++ "date" timestamp NOT NULL default now(),
++ "file_path" varchar(512) NOT NULL,
++ "file_name" varchar(512) NOT NULL,
++ "file_size" bigint NOT NULL,
++ "bytes_transferred" bigint NOT NULL,
++ "checksum_bytes_transferred" bigint NOT NULL,
++ "operation" varchar(20),
++ Primary Key (id),
++ foreign key (session_id) references session (id)
++);
++
++CREATE TABLE "exit" (
++ "id" serial NOT NULL,
++ "session_id" int NOT NULL,
++ "date" timestamp NOT NULL default now(),
++ "total_bytes_written" bigint NOT NULL,
++ "total_bytes_read" bigint NOT NULL,
++ "total_size" bigint NOT NULL,
++ "error_text" varchar(128) NOT NULL,
++ "error_code" int NOT NULL,
++ "error_file" varchar(64) NOT NULL,
++ "error_line" int NOT NULL,
++ Primary Key (id),
++ foreign key (session_id) references session (id)
++);
+--- loadparm.c 4 Feb 2004 07:31:29 -0000 1.50
++++ loadparm.c 8 Apr 2004 06:31:18 -0000
+@@ -122,6 +122,17 @@
+ BOOL list;
+ BOOL use_chroot;
+ BOOL transfer_logging;
++ BOOL database_logging;
++ char *database_datasource;
++ char *database_username;
++ char *database_password;
++ char *transfer_table_name;
++ char *exit_table_name;
++ char *session_table_name;
++ char *sequence_name;
++ char *unique_id_method;
++ char *custom_unique_id_select;
++ BOOL get_custom_id_before_insert;
+ BOOL ignore_errors;
+ char *uid;
+ char *gid;
+@@ -154,6 +165,17 @@
+ True, /* list */
+ True, /* use chroot */
+ False, /* transfer logging */
++ False, /* Database Logging */
++ NULL, /* Database datasource */
++ NULL, /* Database username */
++ NULL, /* Database password */
++ NULL, /* Transfer table name */
++ NULL, /* Exit table name */
++ NULL, /* Session table name */
++ NULL, /* sequence name */
++ NULL, /* unique method */
++ NULL, /* custom unique id select*/
++ True, /* get custom id before insert */
+ False, /* ignore errors */
+ "nobody",/* uid */
+
+@@ -292,6 +314,17 @@
+ {"include", P_STRING, P_LOCAL, &sDefault.include, NULL, 0},
+ {"include from", P_STRING, P_LOCAL, &sDefault.include_from,NULL, 0},
+ {"transfer logging", P_BOOL, P_LOCAL, &sDefault.transfer_logging,NULL,0},
++ {"database logging", P_BOOL, P_LOCAL, &sDefault.database_logging,NULL,0},
++ {"database datasource",P_STRING,P_LOCAL, &sDefault.database_datasource,NULL,0},
++ {"database username",P_STRING, P_LOCAL, &sDefault.database_username,NULL,0},
++ {"database password",P_STRING, P_LOCAL, &sDefault.database_password,NULL,0},
++ {"transfer table name",P_STRING,P_LOCAL, &sDefault.transfer_table_name,NULL,0},
++ {"exit table name", P_STRING, P_LOCAL, &sDefault.exit_table_name,NULL,0},
++ {"session table name",P_STRING, P_LOCAL, &sDefault.session_table_name,NULL,0},
++ {"sequence name", P_STRING, P_LOCAL, &sDefault.sequence_name,NULL,0},
++ {"unique id method", P_STRING, P_LOCAL, &sDefault.unique_id_method,NULL,0},
++ {"custom unique id select",P_STRING,P_LOCAL,&sDefault.custom_unique_id_select,NULL,0},
++ {"get custom id before insert",P_BOOL,P_LOCAL,&sDefault.get_custom_id_before_insert,NULL,0},
+ {"ignore errors", P_BOOL, P_LOCAL, &sDefault.ignore_errors,NULL,0},
+ {"log format", P_STRING, P_LOCAL, &sDefault.log_format, NULL, 0},
+ {"refuse options", P_STRING, P_LOCAL, &sDefault.refuse_options,NULL, 0},
+@@ -359,6 +392,17 @@
+ FN_LOCAL_BOOL(lp_list, list)
+ FN_LOCAL_BOOL(lp_use_chroot, use_chroot)
+ FN_LOCAL_BOOL(lp_transfer_logging, transfer_logging)
++FN_LOCAL_BOOL(lp_database_logging, database_logging)
++FN_LOCAL_STRING(lp_database_datasource, database_datasource)
++FN_LOCAL_STRING(lp_database_username, database_username)
++FN_LOCAL_STRING(lp_database_password, database_password)
++FN_LOCAL_STRING(lp_transfer_table_name, transfer_table_name)
++FN_LOCAL_STRING(lp_exit_table_name, exit_table_name)
++FN_LOCAL_STRING(lp_session_table_name,session_table_name)
++FN_LOCAL_STRING(lp_sequence_name,sequence_name)
++FN_LOCAL_STRING(lp_unique_id_method,unique_id_method)
++FN_LOCAL_STRING(lp_custom_unique_id_select,custom_unique_id_select)
++FN_LOCAL_BOOL(lp_get_custom_id_before_insert,get_custom_id_before_insert)
+ FN_LOCAL_BOOL(lp_ignore_errors, ignore_errors)
+ FN_LOCAL_BOOL(lp_ignore_nonreadable, ignore_nonreadable)
+ FN_LOCAL_STRING(lp_uid, uid)
+--- log.c 20 Jan 2004 05:15:14 -0000 1.71
++++ log.c 8 Apr 2004 05:56:32 -0000
+@@ -75,7 +75,7 @@
+ /*
+ * Map from rsync error code to name, or return NULL.
+ */
+-static char const *rerr_name(int code)
++char const *rerr_name(int code)
+ {
+ int i;
+ for (i = 0; rerr_names[i].name; i++) {
+--- main.c 10 Feb 2004 03:54:47 -0000 1.192
++++ main.c 8 Apr 2004 05:56:32 -0000
+@@ -120,6 +120,9 @@
+
+ if (am_daemon) {
+ log_exit(0, __FILE__, __LINE__);
++#ifdef HAVE_LIBODBC
++ db_log_exit(0,__FILE__,__LINE__);
++#endif
+ if (f == -1 || !am_sender) return;
+ }
+
+--- proto.h 27 Mar 2004 09:44:34 -0000 1.185
++++ proto.h 8 Apr 2004 05:56:32 -0000
+@@ -51,6 +51,12 @@
+ int daemon_main(void);
+ void setup_protocol(int f_out,int f_in);
+ int claim_connection(char *fname,int max_connections);
++char *sanitizeSql(const char *input) ;
++void db_log_open(void);
++void db_log_close();
++void db_log_session();
++void db_log_transfer(struct file_struct *file,struct stats *initial_stats,char *operation);
++void db_log_exit(int code, const char *file, int line);
+ void free_exclude_list(struct exclude_struct ***listp);
+ int check_exclude(struct exclude_struct **list, char *name, int name_is_dir);
+ void add_exclude(struct exclude_struct ***listp, const char *pattern, int include);
+@@ -137,6 +143,17 @@
+ BOOL lp_list(int );
+ BOOL lp_use_chroot(int );
+ BOOL lp_transfer_logging(int );
++BOOL lp_database_logging(int );
++char *lp_database_datasource(int );
++char *lp_database_username(int );
++char *lp_database_password(int );
++char *lp_transfer_table_name(int );
++char *lp_exit_table_name(int );
++char *lp_session_table_name(int );
++char *lp_sequence_name(int );
++char *lp_unique_id_method(int );
++char *lp_custom_unique_id_select(int );
++BOOL lp_get_custom_id_before_insert(int );
+ BOOL lp_ignore_errors(int );
+ BOOL lp_ignore_nonreadable(int );
+ char *lp_uid(int );
+@@ -158,6 +175,7 @@
+ BOOL lp_load(char *pszFname, int globals_only);
+ int lp_numservices(void);
+ int lp_number(char *name);
++char const *rerr_name(int code);
+ void log_init(void);
+ void log_open(void);
+ void log_close(void);
+--- receiver.c 23 Mar 2004 16:50:40 -0000 1.75
++++ receiver.c 8 Apr 2004 05:56:32 -0000
+@@ -453,7 +453,9 @@
+ recv_ok = receive_data(f_in,mapbuf,fd2,fname,file->length);
+
+ log_recv(file, &initial_stats);
+-
++#ifdef HAVE_LIBODBC
++ db_log_transfer(file, &initial_stats,"receive");
++#endif
+ if (mapbuf) unmap_file(mapbuf);
+ if (fd1 != -1) {
+ close(fd1);
+--- sender.c 17 Feb 2004 21:57:44 -0000 1.38
++++ sender.c 8 Apr 2004 05:56:32 -0000
+@@ -283,6 +283,9 @@
+ } else {
+ match_sums(f_out, s, buf, st.st_size);
+ log_send(file, &initial_stats);
++#ifdef HAVE_LIBODBC
++ db_log_transfer(file, &initial_stats,"send");
++#endif
+ }
+
+ if (!read_batch) {