1Add support for logging daemon messages to an SQL database.
2
3To use this patch, run these commands for a successful build:
4
5    patch -p1 <patches/ODBC-dblog.diff
6    ./prepare-source
7    ./configure --enable-ODBC
8    make
9
10See the newly-created file "instructions" for more info.
11
12--- old/Makefile.in
13+++ new/Makefile.in
14@@ -32,7 +32,7 @@ LIBOBJ=lib/wildmatch.o lib/compat.o lib/
15 ZLIBOBJ=zlib/deflate.o zlib/inffast.o zlib/inflate.o zlib/inftrees.o \
16 	zlib/trees.o zlib/zutil.o zlib/adler32.o zlib/compress.o zlib/crc32.o
17 OBJS1=rsync.o generator.o receiver.o cleanup.o sender.o exclude.o util.o \
18-	main.o checksum.o match.o syscall.o log.o backup.o
19+	main.o checksum.o match.o syscall.o log.o backup.o @EXTRA_OBJECT@
20 OBJS2=options.o flist.o io.o compat.o hlink.o token.o uidlist.o socket.o \
21 	fileio.o batch.o clientname.o chmod.o
22 OBJS3=progress.o pipe.o
23--- old/cleanup.c
24+++ new/cleanup.c
25@@ -27,6 +27,7 @@ extern int am_server;
26 extern int am_daemon;
27 extern int io_error;
28 extern int keep_partial;
29+extern int am_generator;
30 extern int log_got_error;
31 extern char *partial_dir;
32 extern char *logfile_name;
33@@ -174,8 +175,13 @@ NORETURN void _exit_cleanup(int code, co
34 				code = exit_code = RERR_PARTIAL;
35 		}
36 
37-		if (code || am_daemon || (logfile_name && (am_server || !verbose)))
38+		if (code || am_daemon || (logfile_name && (am_server || !verbose))) {
39 			log_exit(code, file, line);
40+#ifdef HAVE_LIBODBC
41+			db_log_exit(code, file, line);
42+			db_log_close();
43+#endif
44+		}
45 
46 		/* FALLTHROUGH */
47 #include "case_N.h"
48--- old/clientserver.c
49+++ new/clientserver.c
50@@ -394,6 +394,9 @@ static int rsync_module(int f_in, int f_
51 		   XFLG_ABS_IF_SLASH | XFLG_OLD_PREFIXES);
52 
53 	log_init(1);
54+#ifdef HAVE_LIBODBC
55+	db_log_open();
56+#endif
57 
58 #ifdef HAVE_PUTENV
59 	if (*lp_prexfer_exec(i) || *lp_postxfer_exec(i)) {
60@@ -633,6 +636,9 @@ static int rsync_module(int f_in, int f_
61 			rprintf(FLOG, "rsync %s %s from %s@%s (%s)\n",
62 				am_sender ? "on" : "to",
63 				request, auth_user, host, addr);
64+#ifdef HAVE_LIBODBC
65+			db_log_session();
66+#endif
67 		} else {
68 			rprintf(FLOG, "rsync %s %s from %s (%s)\n",
69 				am_sender ? "on" : "to",
70--- old/configure.in
71+++ new/configure.in
72@@ -610,6 +610,12 @@ if test x"$with_included_popt" != x"yes"
73     AC_CHECK_LIB(popt, poptGetContext, , [with_included_popt=yes])
74 fi
75 
76+AC_ARG_ENABLE(ODBC, AC_HELP_STRING([--enable-ODBC], [compile in support for ODBC database logging]),
77+    [ AC_CHECK_HEADERS(sql.h sqlext.h sqltypes.h)
78+    AC_CHECK_LIB(odbc,SQLExecDirect)
79+    EXTRA_OBJECT="$EXTRA_OBJECT dblog.o"
80+    AC_SUBST(EXTRA_OBJECT) ])
81+
82 AC_MSG_CHECKING([whether to use included libpopt])
83 if test x"$with_included_popt" = x"yes"; then
84     AC_MSG_RESULT($srcdir/popt)
85--- old/db_log_error-list.txt
86+++ new/db_log_error-list.txt
87@@ -0,0 +1,35 @@
88+error type		description
89+0			not an error.
90+1			authentication
91+2			file/dir deletion failed
92+3			connection closed
93+4			read error
94+5			multiplexing overflow
95+6			unexpected tag
96+7			over long v-string received
97+8			invalid block length
98+9			invalid checksum length
99+10			invalid remainder length
100+11			failed to write error
101+12			attempting to send over-long vstring
102+13			temporary filename too long
103+14			lseek failed
104+15			write failed
105+16			rename failed
106+17			rsync hack failed
107+18			"invalid basis_dir index
108+19			fstat failed
109+20			is a directory
110+21			open file failed
111+22			mkstemp failed
112+23			close failed
113+24			failed verification
114+25			IO error, skipping deletion.
115+26			directory creation failed
116+27			ignoring unsafe symbolic link
117+28			symbolic link failed
118+29			mknod failed
119+30			failed to stat
120+31			unlink
121+32			failed to open file/directory
122+33			open?
123--- old/dblog-tables-mysql.sql
124+++ new/dblog-tables-mysql.sql
125@@ -0,0 +1,64 @@
126+drop table transfer;
127+drop table exit;
128+drop table session;
129+
130+CREATE TABLE session (
131+	id			int auto_increment NOT NULL,
132+	date			timestamp NOT NULL,
133+	ip_address		varchar(15) NOT NULL,
134+	username		varchar(20) NOT NULL,
135+	module_name		varchar(20) NOT NULL,
136+	module_path		varchar(255) NOT NULL,
137+	process_id		int NOT NULL,
138+	Primary Key (id)
139+);
140+
141+CREATE TABLE transfer (
142+	id			int auto_increment NOT NULL,
143+	session_id		int NOT NULL,
144+	date			timestamp NOT NULL,
145+	file_name		varchar(255) NOT NULL,
146+	file_size		bigint NOT NULL,
147+	bytes_transferred	bigint NOT NULL,
148+	checksum_bytes_transferred bigint NOT NULL,
149+	operation		varchar(20),
150+	Primary Key (id),
151+	foreign key (session_id) references session (id)
152+);
153+
154+CREATE TABLE exit (
155+	id			int auto_increment NOT NULL,
156+	session_id		int NOT NULL,
157+	date			timestamp NOT NULL,
158+	total_bytes_written	bigint NOT NULL,
159+	total_bytes_read	bigint NOT NULL,
160+	total_size		bigint NOT NULL,
161+	error_text		varchar(128) NOT NULL,
162+	error_code		int NOT NULL,
163+	error_file		varchar(64) NOT NULL,
164+	error_line		int NOT NULL,
165+	process_id		int NOT NULL,
166+	Primary Key (id),
167+	foreign key (session_id) references session (id)
168+);
169+
170+CREATE TABLE error (
171+	id			int auto_increment NOT NULL,
172+	session_id		int NOT NULL,
173+	date			timestamp NOT NULL,
174+	logcode			bigint NOT NULL,
175+	error_number		bigint NOT NULL,
176+	error_text		varchar(512),
177+	PrimaryKey (id),
178+	foreign key (session_id) references session (id)
179+);
180+
181+CREATE TABLE delete (
182+	id			serial NOT NULL,
183+	session_id		int NOT NULL,
184+	date			timestamp NOT NULL,
185+	path			varchar(512) NOT NULL,
186+	mode			int NOT NULL,
187+	PrimaryKey (id),
188+	foreign key (session_id) references session (id)
189+);
190--- old/dblog-tables-postgresql.sql
191+++ new/dblog-tables-postgresql.sql
192@@ -0,0 +1,67 @@
193+drop table transfer;
194+drop table exit;
195+drop table session;
196+drop sequence session_id_seq;
197+create sequence session_id_seq;
198+
199+CREATE TABLE "session" (
200+	"id"			int NOT NULL,
201+	"date"			timestamp NOT NULL default now(),
202+	"ip_address"		varchar(15) NOT NULL,
203+	"username"		varchar(20) NOT NULL,
204+	"module_name"		varchar(20) NOT NULL,
205+	"module_path"		varchar(255) NOT NULL,
206+	"process_id"		int NOT NULL,
207+	Primary Key (id)
208+);
209+
210+CREATE TABLE "transfer" (
211+	"id"			serial NOT NULL,
212+	"session_id"		int NOT NULL,
213+	"date"			timestamp NOT NULL default now(),
214+	"file_name"		varchar(512) NOT NULL,
215+	"file_size"		bigint NOT NULL,
216+	"bytes_transferred"	bigint NOT NULL,
217+	"checksum_bytes_transferred" bigint NOT NULL,
218+	"operation"		varchar(20),
219+	Primary Key (id),
220+	foreign key (session_id) references session (id)
221+);
222+
223+CREATE TABLE "exit" (
224+	"id"			serial NOT NULL,
225+	"session_id"		int NOT NULL,
226+	"date"			timestamp NOT NULL default now(),
227+	"total_bytes_written"	bigint NOT NULL,
228+	"total_bytes_read"	bigint NOT NULL,
229+	"total_size"		bigint NOT NULL,
230+	"error_text"		varchar(128) NOT NULL,
231+	"error_code"		int NOT NULL,
232+	"error_file"		varchar(64) NOT NULL,
233+	"error_line"		int NOT NULL,
234+	"process_id"		int NOT NULL,
235+	Primary Key (id),
236+	foreign key (session_id) references session (id)
237+);
238+
239+CREATE TABLE "error" (
240+	"id"			serial NOT NULL,
241+	"session_id"		int NOT NULL,
242+	"date"			timestamp NOT NULL default now(),
243+	"logcode"		int NOT NULL,
244+	"error_number"		int NOT NULL,
245+	"error_text"		varchar(512),
246+	Primary Key (id),
247+	foreign key (session_id) references session (id)
248+
249+);
250+
251+CREATE TABLE "delete" (
252+	"id"			serial NOT NULL,
253+	"session_id"		int NOT NULL,
254+	"date"			timestamp NOT NULL default now(),
255+	"path"			varchar(512) NOT NULL,
256+	"mode"			int NOT NULL,
257+	Primary Key (id),
258+	foreign key (session_id) references session (id)
259+);
260--- old/dblog.c
261+++ new/dblog.c
262@@ -0,0 +1,549 @@
263+/*
264+ *  ODBC Database logging functions
265+ *
266+ *  Written by Steve Sether, April 2004
267+ *  steve@vellmont.com
268+ */
269+
270+#include "rsync.h"
271+
272+#ifdef HAVE_SQL_H
273+#include <sql.h>
274+#else
275+#ifdef HAVE_ODBC_SQL_H
276+#include <odbc/sql.h>
277+#endif
278+#endif
279+
280+#ifdef HAVE_SQLEXT_H
281+#include <sqlext.h>
282+#else
283+#ifdef HAVE_ODBC_SQLEXT_H
284+#include <odbc/sqlext.h>
285+#endif
286+#endif
287+
288+#ifdef HAVE_SQLTYPES_H
289+#include <sqltypes.h>
290+#else
291+#ifdef HAVE_ODBC_SQLTYPES_H
292+#include <odbc/sqltypes.h>
293+#endif
294+#endif
295+
296+SQLHENV db_environ_handle;			/* Handle ODBC environment */
297+long result;					/* result of functions */
298+SQLHDBC db_handle_g = NULL;			/* database connection handle for generator*/
299+SQLHDBC db_handle_r = NULL;			/* database connection handle for sender */
300+SQLHSTMT sql_statement_handle_g;		/* SQL statement handle for generator*/
301+SQLHSTMT sql_statement_handle_r;		/* SQL statement handle for receiver*/
302+extern int am_daemon;
303+extern int am_sender;
304+extern int am_generator;
305+extern char *auth_user;
306+extern int module_id;
307+extern int dry_run;
308+
309+
310+char sql_status[10];				/* Status SQL */
311+SQLINTEGER V_OD_err, V_OD_rowanz, V_OD_id;
312+SQLSMALLINT V_OD_mlen, V_OD_colanz;
313+char V_OD_msg[200], V_OD_buffer[200];
314+SQLINTEGER session_id;
315+
316+
317+/* This function simply removes invalid characters from the SQL statement
318+ * to prevent SQL injection attacks. */
319+char *sanitizeSql(const char *input)
320+{
321+	char *out, *ptr;
322+	const char *c;
323+
324+	if (strlen(input) > ((~(unsigned int)0)>>1)-3)
325+		return 0;
326+	if (!(out = ptr = new_array(char, strlen(input) * 2 + 1)))
327+		return 0;
328+
329+	for (c = input;  *c;  c++) {
330+		switch (*c) {
331+		case '\'':
332+			*ptr++ = '\'';
333+			*ptr++ = '\'';
334+			break;
335+		case '\b':
336+			*ptr++ = '\\';
337+			*ptr++ = 'b';
338+			break;
339+		case '\n':
340+			*ptr++ = '\\';
341+			*ptr++ = 'n';
342+			break;
343+		case '\r':
344+			*ptr++ = '\\';
345+			*ptr++ = 'r';
346+			break;
347+		case '\t':
348+			*ptr++ = '\\';
349+			*ptr++ = 't';
350+			break;
351+		default:
352+			*ptr++ = *c;
353+			break;
354+		}
355+	}
356+	*ptr = '\0';
357+	return out;
358+}
359+
360+void db_log_open(void)
361+{
362+	if (!lp_database_logging(module_id))
363+		return;
364+
365+	/* get ODBC environment handle */
366+	result = SQLAllocHandle(SQL_HANDLE_ENV,SQL_NULL_HANDLE,&db_environ_handle);
367+	if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
368+		rprintf(FERROR, "Error: couldn't get database environment handle\n");
369+		return;
370+	}
371+
372+	/* Setting database enviroment */
373+	result = SQLSetEnvAttr(db_environ_handle, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
374+	if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
375+		rprintf(FERROR, "Error: couldn't set database environment.\n");
376+		SQLFreeHandle(SQL_HANDLE_ENV, db_environ_handle);
377+		db_environ_handle = NULL;
378+		return;
379+	}
380+	if (db_handle_g == NULL) {
381+		/* Get a database handle for the generator*/
382+		result = SQLAllocHandle(SQL_HANDLE_DBC, db_environ_handle, &db_handle_g);
383+		if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
384+			rprintf(FERROR, "Error: couldn't allocate database handle for generator\n");
385+			SQLFreeHandle(SQL_HANDLE_ENV, db_environ_handle);
386+			db_environ_handle = NULL;
387+			return;
388+		}
389+
390+		/* Set connection attributes for the generator db connection */
391+		SQLSetConnectAttr(db_handle_g, SQL_LOGIN_TIMEOUT, (SQLPOINTER *)5, 0);
392+
393+		/* get the database connection for the generator. */
394+		result = SQLConnect(db_handle_g, (SQLCHAR*) lp_database_datasource(module_id), SQL_NTS,
395+		    (SQLCHAR*) lp_database_username(module_id), SQL_NTS,
396+		    (SQLCHAR*) lp_database_password(module_id), SQL_NTS);
397+
398+		if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
399+			SQLGetDiagRec(SQL_HANDLE_DBC, db_handle_g, 1,
400+			    sql_status, &V_OD_err, V_OD_msg, 100, &V_OD_mlen);
401+			rprintf(FERROR,"Error Connecting to Database (generator) %s\n",V_OD_msg);
402+			SQLFreeHandle(SQL_HANDLE_DBC,db_handle_g);
403+			db_handle_g = NULL;
404+			SQLFreeHandle(SQL_HANDLE_ENV, db_environ_handle);
405+			db_environ_handle = NULL;
406+			return;
407+		}
408+		rprintf(FLOG,"Connected to database for generator!\n");
409+	} else {
410+		rprintf(FERROR,"Already connected to database for generator\n");
411+	}
412+	if (db_handle_r == NULL) {
413+		/* Get a database handle for the receiver */
414+		result = SQLAllocHandle(SQL_HANDLE_DBC, db_environ_handle, &db_handle_r);
415+		if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
416+			rprintf(FERROR, "Error: couldn't allocate database handle for receiver\n");
417+			SQLFreeHandle(SQL_HANDLE_ENV, db_environ_handle);
418+			db_environ_handle = NULL;
419+			return;
420+		}
421+
422+		/* Set connection attributes for the receiver db connection */
423+		SQLSetConnectAttr(db_handle_r, SQL_LOGIN_TIMEOUT, (SQLPOINTER *)5, 0);
424+
425+		/* get the generator connection for the receiver. */
426+		result = SQLConnect(db_handle_r, (SQLCHAR*) lp_database_datasource(module_id), SQL_NTS,
427+		    (SQLCHAR*) lp_database_username(module_id), SQL_NTS,
428+		    (SQLCHAR*) lp_database_password(module_id), SQL_NTS);
429+
430+		if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
431+			SQLGetDiagRec(SQL_HANDLE_DBC, db_handle_r,1,
432+			    sql_status, &V_OD_err,V_OD_msg,100,&V_OD_mlen);
433+			rprintf(FERROR,"Error Connecting to Database (receiver) %s\n",V_OD_msg);
434+			SQLFreeHandle(SQL_HANDLE_DBC,db_handle_r);
435+			db_handle_r = NULL;
436+			SQLFreeHandle(SQL_HANDLE_ENV, db_environ_handle);
437+			db_environ_handle = NULL;
438+			return;
439+		}
440+		rprintf(FLOG,"Connected to database for receiver!\n");
441+	} else {
442+		rprintf(FERROR,"Already connected to database for receiver\n");
443+	}
444+
445+	/* get SQL statement handle for generator */
446+	result = SQLAllocHandle(SQL_HANDLE_STMT, db_handle_g, &sql_statement_handle_g);
447+	if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
448+		SQLGetDiagRec(SQL_HANDLE_DBC, db_handle_g,1, sql_status,&V_OD_err,V_OD_msg,100,&V_OD_mlen);
449+		rprintf(FERROR,"Error in allocating SQL statement handle %s\n",V_OD_msg);
450+		SQLDisconnect(db_handle_g);
451+		SQLFreeHandle(SQL_HANDLE_DBC,db_handle_g);
452+		db_handle_g = NULL;
453+		SQLFreeHandle(SQL_HANDLE_ENV, db_environ_handle);
454+		db_environ_handle = NULL;
455+		return;
456+	}
457+
458+	/* get SQL statement handle for receiver */
459+	result = SQLAllocHandle(SQL_HANDLE_STMT, db_handle_r, &sql_statement_handle_r);
460+	if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
461+		SQLGetDiagRec(SQL_HANDLE_DBC, db_handle_r,1, sql_status,&V_OD_err,V_OD_msg,100,&V_OD_mlen);
462+		rprintf(FERROR,"Error in allocating SQL statement handle %s\n",V_OD_msg);
463+		SQLDisconnect(db_handle_r);
464+		SQLFreeHandle(SQL_HANDLE_DBC,db_handle_r);
465+		db_handle_r = NULL;
466+		SQLFreeHandle(SQL_HANDLE_ENV, db_environ_handle);
467+		db_environ_handle = NULL;
468+		return;
469+	}
470+}
471+
472+void db_log_close()
473+{
474+	if (!lp_database_logging(module_id))
475+		return;
476+
477+	if (am_generator) {
478+		if (sql_statement_handle_g != NULL) {
479+			/* free the statement handle first */
480+			SQLFreeHandle(SQL_HANDLE_STMT,sql_statement_handle_g);
481+			sql_statement_handle_g = NULL;
482+		} else {
483+			rprintf(FERROR,"No generator sql statement handle to close\n");
484+		}
485+
486+		if (db_handle_g != NULL) {
487+			/* disconnect, and free the database handle. */
488+			SQLDisconnect(db_handle_g);
489+			SQLFreeHandle(SQL_HANDLE_DBC,db_handle_g);
490+			db_handle_g = NULL;
491+		} else {
492+			rprintf(FERROR,"Generator database connection already closed\n");
493+		}
494+	} else { /* must be receiver */
495+		if (sql_statement_handle_r != NULL) {
496+			/* free the statement handle first */
497+			SQLFreeHandle(SQL_HANDLE_STMT,sql_statement_handle_r);
498+			sql_statement_handle_r = NULL;
499+		} else {
500+			rprintf(FERROR,"No receiver sql statement handle to close\n");
501+		}
502+
503+		if (db_handle_r != NULL) {
504+			/* disconnect, and free the database handle. */
505+			SQLDisconnect(db_handle_r);
506+			SQLFreeHandle(SQL_HANDLE_DBC,db_handle_r);
507+			db_handle_r = NULL;
508+		} else {
509+			rprintf(FERROR,"Receiver database connection already closed\n");
510+		}
511+	}
512+
513+	if (db_environ_handle != NULL) {
514+		/* free the environment handle */
515+		SQLFreeHandle(SQL_HANDLE_ENV, db_environ_handle);
516+		db_environ_handle = NULL;
517+	} else {
518+		rprintf(FERROR,"No environment handle to close\n");
519+	}
520+}
521+
522+static long get_unique_session_id()
523+{
524+	long unique;
525+	char strSqlStatement[1024];
526+	SQLHDBC db_handle = (am_generator) ? db_handle_g : db_handle_r;
527+	SQLHSTMT sql_statement_handle = (am_generator) ? sql_statement_handle_g : sql_statement_handle_r;
528+
529+	if (db_handle != NULL) {
530+		/* choose the appropriate select statement based upon which DBMS we're using.
531+		 * different datbases use different methods to get a unique ID.  Some use a counter
532+		 * object (sequence), others use an auto increment datatype and have a method
533+		 * to get the last ID inserted using this connection. */
534+		if (strcmp(lp_unique_id_method(module_id),"nextval-postgresql") == 0) {
535+			snprintf(strSqlStatement, sizeof strSqlStatement,
536+			    "SELECT NEXTVAL('%s');", lp_sequence_name(module_id));
537+		} else if (strcmp(lp_unique_id_method(module_id),"nextval-oracle") == 0) {
538+			snprintf(strSqlStatement, sizeof strSqlStatement,
539+			    "SELECT %s.NEXTVAL FROM dual;", lp_sequence_name(module_id));
540+		} else if (strcmp(lp_unique_id_method(module_id),"nextval-db2") == 0) {
541+			snprintf(strSqlStatement, sizeof strSqlStatement,
542+			    "VALUES NEXTVAL FOR %s;",lp_sequence_name(module_id));
543+		} else if (strcmp(lp_unique_id_method(module_id),"last_insert_id") == 0) { /* MySql */
544+			snprintf(strSqlStatement, sizeof strSqlStatement,
545+			    "SELECT LAST_INSERT_ID()");
546+		} else if (strcmp(lp_unique_id_method(module_id),"@@IDENTITY") == 0) { /* Sybase */
547+			snprintf(strSqlStatement, sizeof strSqlStatement,
548+			    "SELECT @@IDENTITY");
549+		} else if (strcmp(lp_unique_id_method(module_id),"custom") == 0){ /* Users custom statement */
550+			snprintf(strSqlStatement, sizeof strSqlStatement,
551+			    lp_custom_unique_id_select(module_id));
552+		}
553+
554+		/* bind the 1st column to unique */
555+		SQLBindCol(sql_statement_handle,1,SQL_C_LONG,&unique,150,&V_OD_err);
556+		/* execute the SQL statement */
557+		result = SQLExecDirect(sql_statement_handle,strSqlStatement,SQL_NTS);
558+		if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
559+			SQLGetDiagRec(SQL_HANDLE_DBC, db_handle,1, sql_status,&V_OD_err,V_OD_msg,100,&V_OD_mlen);
560+			rprintf(FERROR,"Error at get_sequence:  Error in Select! %s %s\n",strSqlStatement,V_OD_msg);
561+		} else {
562+			result = SQLFetch(sql_statement_handle);
563+			if (result != SQL_NO_DATA && unique != 0) {
564+				rprintf(FINFO,"Got unique sequence! %ld\n",unique);
565+			} else {
566+				rprintf(FERROR,"Error at get_sequence:  Didn't get unique session ID\n");
567+			}
568+			/* Close the cursor so the statement can be re-used */
569+			result = SQLFreeStmt(sql_statement_handle,SQL_CLOSE);
570+			if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
571+				SQLGetDiagRec(SQL_HANDLE_DBC, db_handle,1, sql_status,&V_OD_err,V_OD_msg,100,&V_OD_mlen);
572+				rprintf(FERROR,"Error at get_sequence: Error in closing SQL statement handle %s\n",V_OD_msg);
573+				return unique;
574+			}
575+			return unique;
576+		}
577+	}
578+	rprintf(FERROR,"Error at get_sequence: Not connected to database\n");
579+	return -1;
580+}
581+
582+void db_log_session()
583+{
584+	char strSqlStatement[1024];
585+	int gotSessionID = 0;
586+	SQLHDBC db_handle = (am_generator) ? db_handle_g : db_handle_r;
587+	SQLHSTMT sql_statement_handle = (am_generator) ? sql_statement_handle_g : sql_statement_handle_r;
588+
589+	if (!lp_database_logging(module_id))
590+		return;
591+
592+	if (db_handle != NULL) {
593+		/* if we're using a sequence via the nextval command to
594+		 * get a unique ID, we need to get it before we do the
595+		 * insert. We also get the unique ID  now if custom,
596+		 * and get_custom_id_before_insert is set. */
597+		if (strcmp(lp_unique_id_method(module_id),"nextval-postgresql") == 0
598+		 || strcmp(lp_unique_id_method(module_id),"nextval-oracle") == 0
599+		 || strcmp(lp_unique_id_method(module_id),"nextval-db2") == 0
600+		 || (strcmp(lp_unique_id_method(module_id),"custom") == 0
601+		  && lp_get_custom_id_before_insert(module_id))) {
602+			session_id = get_unique_session_id();
603+			gotSessionID = 1;
604+			snprintf(strSqlStatement, sizeof strSqlStatement,
605+			    "INSERT INTO %s (id, date, ip_address, username, module_name, module_path, process_id) VALUES ('%ld', '%s', '%s', '%s','%s','%s','%d');",
606+			    lp_session_table_name(module_id), session_id, timestring(time(NULL)), client_addr(0),
607+			    auth_user, lp_name(module_id), lp_path(module_id), getpid());
608+		} else {
609+			/* Otherwise the ID gets created automatically, and we get the ID it used after the insert. */
610+			snprintf(strSqlStatement, sizeof strSqlStatement,
611+			    "INSERT INTO %s (date, ip_address, username, module_name, module_path, process_id) VALUES ('%s', '%s', '%s', '%s','%s','%d');",
612+			    lp_session_table_name(module_id), timestring(time(NULL)), client_addr(0), auth_user,
613+			    lp_name(module_id), lp_path(module_id), getpid());
614+		}
615+
616+		/* Insert the new session into the database */
617+		result = SQLExecDirect(sql_statement_handle,strSqlStatement,SQL_NTS);
618+		if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
619+			SQLGetDiagRec(SQL_HANDLE_DBC, db_handle,1, sql_status,&V_OD_err,V_OD_msg,100,&V_OD_mlen);
620+			rprintf(FERROR,"Error at db_log_session: Error in Insert %s %s\n",strSqlStatement,V_OD_msg);
621+		}
622+
623+		/* close the cursor so the statement handle can be re-used. */
624+		result = SQLFreeStmt(sql_statement_handle,SQL_CLOSE);
625+		if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
626+			SQLGetDiagRec(SQL_HANDLE_DBC, db_handle,1, sql_status,&V_OD_err,V_OD_msg,100,&V_OD_mlen);
627+			rprintf(FERROR,"Error in resetting SQL statement handle %s\n",V_OD_msg);
628+		}
629+		/* get the session ID for databases that give the unique ID after an insert */
630+		if (gotSessionID == 0) {
631+			session_id = get_unique_session_id();
632+		}
633+	} else {
634+		rprintf(FERROR,"Error at db_log_session:  Not connected to database!\n");
635+	}
636+}
637+
638+void db_log_transfer(struct file_struct *file,struct stats *initial_stats,char *operation)
639+{
640+	extern struct stats stats;
641+	char strSqlStatement[2048];
642+	char strFileName[MAXPATHLEN];
643+	char *strFileNamePtr;
644+	char strFileSize[255];
645+	int64 intBytesTransferred;
646+	int64 intCheckSumBytes;
647+	SQLHDBC db_handle = (am_generator) ? db_handle_g : db_handle_r;
648+	SQLHSTMT sql_statement_handle = (am_generator) ? sql_statement_handle_g : sql_statement_handle_r;
649+
650+	if (!lp_database_logging(module_id))
651+		return;
652+
653+	if (db_handle != NULL) {
654+		strFileNamePtr = f_name(file, NULL);
655+		if (am_sender && file->dir.root) {
656+			pathjoin(strFileName, sizeof strFileName,
657+				 file->dir.root, strFileNamePtr);
658+			strFileNamePtr = strFileName;
659+		}
660+		clean_fname(strFileNamePtr, 0);
661+		if (*strFileNamePtr == '/')
662+			strFileNamePtr++;
663+
664+		snprintf(strFileSize, sizeof strFileSize, "%.0f", (double)file->length);
665+		if (am_sender) {
666+			intBytesTransferred = stats.total_written - initial_stats->total_written;
667+		} else {
668+			intBytesTransferred = stats.total_read - initial_stats->total_read;
669+		}
670+
671+		if (!am_sender) {
672+			intCheckSumBytes = stats.total_written - initial_stats->total_written;
673+		} else {
674+			intCheckSumBytes = stats.total_read - initial_stats->total_read;
675+		}
676+
677+		snprintf(strSqlStatement, sizeof strSqlStatement,
678+		    "INSERT INTO %s (session_id,date, file_name, file_size, bytes_transferred, checksum_bytes_transferred, operation) VALUES ('%ld','%s','%s','%s','%Ld','%Ld','%s');",
679+		    lp_transfer_table_name(module_id), session_id, timestring(time(NULL)),
680+		    sanitizeSql(strFileNamePtr), strFileSize, intBytesTransferred,
681+		    intCheckSumBytes, operation);
682+		result = SQLExecDirect(sql_statement_handle,strSqlStatement,SQL_NTS);
683+		if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
684+			SQLGetDiagRec(SQL_HANDLE_DBC, db_handle,1, sql_status,&V_OD_err,V_OD_msg,100,&V_OD_mlen);
685+			rprintf(FERROR,"Error at db_log_transfer:  Error in Insert %s %s\n",strSqlStatement,V_OD_msg);
686+			if (result == SQL_INVALID_HANDLE)
687+				rprintf(FERROR,"INVALID HANDLE\n");
688+		}
689+	} else {
690+		rprintf(FERROR,"Error at db_log_transfer: Not connected to database!\n");
691+	}
692+}
693+
694+void db_log_exit(int code, const char *file, int line)
695+{
696+	char strSqlStatement[2048];
697+	const char *error_text;
698+	extern struct stats stats;
699+	SQLHDBC db_handle = (am_generator) ? db_handle_g : db_handle_r;
700+	SQLHSTMT sql_statement_handle = (am_generator) ? sql_statement_handle_g : sql_statement_handle_r;
701+
702+	if (!lp_database_logging(module_id))
703+		return;
704+
705+	if (db_handle != NULL) {
706+		if (code != 0) {
707+			error_text = rerr_name(code);
708+			if (!error_text) {
709+				error_text = "unexplained error";
710+			}
711+		} else {
712+			error_text = "";
713+		}
714+		snprintf(strSqlStatement, sizeof strSqlStatement,
715+		    "INSERT INTO %s (session_id, date, total_bytes_written,total_bytes_read,total_size,error_text,error_code,error_file,error_line,process_id) VALUES ('%ld','%s','%Ld','%Ld','%Ld','%s','%d','%s','%d','%d');",
716+		    lp_exit_table_name(module_id), session_id, timestring(time(NULL)), stats.total_written,
717+		    stats.total_read, stats.total_size, error_text, code, file, line, getpid());
718+
719+		result = SQLExecDirect(sql_statement_handle,strSqlStatement,SQL_NTS);
720+
721+		if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
722+			SQLGetDiagRec(SQL_HANDLE_DBC, db_handle,1, sql_status,&V_OD_err,V_OD_msg,100,&V_OD_mlen);
723+			rprintf(FERROR,"Error at db_log_exit: Error in Insert %s %s\n",strSqlStatement,V_OD_msg);
724+		}
725+	} else {
726+		rprintf(FERROR,"Error at db_log_exit: Not connected to database!\n");
727+	}
728+}
729+
730+void db_log_delete(char *fname, int mode)
731+{
732+	char strSqlStatement[2048];
733+	SQLHDBC db_handle = (am_generator) ? db_handle_g : db_handle_r;
734+	SQLHSTMT sql_statement_handle = (am_generator) ? sql_statement_handle_g : sql_statement_handle_r;
735+
736+	if (!am_daemon || dry_run || !lp_database_logging(module_id))
737+		return;
738+
739+	if (db_handle != NULL) {
740+		snprintf(strSqlStatement, sizeof strSqlStatement,
741+		    "INSERT INTO %s (session_id, date, path, mode) VALUES ('%ld','%s','%s','%d');",
742+		    lp_delete_table_name(module_id), session_id, timestring(time(NULL)), sanitizeSql(fname), mode);
743+
744+		result = SQLExecDirect(sql_statement_handle,strSqlStatement,SQL_NTS);
745+
746+		if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
747+			SQLGetDiagRec(SQL_HANDLE_DBC, db_handle,1, sql_status,&V_OD_err,V_OD_msg,100,&V_OD_mlen);
748+			rprintf(FERROR,"Error at db_log_delete: Error in Insert %s %s\n",strSqlStatement,V_OD_msg);
749+		}
750+	} else {
751+		rprintf(FERROR,"Error at db_log_delete: Not connected to database!\n");
752+	}
753+}
754+
755+void db_log_error(enum logcode code, int errcode, const char *format,...)
756+{
757+	char strSqlStatement[MAXPATHLEN+1024];
758+	va_list ap;
759+	char buf[MAXPATHLEN+512];
760+	size_t len;
761+	SQLHDBC db_handle = (am_generator) ? db_handle_g : db_handle_r;
762+	SQLHSTMT sql_statement_handle = (am_generator) ? sql_statement_handle_g : sql_statement_handle_r;
763+
764+	if (!lp_database_logging(module_id))
765+		return;
766+
767+	va_start(ap, format);
768+	len = vsnprintf(buf, sizeof buf, format, ap);
769+	va_end(ap);
770+
771+	/* Deal with buffer overruns.  Instead of panicking, just
772+	 * truncate the resulting string.  (Note that configure ensures
773+	 * that we have a vsnprintf() that doesn't ever return -1.) */
774+	if (len > sizeof buf - 1) {
775+		const char ellipsis[] = "[...]";
776+
777+		/* Reset length, and zero-terminate the end of our buffer */
778+		len = sizeof buf - 1;
779+		buf[len] = '\0';
780+
781+		/* Copy the ellipsis to the end of the string, but give
782+		 * us one extra character:
783+		 *
784+		 *                  v--- null byte at buf[sizeof buf - 1]
785+		 *        abcdefghij0
786+		 *     -> abcd[...]00  <-- now two null bytes at end
787+		 *
788+		 * If the input format string has a trailing newline,
789+		 * we copy it into that extra null; if it doesn't, well,
790+		 * all we lose is one byte.  */
791+		strncpy(buf+len-sizeof ellipsis, ellipsis, sizeof ellipsis);
792+		if (format[strlen(format)-1] == '\n') {
793+			buf[len-1] = '\n';
794+		}
795+	}
796+
797+	if (db_handle != NULL) {
798+		snprintf(strSqlStatement, sizeof strSqlStatement,
799+		    "INSERT INTO %s (session_id, date, logcode, error_number, error_text) VALUES ('%ld','%s','%d','%d','%s');",
800+		    lp_error_table_name(module_id), session_id, timestring(time(NULL)), code, errcode, sanitizeSql(buf));
801+
802+		result = SQLExecDirect(sql_statement_handle,strSqlStatement,SQL_NTS);
803+
804+		if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
805+			SQLGetDiagRec(SQL_HANDLE_DBC, db_handle,1, sql_status,&V_OD_err,V_OD_msg,100,&V_OD_mlen);
806+			rprintf(FERROR,"Error at db_log_error: Error in Insert %s %s\n",strSqlStatement,V_OD_msg);
807+		}
808+	} else {
809+		rprintf(FERROR,"Error at db_log_error: Not connected to database!\n");
810+	}
811+}
812--- old/instructions
813+++ new/instructions
814@@ -0,0 +1,84 @@
815+This patch adds the following options:
816+
817+"database logging"
818+    If set to True, rsync will attempt to connect to
819+    the specified datasource and write to the named tables.
820+    Defaults to False.
821+
822+"database datasource"
823+    Specifies the name of the ODBC data source to use.
824+
825+"database username"
826+    The username to use when connecting to the database.
827+
828+"database password"
829+    The password to use when connecting to the database.
830+
831+"transfer table name"
832+    The name of the transfer table to log to.  This table contains individual
833+    filenames, file sizes, bytes transferred, checksum bytes transferred,
834+    operation (send or receive), and a timestamp.
835+
836+"session table name"
837+    The name of the session table to log to.  This table contains the username,
838+    module name, module path, ip address, process ID, and a timestamp.
839+
840+"exit table name"
841+
842+    The name of the exit table to log to.  This table contains the total bytes
843+    read, total bytes written, total size of all files, error code, line the
844+    error occured at, file the error occured at and the text of the error.
845+    (most of which will be blank if the program exited normally).
846+
847+"delete table name"
848+
849+    The name of the table to log deleted files/directories to.
850+
851+"error table name"
852+
853+   The name of the table errors will be logged to.
854+
855+"unique id method"
856+    Different databases use different methods to get a unique identifier.
857+    Some databases support sequence objects, and use various forms of the
858+    nextval command to retrieve a unique identifier from it.  Other databases
859+    support an autonumber field, and support different methds of retrieving
860+    the ID used in the last insert.  Valid values for this option are:
861+
862+	nextval-postgres
863+	    uses the syntax of nextval for PostgreSQL databases
864+
865+	nextval-oracle
866+	    uses the syntax of nextval for Oracle databases
867+
868+	nextval-db2
869+	    uses the syntax of nextval for DB2 databases
870+
871+	last_insert_id
872+	    uses the last_insert_id() command for the MySQL databases
873+
874+	@@IDENTITY
875+	    uses the @@IDENTITY command for Sybase databases
876+
877+	custom
878+	    Define your own method to get a unique identifier.  See the
879+	    "custom unique id select", and the "get custom id before select"
880+	    parameters.
881+
882+"sequence name"
883+    If your database supports sequences, list the name of the sequence to use
884+    for the session unique identifier.
885+
886+"custom unique id select"
887+    Only used if you specify the custom method in "unique id method".  This is
888+    a SQL statement to be executed to get a unique ID.  This SQL statement must
889+    return one column with the unique ID to use for the session ID.  Should be
890+    used in concert with the "get custom id before select" parameter.
891+
892+"get custom id before insert"
893+    This parameter is ignored unless the "unique id method" is set to custom.
894+    If set to true, the "custom unique id select" statement will be executed
895+    BEFORE the session row is inserted into the database.  (as is done when a
896+    sequence is used for unique IDs).  If False the statement will be executed
897+    after the session row is inserted (as is done when the session ID is
898+    automatically generates unique IDs).  Defaults to True.
899--- old/loadparm.c
900+++ new/loadparm.c
901@@ -122,9 +122,16 @@ typedef struct
902 {
903 	char *auth_users;
904 	char *comment;
905+	char *custom_unique_id_select;
906+	char *database_datasource;
907+	char *database_password;
908+	char *database_username;
909+	char *delete_table_name;
910 	char *dont_compress;
911+	char *error_table_name;
912 	char *exclude;
913 	char *exclude_from;
914+	char *exit_table_name;
915 	char *filter;
916 	char *gid;
917 	char *hosts_allow;
918@@ -142,14 +149,20 @@ typedef struct
919 	char *prexfer_exec;
920 	char *refuse_options;
921 	char *secrets_file;
922+	char *sequence_name;
923+	char *session_table_name;
924 	char *temp_dir;
925+	char *transfer_table_name;
926 	char *uid;
927+	char *unique_id_method;
928 
929 	int max_connections;
930 	int max_verbosity;
931 	int syslog_facility;
932 	int timeout;
933 
934+	BOOL database_logging;
935+	BOOL get_custom_id_before_insert;
936 	BOOL ignore_errors;
937 	BOOL ignore_nonreadable;
938 	BOOL list;
939@@ -169,9 +182,16 @@ static service sDefault =
940 {
941  /* auth_users; */		NULL,
942  /* comment; */			NULL,
943+ /* custom_unique_id_select; */	NULL,
944+ /* database_datasource; */	NULL,
945+ /* database_password; */	NULL,
946+ /* database_username; */	NULL,
947+ /* delete_table_name; */	NULL,
948  /* dont_compress; */		"*.gz *.tgz *.zip *.z *.rpm *.deb *.iso *.bz2 *.tbz",
949+ /* error_table_name; */	NULL,
950  /* exclude; */			NULL,
951  /* exclude_from; */		NULL,
952+ /* exit_table_name; */		NULL,
953  /* filter; */			NULL,
954  /* gid; */			NOBODY_GROUP,
955  /* hosts_allow; */		NULL,
956@@ -189,14 +209,20 @@ static service sDefault =
957  /* prexfer_exec; */		NULL,
958  /* refuse_options; */		NULL,
959  /* secrets_file; */		NULL,
960+ /* sequence_name; */		NULL,
961+ /* session_table_name; */	NULL,
962  /* temp_dir; */ 		NULL,
963+ /* transfer_table_name; */	NULL,
964  /* uid; */			NOBODY_USER,
965+ /* unique_id_method; */	NULL,
966 
967  /* max_connections; */		0,
968  /* max_verbosity; */		1,
969  /* syslog_facility; */		LOG_DAEMON,
970  /* timeout; */			0,
971 
972+ /* database_logging; */	False,
973+ /* get_custom_id_before_insert; */ True,
974  /* ignore_errors; */		False,
975  /* ignore_nonreadable; */	False,
976  /* list; */			True,
977@@ -295,10 +321,19 @@ static struct parm_struct parm_table[] =
978 
979  {"auth users",        P_STRING, P_LOCAL, &sDefault.auth_users,        NULL,0},
980  {"comment",           P_STRING, P_LOCAL, &sDefault.comment,           NULL,0},
981+ {"custom unique id select",P_STRING,P_LOCAL,&sDefault.custom_unique_id_select,NULL,0},
982+ {"database datasource",P_STRING,P_LOCAL, &sDefault.database_datasource,NULL,0},
983+ {"database logging",  P_BOOL,   P_LOCAL, &sDefault.database_logging,  NULL,0},
984+ {"database password", P_STRING, P_LOCAL, &sDefault.database_password, NULL,0},
985+ {"database username", P_STRING, P_LOCAL, &sDefault.database_username, NULL,0},
986+ {"delete table name", P_STRING, P_LOCAL, &sDefault.delete_table_name, NULL,0},
987  {"dont compress",     P_STRING, P_LOCAL, &sDefault.dont_compress,     NULL,0},
988+ {"error table name",  P_STRING, P_LOCAL, &sDefault.error_table_name,  NULL,0},
989  {"exclude from",      P_STRING, P_LOCAL, &sDefault.exclude_from,      NULL,0},
990  {"exclude",           P_STRING, P_LOCAL, &sDefault.exclude,           NULL,0},
991+ {"exit table name",   P_STRING, P_LOCAL, &sDefault.exit_table_name,   NULL,0},
992  {"filter",            P_STRING, P_LOCAL, &sDefault.filter,            NULL,0},
993+ {"get custom id before insert",P_BOOL,P_LOCAL,&sDefault.get_custom_id_before_insert,NULL,0},
994  {"gid",               P_STRING, P_LOCAL, &sDefault.gid,               NULL,0},
995  {"hosts allow",       P_STRING, P_LOCAL, &sDefault.hosts_allow,       NULL,0},
996  {"hosts deny",        P_STRING, P_LOCAL, &sDefault.hosts_deny,        NULL,0},
997@@ -323,12 +358,16 @@ static struct parm_struct parm_table[] =
998  {"read only",         P_BOOL,   P_LOCAL, &sDefault.read_only,         NULL,0},
999  {"refuse options",    P_STRING, P_LOCAL, &sDefault.refuse_options,    NULL,0},
1000  {"secrets file",      P_STRING, P_LOCAL, &sDefault.secrets_file,      NULL,0},
1001+ {"sequence name",     P_STRING, P_LOCAL, &sDefault.sequence_name,     NULL,0},
1002+ {"session table name",P_STRING, P_LOCAL, &sDefault.session_table_name,NULL,0},
1003  {"strict modes",      P_BOOL,   P_LOCAL, &sDefault.strict_modes,      NULL,0},
1004  {"syslog facility",   P_ENUM,   P_LOCAL, &sDefault.syslog_facility,enum_facilities,0},
1005  {"temp dir",          P_PATH,   P_LOCAL, &sDefault.temp_dir,          NULL,0},
1006  {"timeout",           P_INTEGER,P_LOCAL, &sDefault.timeout,           NULL,0},
1007  {"transfer logging",  P_BOOL,   P_LOCAL, &sDefault.transfer_logging,  NULL,0},
1008+ {"transfer table name",P_STRING,P_LOCAL, &sDefault.transfer_table_name,NULL,0},
1009  {"uid",               P_STRING, P_LOCAL, &sDefault.uid,               NULL,0},
1010+ {"unique id method",  P_STRING, P_LOCAL, &sDefault.unique_id_method,  NULL,0},
1011  {"use chroot",        P_BOOL,   P_LOCAL, &sDefault.use_chroot,        NULL,0},
1012  {"write only",        P_BOOL,   P_LOCAL, &sDefault.write_only,        NULL,0},
1013  {NULL,                P_BOOL,   P_NONE,  NULL,                        NULL,0}
1014@@ -384,9 +423,16 @@ FN_GLOBAL_INTEGER(lp_rsync_port, &Global
1015 
1016 FN_LOCAL_STRING(lp_auth_users, auth_users)
1017 FN_LOCAL_STRING(lp_comment, comment)
1018+FN_LOCAL_STRING(lp_custom_unique_id_select,custom_unique_id_select)
1019+FN_LOCAL_STRING(lp_database_datasource, database_datasource)
1020+FN_LOCAL_STRING(lp_database_password, database_password)
1021+FN_LOCAL_STRING(lp_database_username, database_username)
1022+FN_LOCAL_STRING(lp_delete_table_name,delete_table_name)
1023 FN_LOCAL_STRING(lp_dont_compress, dont_compress)
1024+FN_LOCAL_STRING(lp_error_table_name,error_table_name)
1025 FN_LOCAL_STRING(lp_exclude, exclude)
1026 FN_LOCAL_STRING(lp_exclude_from, exclude_from)
1027+FN_LOCAL_STRING(lp_exit_table_name, exit_table_name)
1028 FN_LOCAL_STRING(lp_filter, filter)
1029 FN_LOCAL_STRING(lp_gid, gid)
1030 FN_LOCAL_STRING(lp_hosts_allow, hosts_allow)
1031@@ -404,14 +450,20 @@ FN_LOCAL_STRING(lp_postxfer_exec, postxf
1032 FN_LOCAL_STRING(lp_prexfer_exec, prexfer_exec)
1033 FN_LOCAL_STRING(lp_refuse_options, refuse_options)
1034 FN_LOCAL_STRING(lp_secrets_file, secrets_file)
1035+FN_LOCAL_STRING(lp_sequence_name,sequence_name)
1036+FN_LOCAL_STRING(lp_session_table_name,session_table_name)
1037 FN_LOCAL_INTEGER(lp_syslog_facility, syslog_facility)
1038 FN_LOCAL_STRING(lp_temp_dir, temp_dir)
1039+FN_LOCAL_STRING(lp_transfer_table_name, transfer_table_name)
1040 FN_LOCAL_STRING(lp_uid, uid)
1041+FN_LOCAL_STRING(lp_unique_id_method,unique_id_method)
1042 
1043 FN_LOCAL_INTEGER(lp_max_connections, max_connections)
1044 FN_LOCAL_INTEGER(lp_max_verbosity, max_verbosity)
1045 FN_LOCAL_INTEGER(lp_timeout, timeout)
1046 
1047+FN_LOCAL_BOOL(lp_database_logging, database_logging)
1048+FN_LOCAL_BOOL(lp_get_custom_id_before_insert,get_custom_id_before_insert)
1049 FN_LOCAL_BOOL(lp_ignore_errors, ignore_errors)
1050 FN_LOCAL_BOOL(lp_ignore_nonreadable, ignore_nonreadable)
1051 FN_LOCAL_BOOL(lp_list, list)
1052--- old/log.c
1053+++ new/log.c
1054@@ -93,7 +93,7 @@ struct {
1055 /*
1056  * Map from rsync error code to name, or return NULL.
1057  */
1058-static char const *rerr_name(int code)
1059+char const *rerr_name(int code)
1060 {
1061 	int i;
1062 	for (i = 0; rerr_names[i].name; i++) {
1063--- old/receiver.c
1064+++ new/receiver.c
1065@@ -110,6 +110,10 @@ static int get_tmpname(char *fnametmp, c
1066 
1067 	if (maxname < 1) {
1068 		rprintf(FERROR, "temporary filename too long: %s\n", fname);
1069+#ifdef HAVE_LIBODBC
1070+		db_log_error(FERROR,13, "temporary filename too long: %s\n",
1071+			fname);
1072+#endif
1073 		fnametmp[0] = '\0';
1074 		return 0;
1075 	}
1076@@ -173,6 +177,10 @@ static int receive_data(int f_in, char *
1077 		if (fd != -1 && (j = do_lseek(fd, offset, SEEK_SET)) != offset) {
1078 			rsyserr(FERROR, errno, "lseek of %s returned %.0f, not %.0f",
1079 				full_fname(fname), (double)j, (double)offset);
1080+#ifdef HAVE_LIBODBC
1081+			db_log_error(FERROR, 14, "lseek failed on %s",
1082+				full_fname(fname));
1083+#endif
1084 			exit_cleanup(RERR_FILEIO);
1085 		}
1086 	}
1087@@ -230,6 +238,11 @@ static int receive_data(int f_in, char *
1088 						"lseek of %s returned %.0f, not %.0f",
1089 						full_fname(fname),
1090 						(double)pos, (double)offset);
1091+#ifdef HAVE_LIBODBC
1092+					db_log_error(FERROR, 14,
1093+						"lseek failed on %s",
1094+						full_fname(fname));
1095+#endif
1096 					exit_cleanup(RERR_FILEIO);
1097 				}
1098 				continue;
1099@@ -255,6 +268,9 @@ static int receive_data(int f_in, char *
1100 	    report_write_error:
1101 		rsyserr(FERROR, errno, "write failed on %s",
1102 			full_fname(fname));
1103+#ifdef HAVE_LIBODBC
1104+		db_log_error(FERROR, 15, "write failed on %s",full_fname(fname));
1105+#endif
1106 		exit_cleanup(RERR_FILEIO);
1107 	}
1108 
1109@@ -298,6 +314,12 @@ static void handle_delayed_updates(struc
1110 				rsyserr(FERROR, errno,
1111 					"rename failed for %s (from %s)",
1112 					full_fname(fname), partialptr);
1113+#ifdef HAVE_LIBODBC
1114+				db_log_error(FERROR, 16,
1115+					"rename failed for %s (from %s)",
1116+					full_fname(fname),
1117+					partialptr);
1118+#endif
1119 			} else {
1120 				if (remove_source_files
1121 				    || (preserve_hard_links
1122@@ -422,6 +444,9 @@ int recv_files(int f_in, struct file_lis
1123 		if (server_filter_list.head
1124 		    && check_filter(&server_filter_list, fname, 0) < 0) {
1125 			rprintf(FERROR, "attempt to hack rsync failed.\n");
1126+#ifdef HAVE_LIBODBC
1127+			db_log_error(FERROR,17,"attempt to hack rsync failed.");
1128+#endif
1129 			exit_cleanup(RERR_PROTOCOL);
1130 		}
1131 
1132@@ -478,6 +503,11 @@ int recv_files(int f_in, struct file_lis
1133 					rprintf(FERROR,
1134 						"invalid basis_dir index: %d.\n",
1135 						fnamecmp_type);
1136+#ifdef HAVE_LIBODBC
1137+					db_log_error(FERROR, 18,
1138+						"invalid basis_dir index: %d.\n",
1139+						fnamecmp_type);
1140+#endif
1141 					exit_cleanup(RERR_PROTOCOL);
1142 				}
1143 				pathjoin(fnamecmpbuf, sizeof fnamecmpbuf,
1144@@ -526,6 +556,9 @@ int recv_files(int f_in, struct file_lis
1145 		} else if (do_fstat(fd1,&st) != 0) {
1146 			rsyserr(FERROR, errno, "fstat %s failed",
1147 				full_fname(fnamecmp));
1148+#ifdef HAVE_LIBODBC
1149+			db_log_error(FERROR, 19,"fstat %s failed",full_fname(fnamecmp));
1150+#endif
1151 			discard_receive_data(f_in, file->length);
1152 			close(fd1);
1153 			continue;
1154@@ -539,6 +572,9 @@ int recv_files(int f_in, struct file_lis
1155 			 */
1156 			rprintf(FERROR,"recv_files: %s is a directory\n",
1157 				full_fname(fnamecmp));
1158+#ifdef HAVE_LIBODBC
1159+			db_log_error(FERROR,20,"recv_files: %s is a directory",full_fname(fnamecmp));
1160+#endif
1161 			discard_receive_data(f_in, file->length);
1162 			close(fd1);
1163 			continue;
1164@@ -562,6 +598,9 @@ int recv_files(int f_in, struct file_lis
1165 			if (fd2 == -1) {
1166 				rsyserr(FERROR, errno, "open %s failed",
1167 					full_fname(fname));
1168+#ifdef HAVE_LIBODBC
1169+				db_log_error(FERROR,22, "open %s failed", full_fname(fname));
1170+#endif
1171 				discard_receive_data(f_in, file->length);
1172 				if (fd1 != -1)
1173 					close(fd1);
1174@@ -595,6 +634,10 @@ int recv_files(int f_in, struct file_lis
1175 			if (fd2 == -1) {
1176 				rsyserr(FERROR, errno, "mkstemp %s failed",
1177 					full_fname(fnametmp));
1178+#ifdef HAVE_LIBODBC
1179+				db_log_error(FERROR, 22, "mkstemp %s failed",
1180+					full_fname(fnametmp));
1181+#endif
1182 				discard_receive_data(f_in, file->length);
1183 				if (fd1 != -1)
1184 					close(fd1);
1185@@ -615,12 +658,19 @@ int recv_files(int f_in, struct file_lis
1186 				       fname, fd2, file->length);
1187 
1188 		log_item(log_code, file, &initial_stats, iflags, NULL);
1189+#ifdef HAVE_LIBODBC
1190+		db_log_transfer(file, &initial_stats, "receive");
1191+#endif
1192 
1193 		if (fd1 != -1)
1194 			close(fd1);
1195 		if (close(fd2) < 0) {
1196 			rsyserr(FERROR, errno, "close failed on %s",
1197 				full_fname(fnametmp));
1198+#ifdef HAVE_LIBODBC
1199+			db_log_error(FERROR, 23, "close failed on %s",
1200+				full_fname(fnametmp));
1201+#endif
1202 			exit_cleanup(RERR_FILEIO);
1203 		}
1204 
1205@@ -679,6 +729,12 @@ int recv_files(int f_in, struct file_lis
1206 				rprintf(msgtype,
1207 					"%s: %s failed verification -- update %s%s.\n",
1208 					errstr, fname, keptstr, redostr);
1209+#ifdef HAVE_LIBODBC
1210+				db_log_error(msgtype,24,
1211+					"%s: %s failed verification -- update %s%s.\n",
1212+					errstr, fname,
1213+					keptstr, redostr);
1214+#endif
1215 			}
1216 			if (!phase) {
1217 				SIVAL(numbuf, 0, i);
1218--- old/sender.c
1219+++ new/sender.c
1220@@ -355,6 +355,9 @@ void send_files(struct file_list *flist,
1221 			end_progress(st.st_size);
1222 
1223 		log_item(log_code, file, &initial_stats, iflags, NULL);
1224+#ifdef HAVE_LIBODBC
1225+		db_log_transfer(file, &initial_stats,"send");
1226+#endif
1227 
1228 		if (mbuf) {
1229 			j = unmap_file(mbuf);
1230--- old/proto.h
1231+++ new/proto.h
1232@@ -47,6 +47,14 @@ int start_daemon(int f_in, int f_out);
1233 int daemon_main(void);
1234 void setup_protocol(int f_out,int f_in);
1235 int claim_connection(char *fname,int max_connections);
1236+char *sanitizeSql(const char *input);
1237+void db_log_open(void);
1238+void db_log_close();
1239+void db_log_session();
1240+void db_log_transfer(struct file_struct *file,struct stats *initial_stats,char *operation);
1241+void db_log_exit(int code, const char *file, int line);
1242+void db_log_delete(char *fname, int mode);
1243+void db_log_error(enum logcode code, int errcode, const char *format,...);
1244 void set_filter_dir(const char *dir, unsigned int dirlen);
1245 void *push_local_filters(const char *dir, unsigned int dirlen);
1246 void pop_local_filters(void *mem);
1247@@ -147,9 +155,16 @@ char *lp_socket_options(void);
1248 int lp_rsync_port(void);
1249 char *lp_auth_users(int );
1250 char *lp_comment(int );
1251+char *lp_custom_unique_id_select(int );
1252+char *lp_database_datasource(int );
1253+char *lp_database_password(int );
1254+char *lp_database_username(int );
1255+char *lp_delete_table_name(int );
1256 char *lp_dont_compress(int );
1257+char *lp_error_table_name(int );
1258 char *lp_exclude(int );
1259 char *lp_exclude_from(int );
1260+char *lp_exit_table_name(int );
1261 char *lp_filter(int );
1262 char *lp_gid(int );
1263 char *lp_hosts_allow(int );
1264@@ -167,12 +182,18 @@ char *lp_postxfer_exec(int );
1265 char *lp_prexfer_exec(int );
1266 char *lp_refuse_options(int );
1267 char *lp_secrets_file(int );
1268+char *lp_sequence_name(int );
1269+char *lp_session_table_name(int );
1270 int lp_syslog_facility(int );
1271 char *lp_temp_dir(int );
1272+char *lp_transfer_table_name(int );
1273 char *lp_uid(int );
1274+char *lp_unique_id_method(int );
1275 int lp_max_connections(int );
1276 int lp_max_verbosity(int );
1277 int lp_timeout(int );
1278+BOOL lp_database_logging(int );
1279+BOOL lp_get_custom_id_before_insert(int );
1280 BOOL lp_ignore_errors(int );
1281 BOOL lp_ignore_nonreadable(int );
1282 BOOL lp_list(int );
1283@@ -184,6 +205,7 @@ BOOL lp_write_only(int );
1284 BOOL lp_load(char *pszFname, int globals_only);
1285 int lp_numservices(void);
1286 int lp_number(char *name);
1287+char const *rerr_name(int code);
1288 void log_init(int restart);
1289 void logfile_close(void);
1290 void logfile_reopen(void);
1291--- old/configure
1292+++ new/configure
1293@@ -664,6 +664,7 @@ INSTALL_DATA
1294 HAVE_REMSH
1295 LIBOBJS
1296 ALLOCA
1297+EXTRA_OBJECT
1298 OBJ_SAVE
1299 OBJ_RESTORE
1300 CC_SHOBJ_FLAG
1301@@ -1258,6 +1259,7 @@ Optional Features:
1302   --disable-largefile     omit support for large files
1303   --disable-ipv6          don't even try to use IPv6
1304   --disable-locale        turn off locale features
1305+  --enable-ODBC           compile in support for ODBC database logging
1306 
1307 Optional Packages:
1308   --with-PACKAGE[=ARG]    use PACKAGE [ARG=yes]
1309@@ -14159,6 +14161,266 @@ fi
1310 
1311 fi
1312 
1313+# Check whether --enable-ODBC was given.
1314+if test "${enable_ODBC+set}" = set; then
1315+  enableval=$enable_ODBC;
1316+
1317+
1318+for ac_header in sql.h sqlext.h sqltypes.h
1319+do
1320+as_ac_Header=`echo "ac_cv_header_$ac_header" | $as_tr_sh`
1321+if { as_var=$as_ac_Header; eval "test \"\${$as_var+set}\" = set"; }; then
1322+  { echo "$as_me:$LINENO: checking for $ac_header" >&5
1323+echo $ECHO_N "checking for $ac_header... $ECHO_C" >&6; }
1324+if { as_var=$as_ac_Header; eval "test \"\${$as_var+set}\" = set"; }; then
1325+  echo $ECHO_N "(cached) $ECHO_C" >&6
1326+fi
1327+ac_res=`eval echo '${'$as_ac_Header'}'`
1328+	       { echo "$as_me:$LINENO: result: $ac_res" >&5
1329+echo "${ECHO_T}$ac_res" >&6; }
1330+else
1331+  # Is the header compilable?
1332+{ echo "$as_me:$LINENO: checking $ac_header usability" >&5
1333+echo $ECHO_N "checking $ac_header usability... $ECHO_C" >&6; }
1334+cat >conftest.$ac_ext <<_ACEOF
1335+/* confdefs.h.  */
1336+_ACEOF
1337+cat confdefs.h >>conftest.$ac_ext
1338+cat >>conftest.$ac_ext <<_ACEOF
1339+/* end confdefs.h.  */
1340+$ac_includes_default
1341+#include <$ac_header>
1342+_ACEOF
1343+rm -f conftest.$ac_objext
1344+if { (ac_try="$ac_compile"
1345+case "(($ac_try" in
1346+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
1347+  *) ac_try_echo=$ac_try;;
1348+esac
1349+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
1350+  (eval "$ac_compile") 2>conftest.er1
1351+  ac_status=$?
1352+  grep -v '^ *+' conftest.er1 >conftest.err
1353+  rm -f conftest.er1
1354+  cat conftest.err >&5
1355+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
1356+  (exit $ac_status); } &&
1357+	 { ac_try='test -z "$ac_c_werror_flag" || test ! -s conftest.err'
1358+  { (case "(($ac_try" in
1359+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
1360+  *) ac_try_echo=$ac_try;;
1361+esac
1362+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
1363+  (eval "$ac_try") 2>&5
1364+  ac_status=$?
1365+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
1366+  (exit $ac_status); }; } &&
1367+	 { ac_try='test -s conftest.$ac_objext'
1368+  { (case "(($ac_try" in
1369+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
1370+  *) ac_try_echo=$ac_try;;
1371+esac
1372+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
1373+  (eval "$ac_try") 2>&5
1374+  ac_status=$?
1375+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
1376+  (exit $ac_status); }; }; then
1377+  ac_header_compiler=yes
1378+else
1379+  echo "$as_me: failed program was:" >&5
1380+sed 's/^/| /' conftest.$ac_ext >&5
1381+
1382+	ac_header_compiler=no
1383+fi
1384+
1385+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
1386+{ echo "$as_me:$LINENO: result: $ac_header_compiler" >&5
1387+echo "${ECHO_T}$ac_header_compiler" >&6; }
1388+
1389+# Is the header present?
1390+{ echo "$as_me:$LINENO: checking $ac_header presence" >&5
1391+echo $ECHO_N "checking $ac_header presence... $ECHO_C" >&6; }
1392+cat >conftest.$ac_ext <<_ACEOF
1393+/* confdefs.h.  */
1394+_ACEOF
1395+cat confdefs.h >>conftest.$ac_ext
1396+cat >>conftest.$ac_ext <<_ACEOF
1397+/* end confdefs.h.  */
1398+#include <$ac_header>
1399+_ACEOF
1400+if { (ac_try="$ac_cpp conftest.$ac_ext"
1401+case "(($ac_try" in
1402+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
1403+  *) ac_try_echo=$ac_try;;
1404+esac
1405+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
1406+  (eval "$ac_cpp conftest.$ac_ext") 2>conftest.er1
1407+  ac_status=$?
1408+  grep -v '^ *+' conftest.er1 >conftest.err
1409+  rm -f conftest.er1
1410+  cat conftest.err >&5
1411+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
1412+  (exit $ac_status); } >/dev/null; then
1413+  if test -s conftest.err; then
1414+    ac_cpp_err=$ac_c_preproc_warn_flag
1415+    ac_cpp_err=$ac_cpp_err$ac_c_werror_flag
1416+  else
1417+    ac_cpp_err=
1418+  fi
1419+else
1420+  ac_cpp_err=yes
1421+fi
1422+if test -z "$ac_cpp_err"; then
1423+  ac_header_preproc=yes
1424+else
1425+  echo "$as_me: failed program was:" >&5
1426+sed 's/^/| /' conftest.$ac_ext >&5
1427+
1428+  ac_header_preproc=no
1429+fi
1430+
1431+rm -f conftest.err conftest.$ac_ext
1432+{ echo "$as_me:$LINENO: result: $ac_header_preproc" >&5
1433+echo "${ECHO_T}$ac_header_preproc" >&6; }
1434+
1435+# So?  What about this header?
1436+case $ac_header_compiler:$ac_header_preproc:$ac_c_preproc_warn_flag in
1437+  yes:no: )
1438+    { echo "$as_me:$LINENO: WARNING: $ac_header: accepted by the compiler, rejected by the preprocessor!" >&5
1439+echo "$as_me: WARNING: $ac_header: accepted by the compiler, rejected by the preprocessor!" >&2;}
1440+    { echo "$as_me:$LINENO: WARNING: $ac_header: proceeding with the compiler's result" >&5
1441+echo "$as_me: WARNING: $ac_header: proceeding with the compiler's result" >&2;}
1442+    ac_header_preproc=yes
1443+    ;;
1444+  no:yes:* )
1445+    { echo "$as_me:$LINENO: WARNING: $ac_header: present but cannot be compiled" >&5
1446+echo "$as_me: WARNING: $ac_header: present but cannot be compiled" >&2;}
1447+    { echo "$as_me:$LINENO: WARNING: $ac_header:     check for missing prerequisite headers?" >&5
1448+echo "$as_me: WARNING: $ac_header:     check for missing prerequisite headers?" >&2;}
1449+    { echo "$as_me:$LINENO: WARNING: $ac_header: see the Autoconf documentation" >&5
1450+echo "$as_me: WARNING: $ac_header: see the Autoconf documentation" >&2;}
1451+    { echo "$as_me:$LINENO: WARNING: $ac_header:     section \"Present But Cannot Be Compiled\"" >&5
1452+echo "$as_me: WARNING: $ac_header:     section \"Present But Cannot Be Compiled\"" >&2;}
1453+    { echo "$as_me:$LINENO: WARNING: $ac_header: proceeding with the preprocessor's result" >&5
1454+echo "$as_me: WARNING: $ac_header: proceeding with the preprocessor's result" >&2;}
1455+    { echo "$as_me:$LINENO: WARNING: $ac_header: in the future, the compiler will take precedence" >&5
1456+echo "$as_me: WARNING: $ac_header: in the future, the compiler will take precedence" >&2;}
1457+
1458+    ;;
1459+esac
1460+{ echo "$as_me:$LINENO: checking for $ac_header" >&5
1461+echo $ECHO_N "checking for $ac_header... $ECHO_C" >&6; }
1462+if { as_var=$as_ac_Header; eval "test \"\${$as_var+set}\" = set"; }; then
1463+  echo $ECHO_N "(cached) $ECHO_C" >&6
1464+else
1465+  eval "$as_ac_Header=\$ac_header_preproc"
1466+fi
1467+ac_res=`eval echo '${'$as_ac_Header'}'`
1468+	       { echo "$as_me:$LINENO: result: $ac_res" >&5
1469+echo "${ECHO_T}$ac_res" >&6; }
1470+
1471+fi
1472+if test `eval echo '${'$as_ac_Header'}'` = yes; then
1473+  cat >>confdefs.h <<_ACEOF
1474+#define `echo "HAVE_$ac_header" | $as_tr_cpp` 1
1475+_ACEOF
1476+
1477+fi
1478+
1479+done
1480+
1481+
1482+{ echo "$as_me:$LINENO: checking for SQLExecDirect in -lodbc" >&5
1483+echo $ECHO_N "checking for SQLExecDirect in -lodbc... $ECHO_C" >&6; }
1484+if test "${ac_cv_lib_odbc_SQLExecDirect+set}" = set; then
1485+  echo $ECHO_N "(cached) $ECHO_C" >&6
1486+else
1487+  ac_check_lib_save_LIBS=$LIBS
1488+LIBS="-lodbc  $LIBS"
1489+cat >conftest.$ac_ext <<_ACEOF
1490+/* confdefs.h.  */
1491+_ACEOF
1492+cat confdefs.h >>conftest.$ac_ext
1493+cat >>conftest.$ac_ext <<_ACEOF
1494+/* end confdefs.h.  */
1495+
1496+/* Override any GCC internal prototype to avoid an error.
1497+   Use char because int might match the return type of a GCC
1498+   builtin and then its argument prototype would still apply.  */
1499+#ifdef __cplusplus
1500+extern "C"
1501+#endif
1502+char SQLExecDirect ();
1503+int
1504+main ()
1505+{
1506+return SQLExecDirect ();
1507+  ;
1508+  return 0;
1509+}
1510+_ACEOF
1511+rm -f conftest.$ac_objext conftest$ac_exeext
1512+if { (ac_try="$ac_link"
1513+case "(($ac_try" in
1514+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
1515+  *) ac_try_echo=$ac_try;;
1516+esac
1517+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
1518+  (eval "$ac_link") 2>conftest.er1
1519+  ac_status=$?
1520+  grep -v '^ *+' conftest.er1 >conftest.err
1521+  rm -f conftest.er1
1522+  cat conftest.err >&5
1523+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
1524+  (exit $ac_status); } &&
1525+	 { ac_try='test -z "$ac_c_werror_flag" || test ! -s conftest.err'
1526+  { (case "(($ac_try" in
1527+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
1528+  *) ac_try_echo=$ac_try;;
1529+esac
1530+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
1531+  (eval "$ac_try") 2>&5
1532+  ac_status=$?
1533+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
1534+  (exit $ac_status); }; } &&
1535+	 { ac_try='test -s conftest$ac_exeext'
1536+  { (case "(($ac_try" in
1537+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
1538+  *) ac_try_echo=$ac_try;;
1539+esac
1540+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
1541+  (eval "$ac_try") 2>&5
1542+  ac_status=$?
1543+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
1544+  (exit $ac_status); }; }; then
1545+  ac_cv_lib_odbc_SQLExecDirect=yes
1546+else
1547+  echo "$as_me: failed program was:" >&5
1548+sed 's/^/| /' conftest.$ac_ext >&5
1549+
1550+	ac_cv_lib_odbc_SQLExecDirect=no
1551+fi
1552+
1553+rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \
1554+      conftest$ac_exeext conftest.$ac_ext
1555+LIBS=$ac_check_lib_save_LIBS
1556+fi
1557+{ echo "$as_me:$LINENO: result: $ac_cv_lib_odbc_SQLExecDirect" >&5
1558+echo "${ECHO_T}$ac_cv_lib_odbc_SQLExecDirect" >&6; }
1559+if test $ac_cv_lib_odbc_SQLExecDirect = yes; then
1560+  cat >>confdefs.h <<_ACEOF
1561+#define HAVE_LIBODBC 1
1562+_ACEOF
1563+
1564+  LIBS="-lodbc $LIBS"
1565+
1566+fi
1567+
1568+    EXTRA_OBJECT="$EXTRA_OBJECT dblog.o"
1569+
1570+fi
1571+
1572+
1573 { echo "$as_me:$LINENO: checking whether to use included libpopt" >&5
1574 echo $ECHO_N "checking whether to use included libpopt... $ECHO_C" >&6; }
1575 if test x"$with_included_popt" = x"yes"; then
1576@@ -15483,6 +15745,7 @@ INSTALL_DATA!$INSTALL_DATA$ac_delim
1577 HAVE_REMSH!$HAVE_REMSH$ac_delim
1578 LIBOBJS!$LIBOBJS$ac_delim
1579 ALLOCA!$ALLOCA$ac_delim
1580+EXTRA_OBJECT!$EXTRA_OBJECT$ac_delim
1581 OBJ_SAVE!$OBJ_SAVE$ac_delim
1582 OBJ_RESTORE!$OBJ_RESTORE$ac_delim
1583 CC_SHOBJ_FLAG!$CC_SHOBJ_FLAG$ac_delim
1584@@ -15490,7 +15753,7 @@ BUILD_POPT!$BUILD_POPT$ac_delim
1585 LTLIBOBJS!$LTLIBOBJS$ac_delim
1586 _ACEOF
1587 
1588-  if test `sed -n "s/.*$ac_delim\$/X/p" conf$$subs.sed | grep -c X` = 71; then
1589+  if test `sed -n "s/.*$ac_delim\$/X/p" conf$$subs.sed | grep -c X` = 72; then
1590     break
1591   elif $ac_last_try; then
1592     { { echo "$as_me:$LINENO: error: could not make $CONFIG_STATUS" >&5
1593--- old/config.h.in
1594+++ new/config.h.in
1595@@ -155,6 +155,9 @@
1596 /* Define to 1 if you have the `nsl_s' library (-lnsl_s). */
1597 #undef HAVE_LIBNSL_S
1598 
1599+/* Define to 1 if you have the `odbc' library (-lodbc). */
1600+#undef HAVE_LIBODBC
1601+
1602 /* Define to 1 if you have the `popt' library (-lpopt). */
1603 #undef HAVE_LIBPOPT
1604 
1605@@ -280,6 +283,15 @@
1606 /* Define to 1 if you have the "socketpair" function */
1607 #undef HAVE_SOCKETPAIR
1608 
1609+/* Define to 1 if you have the <sqlext.h> header file. */
1610+#undef HAVE_SQLEXT_H
1611+
1612+/* Define to 1 if you have the <sqltypes.h> header file. */
1613+#undef HAVE_SQLTYPES_H
1614+
1615+/* Define to 1 if you have the <sql.h> header file. */
1616+#undef HAVE_SQL_H
1617+
1618 /* Define to 1 if you have the <stdint.h> header file. */
1619 #undef HAVE_STDINT_H
1620 
1621