Updated to apply cleanly to latest CVS source.
[rsync-patches.git] / ODBC-dblog.diff
1 Add support for logging daemon messages to an SQL database.
2
3 After applying this patch you'll need to run autoconf and autoheader to
4 generate updated versions of "configure" and "config.h.in".
5
6 You'll need to run configure with the --with-ODBC option in order for the
7 extended features to be active.
8
9 Patch provided by Steve Sether.
10
11 (Tweaked by Wayne Davison for rsync-style purposes but not compiled, so if the
12 dblog.c file has a compile problem, it's probably my fault...)
13
14
15 --- README-ODBC 2004-02-13 17:08:33.000000000 -0800
16 +++ README-ODBC 2004-04-07 22:42:28.000000000 -0700
17 @@ -0,0 +1,80 @@
18 +This patch adds the following options:
19 +
20 +"database logging"
21 +If set to True, rsync will attempt to connect to
22 +the specified datasource and write to the named tables.
23 +Defaults to False.
24 +
25 +
26 +"database datasource"
27 +Specifies the name of the ODBC data source to use.
28 +
29 +"database username"
30 +The username to use when connecting to the database.
31 +
32 +"database password"
33 +The password to use when connecting to the database.
34 +
35 +"transfer table name"
36 +The name of the transfer table to log to.
37 +This table contains individual filenames, file sizes, bytes transferred, checksum bytes transferred, operation (send or receive), and a timestamp.
38 +
39 +"session table name"
40 +The name of the session table to log to.
41 +This table contains the username, module name, module path, ip address, process ID, and a timestamp.
42 +
43 +"exit table name"
44 +The name of the exit table to log to.
45 +This table contains the total bytes read, total bytes written, total size
46 +of all files, error code, line the error occured at, file the error occured at
47 +and the text of the error. (most of which will be blank if the program exited normally).
48 +
49 +
50 +"unique id method"
51 +Different databases use different methods to get a unique identifier.
52 +Some databases support sequence objects, and use various forms of the
53 +nextval command to retrieve a unique identifier from it.  Other databases
54 +support an autonumber field, and support different methds of retrieving
55 +the ID used in the last insert.  Valid values for this option are:
56 +
57 +       nextval-postgres
58 +       uses the syntax of nextval for PostgreSQL databases
59 +
60 +       nextval-oracle
61 +       uses the syntax of nextval for Oracle databases
62 +
63 +       nextval-db2
64 +       uses the syntax of nextval for  DB2 databases
65 +
66 +       last_insert_id
67 +       uses the last_insert_id() command for the MySQL databases
68 +
69 +       @@IDENTITY
70 +       uses the @@IDENTITY command for Sybase databases
71 +
72 +       custom
73 +       Define your own method to get a unique identifier.  See the
74 +       "custom unique id select", and the "get custom id before select"
75 +       parameters.
76 +
77 +
78 +"sequence name"
79 +If your database supports sequences, list the name of the sequence to use
80 +for the session unique identifier.
81 +
82 +"custom unique id select"
83 +Only used if you specify the custom method in "unique id method".  This
84 +is a SQL statement to be executed to get a unique ID.  This SQL
85 +statement must return one column with the unique ID to use for
86 +the session ID.  Should be used in concert with the "get custom
87 +id before select" parameter.
88 +
89 +"get custom id before insert"
90 +This parameter is ignored unless the "unique id method" is set to custom.
91 +If set to true, the "custom unique id select" statement will
92 +be executed BEFORE the session row is inserted into the database.
93 +(as is done when a sequence is used for unique IDs).
94 +If False the statement will be executed after the session
95 +row is inserted (as is done when the session ID is automatically generates
96 +unique IDs).
97 +Defaults to True.
98 --- Makefile.in 10 Feb 2004 17:06:11 -0000      1.98
99 +++ Makefile.in 8 Apr 2004 05:56:31 -0000
100 @@ -32,7 +32,7 @@
101         zlib/inflate.o zlib/inftrees.o zlib/infutil.o zlib/trees.o \
102         zlib/zutil.o zlib/adler32.o
103  OBJS1=rsync.o generator.o receiver.o cleanup.o sender.o exclude.o util.o \
104 -       main.o checksum.o match.o syscall.o log.o backup.o
105 +       main.o checksum.o match.o syscall.o log.o backup.o @EXTRA_OBJECT@
106  OBJS2=options.o flist.o io.o compat.o hlink.o token.o uidlist.o socket.o \
107         fileio.o batch.o clientname.o
108  OBJS3=progress.o pipe.o
109 --- cleanup.c   27 Jan 2004 08:14:33 -0000      1.21
110 +++ cleanup.c   8 Apr 2004 05:56:31 -0000
111 @@ -138,7 +138,12 @@
112                         code = RERR_VANISHED;
113         }
114  
115 -       if (code) log_exit(code, file, line);
116 +       if (code) {
117 +               log_exit(code, file, line);
118 +#ifdef HAVE_LIBODBC
119 +               db_log_exit(code,file,line);
120 +#endif
121 +       }
122  
123         if (verbose > 2)
124                 rprintf(FINFO,"_exit_cleanup(code=%d, file=%s, line=%d): about to call exit(%d)\n", 
125 --- clientserver.c      14 Apr 2004 23:33:34 -0000      1.121
126 +++ clientserver.c      15 Apr 2004 18:51:14 -0000
127 @@ -311,6 +311,9 @@
128         exclude_path_prefix = NULL;
129  
130         log_init();
131 +#ifdef HAVE_LIBODBC
132 +       db_log_open();
133 +#endif
134  
135         if (use_chroot) {
136                 /*
137 @@ -429,6 +432,9 @@
138                         rprintf(FINFO,"rsync %s %s from %s@%s (%s)\n",
139                                 am_sender?"on":"to",
140                                 request, auth_user, host, addr);
141 +#ifdef HAVE_LIBODBC
142 +                       db_log_session();
143 +#endif
144                 } else {
145                         rprintf(FINFO,"rsync %s %s from %s (%s)\n",
146                                 am_sender?"on":"to",
147 --- configure.in        9 Apr 2004 18:09:16 -0000       1.190
148 +++ configure.in        15 Apr 2004 18:51:14 -0000
149 @@ -94,6 +94,8 @@
150         [  --with-rsync-path=PATH  set default --rsync-path to PATH (default: rsync)],
151         [ RSYNC_PATH="$with_rsync_path" ],
152         [ RSYNC_PATH="rsync" ])
153 +AC_ARG_WITH(ODBC,
154 +       [  --with-ODBC             compile in support for ODBC database logging])
155  
156  AC_DEFINE_UNQUOTED(RSYNC_PATH, "$RSYNC_PATH", [location of rsync on remote machine])
157  
158 @@ -458,6 +460,14 @@
159  if test x"$with_included_popt" != x"yes"
160  then
161      AC_CHECK_LIB(popt, poptGetContext, , [with_included_popt=yes])
162 +fi
163 +
164 +if test x"$with_ODBC" = x"yes"
165 +then
166 +    AC_CHECK_HEADERS(sql.h sqlext.h sqltypes.h)
167 +    AC_CHECK_LIB(odbc,SQLExecDirect)
168 +    EXTRA_OBJECT="$EXTRA_OBJECT dblog.o"
169 +    AC_SUBST(EXTRA_OBJECT)
170  fi
171  
172  AC_MSG_CHECKING([whether to use included libpopt])
173 --- dblog.c     2004-02-13 17:08:33.000000000 -0800
174 +++ dblog.c     2004-04-07 23:18:56.000000000 -0700
175 @@ -0,0 +1,352 @@
176 +/*
177 + *  ODBC Database logging functions
178 + *
179 + *  Written by Steve Sether, April 2004
180 + *  steve@vellmont.com
181 + */
182 +
183 +#include "rsync.h"
184 +
185 +#ifdef HAVE_SQL_H
186 +#include <sql.h>
187 +#else
188 +#ifdef HAVE_ODBC_SQL_H
189 +#include <odbc/sql.h>
190 +#endif
191 +#endif
192 +
193 +#ifdef HAVE_SQLEXT_H
194 +#include <sqlext.h>
195 +#else
196 +#ifdef HAVE_ODBC_SQLEXT_H
197 +#include <odbc/sqlext.h>
198 +#endif
199 +#endif
200 +
201 +#ifdef HAVE_SQLTYPES_H
202 +#include <sqltypes.h>
203 +#else
204 +#ifdef HAVE_ODBC_SQLTYPES_H
205 +#include <odbc/sqltypes.h>
206 +#endif
207 +#endif
208 +
209 +SQLHENV db_environ_handle;                     /* Handle ODBC environment */
210 +long result;                                   /* result of functions */
211 +SQLHDBC db_handle= NULL;                       /* database connection handle */
212 +SQLHSTMT sql_statement_handle;                 /* SQL statement handle */
213 +extern int am_sender;
214 +extern char *auth_user;
215 +extern int module_id;
216 +
217 +char sql_status[10];                           /* Status SQL */
218 +SQLINTEGER V_OD_err, V_OD_rowanz, V_OD_id;
219 +SQLSMALLINT V_OD_mlen, V_OD_colanz;
220 +char V_OD_msg[200], V_OD_buffer[200];
221 +SQLINTEGER session_id;
222 +
223 +
224 +/* This function simply removes invalid characters from the SQL statement
225 + * to prevent SQL injection attacks. */
226 +char *sanitizeSql(const char *input)
227 +{
228 +       char *out, *ptr;
229 +       const char *c;
230 +
231 +       if (strlen(input) > ((~(unsigned int)0)>>1)-3)
232 +               return 0;
233 +       if (!(out = ptr = new_array(char, strlen(input) * 2 + 1)))
234 +               return 0;
235 +
236 +       for (c = input;  *c;  c++) {
237 +               switch (*c) {
238 +               case '\'':
239 +                       *ptr++ = '\'';
240 +                       *ptr++ = '\'';
241 +                       break;
242 +               case '\b':
243 +                       *ptr++ = '\\';
244 +                       *ptr++ = 'b';
245 +                       break;
246 +               case '\n':
247 +                       *ptr++ = '\\';
248 +                       *ptr++ = 'n';
249 +                       break;
250 +               case '\r':
251 +                       *ptr++ = '\\';
252 +                       *ptr++ = 'r';
253 +                       break;
254 +               case '\t':
255 +                       *ptr++ = '\\';
256 +                       *ptr++ = 't';
257 +                       break;
258 +               default:
259 +                       *ptr++ = *c;
260 +                       break;
261 +               }
262 +       }
263 +       *ptr = '\0';
264 +       return out;
265 +}
266 +
267 +void db_log_open(void)
268 +{
269 +       if (lp_database_logging(module_id)) {
270 +               if (db_handle == NULL) {
271 +                       /* get ODBC environment handle */
272 +                       result = SQLAllocHandle(SQL_HANDLE_ENV,SQL_NULL_HANDLE,&db_environ_handle);
273 +                       if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
274 +                               rprintf(FERROR, "Error: couldn't get database environment handle\n");
275 +                               return;
276 +                       }
277 +
278 +                       /* Setting database enviroment */
279 +                       result = SQLSetEnvAttr(db_environ_handle, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
280 +                       if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
281 +                               rprintf(FERROR, "Error: couldn't set database environment.\n");
282 +                               SQLFreeHandle(SQL_HANDLE_ENV, db_environ_handle);
283 +                               db_environ_handle = NULL;
284 +                               return;
285 +                       }
286 +
287 +                       /* Get a database handle */
288 +                       result = SQLAllocHandle(SQL_HANDLE_DBC, db_environ_handle, &db_handle);
289 +                       if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
290 +                               rprintf(FERROR, "Error: couldn't allocate database handle\n");
291 +                               SQLFreeHandle(SQL_HANDLE_ENV, db_environ_handle);
292 +                               db_environ_handle = NULL;
293 +                               return;
294 +                       }
295 +
296 +                       /* Set connection attributes */
297 +                       SQLSetConnectAttr(db_handle, SQL_LOGIN_TIMEOUT, (SQLPOINTER *)5, 0);
298 +
299 +                       /* Connect to the database. */
300 +                       result = SQLConnect(db_handle, (SQLCHAR*) lp_database_datasource(module_id), SQL_NTS,
301 +                           (SQLCHAR*) lp_database_username(module_id), SQL_NTS,
302 +                           (SQLCHAR*) lp_database_password(module_id), SQL_NTS);
303 +
304 +                       if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
305 +                               SQLGetDiagRec(SQL_HANDLE_DBC, db_handle,1,
306 +                                   sql_status, &V_OD_err,V_OD_msg,100,&V_OD_mlen);
307 +                               rprintf(FERROR,"Error Connecting to Database %s\n",V_OD_msg);
308 +                               SQLFreeHandle(SQL_HANDLE_DBC,db_handle);
309 +                               db_handle = NULL;
310 +                               SQLFreeHandle(SQL_HANDLE_ENV, db_environ_handle);
311 +                               db_environ_handle = NULL;
312 +                               return;
313 +                       }
314 +                       rprintf(FLOG,"Connected to database!\n");
315 +
316 +                       /* get SQL statement handle */
317 +                       result = SQLAllocHandle(SQL_HANDLE_STMT, db_handle, &sql_statement_handle);
318 +                       if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
319 +                               SQLGetDiagRec(SQL_HANDLE_DBC, db_handle,1, sql_status,&V_OD_err,V_OD_msg,100,&V_OD_mlen);
320 +                               rprintf(FERROR,"Error in allocating SQL statement handle %s\n",V_OD_msg);
321 +                               SQLDisconnect(db_handle);
322 +                               SQLFreeHandle(SQL_HANDLE_DBC,db_handle);
323 +                               db_handle = NULL;
324 +                               SQLFreeHandle(SQL_HANDLE_ENV, db_environ_handle);
325 +                               db_environ_handle = NULL;
326 +                               return;
327 +                       }
328 +               } else {
329 +                       rprintf(FERROR,"Already connected to database\n");
330 +               }
331 +       }
332 +}
333 +
334 +void db_log_close()
335 +{
336 +       if (lp_database_logging(module_id)) {
337 +               if (sql_statement_handle != NULL) {
338 +                       /* free the statement handle first */
339 +                       SQLFreeHandle(SQL_HANDLE_STMT,sql_statement_handle);
340 +                       sql_statement_handle = NULL;
341 +               } else {
342 +                       rprintf(FERROR,"No sql statement handle to close\n");
343 +               }
344 +               if (db_handle != NULL) {
345 +                       /* disconnect, and free the database handle. */
346 +                       SQLDisconnect(db_handle);
347 +                       SQLFreeHandle(SQL_HANDLE_DBC,db_handle);
348 +                       db_handle = NULL;
349 +               } else {
350 +                       rprintf(FERROR,"Database already closed");
351 +               }
352 +               if (db_environ_handle != NULL) {
353 +                       /* free the environment handle */
354 +                       SQLFreeHandle(SQL_HANDLE_ENV, db_environ_handle);
355 +                       db_environ_handle = NULL;
356 +               } else {
357 +                       rprintf(FERROR,"No environment handle to close");
358 +               }
359 +       }
360 +}
361 +
362 +static long get_unique_session_id()
363 +{
364 +       long unique;
365 +       char strSqlStatement[1024];
366 +
367 +       if (db_handle != NULL) {
368 +               /* choose the appropriate select statement based upon which DBMS we're using.
369 +                * different datbases use different methods to get a unique ID.  Some use a counter
370 +                * object (sequence), others use an auto increment datatype and have a method
371 +                * to get the last ID inserted using this connection. */
372 +               if (strcmp(lp_unique_id_method(module_id),"nextval-postgresql") == 0) {
373 +                       snprintf(strSqlStatement,sizeof(strSqlStatement),"SELECT NEXTVAL('%s');",lp_sequence_name(module_id));
374 +               } else if (strcmp(lp_unique_id_method(module_id),"nextval-oracle") == 0) {
375 +                       snprintf(strSqlStatement,sizeof(strSqlStatement),"SELECT %s.NEXTVAL FROM dual;",lp_sequence_name(module_id));
376 +               } else if (strcmp(lp_unique_id_method(module_id),"nextval-db2") == 0) {
377 +                       snprintf(strSqlStatement,sizeof(strSqlStatement),"VALUES NEXTVAL FOR %s;",lp_sequence_name(module_id));
378 +               } else if (strcmp(lp_unique_id_method(module_id),"last_insert_id") == 0) {  /* MySql */
379 +                       snprintf(strSqlStatement,sizeof(strSqlStatement),"SELECT LAST_INSERT_ID()");
380 +               } else if (strcmp(lp_unique_id_method(module_id),"@@IDENTITY") == 0) {           /* Sybase */
381 +                       snprintf(strSqlStatement,sizeof(strSqlStatement),"SELECT @@IDENTITY");
382 +               } else if (strcmp(lp_unique_id_method(module_id),"custom") == 0){     /* Users custom statement */
383 +                       snprintf(strSqlStatement,sizeof(strSqlStatement),lp_custom_unique_id_select(module_id));
384 +               }
385 +
386 +               /* bind the 1st column to unique */
387 +               SQLBindCol(sql_statement_handle,1,SQL_C_LONG,&unique,150,&V_OD_err);
388 +               /* execute the SQL statement */
389 +               result = SQLExecDirect(sql_statement_handle,strSqlStatement,SQL_NTS);
390 +               if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
391 +                       SQLGetDiagRec(SQL_HANDLE_DBC, db_handle,1, sql_status,&V_OD_err,V_OD_msg,100,&V_OD_mlen);
392 +                       rprintf(FERROR,"Error at get_sequence:  Error in Select! %s %s\n",strSqlStatement,V_OD_msg);
393 +               } else {
394 +                       result = SQLFetch(sql_statement_handle);
395 +                       if (result != SQL_NO_DATA && unique != 0) {
396 +                               rprintf(FINFO,"Got unique sequence! %ld\n",unique);
397 +                       } else {
398 +                               rprintf(FERROR,"Error at get_sequence:  Didn't get unique session ID\n");
399 +                       }
400 +                       /* Close the cursor so the statement can be re-used */
401 +                       result = SQLFreeStmt(sql_statement_handle,SQL_CLOSE);
402 +                       if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
403 +                               SQLGetDiagRec(SQL_HANDLE_DBC, db_handle,1, sql_status,&V_OD_err,V_OD_msg,100,&V_OD_mlen);
404 +                               rprintf(FERROR,"Error at get_sequence: Error in closing SQL statement handle %s\n",V_OD_msg);
405 +                               return unique;
406 +                       }
407 +                       return unique;
408 +               }
409 +       }
410 +       rprintf(FERROR,"Error at get_sequence: Not connected to database\n");
411 +       return -1;
412 +}
413 +
414 +
415 +void db_log_session()
416 +{
417 +       char strSqlStatement[1024];
418 +       int gotSessionID = 0;
419 +       if (lp_database_logging(module_id)) {
420 +               /* if we're using a sequence via the nextval command to get a unique ID, we need to get it before
421 +                * we do the insert. We also get the unique ID  now if custom, and get_custom_id_before_insert is set. */
422 +               if (strcmp(lp_unique_id_method(module_id),"nextval-postgresql") == 0
423 +                   || strcmp(lp_unique_id_method(module_id),"nextval-oracle") == 0
424 +                   || strcmp(lp_unique_id_method(module_id),"nextval-db2") == 0
425 +                   || (strcmp(lp_unique_id_method(module_id),"custom") == 0
426 +                    && lp_get_custom_id_before_insert(module_id))) {
427 +                       session_id = get_unique_session_id();
428 +                       gotSessionID = 1;
429 +                       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());
430 +               } else {
431 +                       /* Otherwise the ID gets created automatically, and we get the ID it used after the insert. */
432 +                       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());
433 +               }
434 +
435 +               /* Insert the new session into the database */
436 +               result = SQLExecDirect(sql_statement_handle,strSqlStatement,SQL_NTS);
437 +               if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
438 +                       SQLGetDiagRec(SQL_HANDLE_DBC, db_handle,1, sql_status,&V_OD_err,V_OD_msg,100,&V_OD_mlen);
439 +                       rprintf(FERROR,"Error at db_log_session: Error in Insert %s %s\n",strSqlStatement,V_OD_msg);
440 +               }
441 +
442 +               /* close the cursor so the statement handle can be re-used. */
443 +               result = SQLFreeStmt(sql_statement_handle,SQL_CLOSE);
444 +               if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
445 +                       SQLGetDiagRec(SQL_HANDLE_DBC, db_handle,1, sql_status,&V_OD_err,V_OD_msg,100,&V_OD_mlen);
446 +                       rprintf(FERROR,"Error in resetting SQL statement handle %s\n",V_OD_msg);
447 +               }
448 +               /* get the session ID for databases that give the unique ID after an insert */
449 +               if (gotSessionID == 0) {
450 +                       session_id = get_unique_session_id();
451 +               }
452 +       }
453 +       else {
454 +               rprintf(FERROR,"Error at db_log_session:  Not connected to database!\n");
455 +       }
456 +}
457 +
458 +void db_log_transfer(struct file_struct *file,struct stats *initial_stats,char *operation)
459 +{
460 +       extern struct stats stats;
461 +       char strSqlStatement[1024];
462 +       char strFilePath[255];
463 +       char strFileName[255];
464 +       char strFileSize[255];
465 +       int64 intBytesTransferred;
466 +       int64 intCheckSumBytes;
467 +
468 +       if (lp_database_logging(module_id)) {
469 +               if (db_handle != NULL) {
470 +                       snprintf(strFileName,sizeof(strFileName), "%s",f_name(file));
471 +                       snprintf(strFilePath, sizeof(strFilePath), "%s", file->basedir?file->basedir:"");
472 +                       snprintf(strFileSize,sizeof(strFileSize),"%.0f", (double)file->length);
473 +                       if (am_sender) {
474 +                               intBytesTransferred = stats.total_written - initial_stats->total_written;
475 +                       } else {
476 +                               intBytesTransferred = stats.total_read - initial_stats->total_read;
477 +                       }
478 +
479 +                       if (!am_sender) {
480 +                               intCheckSumBytes = stats.total_written - initial_stats->total_written;
481 +                       } else {
482 +                               intCheckSumBytes = stats.total_read - initial_stats->total_read;
483 +                       }
484 +
485 +                       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);
486 +                       result = SQLExecDirect(sql_statement_handle,strSqlStatement,SQL_NTS);
487 +                       if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
488 +                               SQLGetDiagRec(SQL_HANDLE_DBC, db_handle,1, sql_status,&V_OD_err,V_OD_msg,100,&V_OD_mlen);
489 +                               rprintf(FERROR,"Error at db_log_transfer:  Error in Insert %s %s\n",strSqlStatement,V_OD_msg);
490 +                               if (result == SQL_INVALID_HANDLE)
491 +                                       rprintf(FERROR,"INVALID HANDLE\n");
492 +                       }
493 +               } else {
494 +                       rprintf(FERROR,"Error at db_log_transfer: Not connected to database!\n");
495 +               }
496 +       }
497 +}
498 +
499 +
500 +void db_log_exit(int code, const char *file, int line)
501 +{
502 +       char strSqlStatement[2048];
503 +       const char *error_text;
504 +       extern struct stats stats;
505 +       if (db_handle != NULL) {
506 +               if (lp_database_logging(module_id)) {
507 +                       if (code != 0) {
508 +                               error_text = rerr_name(code);
509 +                               if (!error_text) {
510 +                                       error_text = "unexplained error";
511 +                               }
512 +                       } else {
513 +                               error_text = "";
514 +                       }
515 +                       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);
516 +
517 +                       result = SQLExecDirect(sql_statement_handle,strSqlStatement,SQL_NTS);
518 +
519 +                       if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
520 +                               SQLGetDiagRec(SQL_HANDLE_DBC, db_handle,1, sql_status,&V_OD_err,V_OD_msg,100,&V_OD_mlen);
521 +                               rprintf(FERROR,"Error at db_log_exit: Error in Insert %s %s\n",strSqlStatement,V_OD_msg);
522 +                       }
523 +               }
524 +       } else {
525 +               rprintf(FERROR,"Error at db_log_exit: Not connected to database!\n");
526 +       }
527 +}
528 --- dblog-tables-mysql.sql      2004-02-13 17:08:33.000000000 -0800
529 +++ dblog-tables-mysql.sql      2004-04-07 23:00:13.000000000 -0700
530 @@ -0,0 +1,43 @@
531 +drop table transfer;
532 +drop table exit;
533 +drop table session;
534 +
535 +CREATE TABLE session (
536 +       id                      int auto_increment NOT NULL,
537 +       date                    timestamp NOT NULL,
538 +       ip_address              varchar(15) NOT NULL,
539 +       username                varchar(20) NOT NULL,
540 +       module_name             varchar(20) NOT NULL,
541 +       module_path             varchar(255) NOT NULL,
542 +       process_id              int NOT NULL,
543 +       Primary Key (id)
544 +);
545 +
546 +CREATE TABLE transfer (
547 +       id                      int auto_increment NOT NULL,
548 +       session_id              int NOT NULL,
549 +       date                    timestamp NOT NULL,
550 +       file_path               varchar(255) NOT NULL,
551 +       file_name               varchar(255) NOT NULL,
552 +       file_size               bigint NOT NULL,
553 +       bytes_transferred       bigint NOT NULL,
554 +       checksum_bytes_transferred bigint NOT NULL,
555 +       operation               varchar(20),
556 +       Primary Key (id),
557 +       foreign key (session_id) references session (id)
558 +);
559 +
560 +CREATE TABLE exit (
561 +       id                      int auto_increment NOT NULL,
562 +       session_id              int NOT NULL,
563 +       date                    timestamp NOT NULL,
564 +       total_bytes_written     bigint NOT NULL,
565 +       total_bytes_read        bigint NOT NULL,
566 +       total_size              bigint NOT NULL,
567 +       error_text              varchar(128) NOT NULL,
568 +       error_code              int NOT NULL,
569 +       error_file              varchar(64) NOT NULL,
570 +       error_line              int NOT NULL,
571 +       Primary Key (id),
572 +       foreign key (session_id) references session (id)
573 +);
574 --- dblog-tables-postgresql.sql 2004-02-13 17:08:33.000000000 -0800
575 +++ dblog-tables-postgresql.sql 2004-04-07 22:59:55.000000000 -0700
576 @@ -0,0 +1,45 @@
577 +drop table transfer;
578 +drop table exit;
579 +drop table session;
580 +drop sequence session_id_seq;
581 +create sequence session_id_seq;
582 +
583 +CREATE TABLE "session" (
584 +       "id"                    int NOT NULL,
585 +       "date"                  timestamp NOT NULL default now(),
586 +       "ip_address"            varchar(15) NOT NULL,
587 +       "username"              varchar(20) NOT NULL,
588 +       "module_name"           varchar(20) NOT NULL,
589 +       "module_path"           varchar(255) NOT NULL,
590 +       "process_id"            int NOT NULL,
591 +       Primary Key (id)
592 +);
593 +
594 +CREATE TABLE "transfer" (
595 +       "id"                    serial NOT NULL,
596 +       "session_id"            int NOT NULL,
597 +       "date"                  timestamp NOT NULL default now(),
598 +       "file_path"             varchar(512) NOT NULL,
599 +       "file_name"             varchar(512) NOT NULL,
600 +       "file_size"             bigint NOT NULL,
601 +       "bytes_transferred"     bigint NOT NULL,
602 +       "checksum_bytes_transferred" bigint NOT NULL,
603 +       "operation"             varchar(20),
604 +       Primary Key (id),
605 +       foreign key (session_id) references session (id)
606 +);
607 +
608 +CREATE TABLE "exit" (
609 +       "id"                    serial NOT NULL,
610 +       "session_id"            int NOT NULL,
611 +       "date"                  timestamp NOT NULL default now(),
612 +       "total_bytes_written"   bigint NOT NULL,
613 +       "total_bytes_read"      bigint NOT NULL,
614 +       "total_size"            bigint NOT NULL,
615 +       "error_text"            varchar(128) NOT NULL,
616 +       "error_code"            int NOT NULL,
617 +       "error_file"            varchar(64) NOT NULL,
618 +       "error_line"            int NOT NULL,
619 +       Primary Key (id),
620 +       foreign key (session_id) references session (id)
621 +);
622 --- loadparm.c  4 Feb 2004 07:31:29 -0000       1.50
623 +++ loadparm.c  8 Apr 2004 06:31:18 -0000
624 @@ -122,6 +122,17 @@
625         BOOL list;
626         BOOL use_chroot;
627         BOOL transfer_logging;
628 +       BOOL database_logging;
629 +       char *database_datasource;
630 +       char *database_username;
631 +       char *database_password;
632 +       char *transfer_table_name;
633 +       char *exit_table_name;
634 +       char *session_table_name;
635 +       char *sequence_name;
636 +       char *unique_id_method;
637 +       char *custom_unique_id_select;
638 +       BOOL get_custom_id_before_insert;
639         BOOL ignore_errors;
640         char *uid;
641         char *gid;
642 @@ -154,6 +165,17 @@
643         True,    /* list */
644         True,    /* use chroot */
645         False,   /* transfer logging */
646 +       False,   /* Database Logging */
647 +       NULL,    /* Database datasource */
648 +       NULL,    /* Database username */
649 +       NULL,    /* Database password */
650 +       NULL,    /* Transfer table name */
651 +       NULL,    /* Exit table name */
652 +       NULL,    /* Session table name */
653 +       NULL,    /* sequence name */
654 +       NULL,    /* unique method */
655 +       NULL,    /* custom unique id select*/
656 +       True,    /* get custom id before insert */
657         False,   /* ignore errors */
658         "nobody",/* uid */
659  
660 @@ -292,6 +314,17 @@
661    {"include",          P_STRING,  P_LOCAL,  &sDefault.include,     NULL,   0},
662    {"include from",     P_STRING,  P_LOCAL,  &sDefault.include_from,NULL,   0},
663    {"transfer logging", P_BOOL,    P_LOCAL,  &sDefault.transfer_logging,NULL,0},
664 +  {"database logging", P_BOOL,    P_LOCAL,  &sDefault.database_logging,NULL,0},
665 +  {"database datasource",P_STRING,P_LOCAL,  &sDefault.database_datasource,NULL,0},
666 +  {"database username",P_STRING,  P_LOCAL,  &sDefault.database_username,NULL,0},
667 +  {"database password",P_STRING,  P_LOCAL,  &sDefault.database_password,NULL,0},
668 +  {"transfer table name",P_STRING,P_LOCAL,  &sDefault.transfer_table_name,NULL,0},
669 +  {"exit table name",  P_STRING,  P_LOCAL,  &sDefault.exit_table_name,NULL,0},
670 +  {"session table name",P_STRING, P_LOCAL,  &sDefault.session_table_name,NULL,0},
671 +  {"sequence name",    P_STRING,  P_LOCAL,  &sDefault.sequence_name,NULL,0},
672 +  {"unique id method", P_STRING,  P_LOCAL,  &sDefault.unique_id_method,NULL,0},
673 +  {"custom unique id select",P_STRING,P_LOCAL,&sDefault.custom_unique_id_select,NULL,0},
674 +  {"get custom id before insert",P_BOOL,P_LOCAL,&sDefault.get_custom_id_before_insert,NULL,0},
675    {"ignore errors",    P_BOOL,    P_LOCAL,  &sDefault.ignore_errors,NULL,0},
676    {"log format",       P_STRING,  P_LOCAL,  &sDefault.log_format,  NULL,   0},
677    {"refuse options",   P_STRING,  P_LOCAL,  &sDefault.refuse_options,NULL, 0},
678 @@ -359,6 +392,17 @@
679  FN_LOCAL_BOOL(lp_list, list)
680  FN_LOCAL_BOOL(lp_use_chroot, use_chroot)
681  FN_LOCAL_BOOL(lp_transfer_logging, transfer_logging)
682 +FN_LOCAL_BOOL(lp_database_logging, database_logging)
683 +FN_LOCAL_STRING(lp_database_datasource, database_datasource)
684 +FN_LOCAL_STRING(lp_database_username, database_username)
685 +FN_LOCAL_STRING(lp_database_password, database_password)
686 +FN_LOCAL_STRING(lp_transfer_table_name, transfer_table_name)
687 +FN_LOCAL_STRING(lp_exit_table_name, exit_table_name)
688 +FN_LOCAL_STRING(lp_session_table_name,session_table_name)
689 +FN_LOCAL_STRING(lp_sequence_name,sequence_name)
690 +FN_LOCAL_STRING(lp_unique_id_method,unique_id_method)
691 +FN_LOCAL_STRING(lp_custom_unique_id_select,custom_unique_id_select)
692 +FN_LOCAL_BOOL(lp_get_custom_id_before_insert,get_custom_id_before_insert)
693  FN_LOCAL_BOOL(lp_ignore_errors, ignore_errors)
694  FN_LOCAL_BOOL(lp_ignore_nonreadable, ignore_nonreadable)
695  FN_LOCAL_STRING(lp_uid, uid)
696 --- log.c       20 Jan 2004 05:15:14 -0000      1.71
697 +++ log.c       8 Apr 2004 05:56:32 -0000
698 @@ -75,7 +75,7 @@
699  /*
700   * Map from rsync error code to name, or return NULL.
701   */
702 -static char const *rerr_name(int code)
703 +char const *rerr_name(int code)
704  {
705         int i;
706         for (i = 0; rerr_names[i].name; i++) {
707 --- main.c      10 Feb 2004 03:54:47 -0000      1.192
708 +++ main.c      8 Apr 2004 05:56:32 -0000
709 @@ -120,6 +120,9 @@
710  
711         if (am_daemon) {
712                 log_exit(0, __FILE__, __LINE__);
713 +#ifdef HAVE_LIBODBC
714 +               db_log_exit(0,__FILE__,__LINE__);
715 +#endif
716                 if (f == -1 || !am_sender) return;
717         }
718  
719 --- proto.h     14 Apr 2004 23:33:30 -0000      1.188
720 +++ proto.h     15 Apr 2004 18:51:15 -0000
721 @@ -51,6 +51,12 @@
722  int daemon_main(void);
723  void setup_protocol(int f_out,int f_in);
724  int claim_connection(char *fname,int max_connections);
725 +char *sanitizeSql(const char *input);
726 +void db_log_open(void);
727 +void db_log_close();
728 +void db_log_session();
729 +void db_log_transfer(struct file_struct *file,struct stats *initial_stats,char *operation);
730 +void db_log_exit(int code, const char *file, int line);
731  void free_exclude_list(struct exclude_list_struct *listp);
732  int check_exclude(struct exclude_list_struct *listp, char *name, int name_is_dir,
733                   const char *type);
734 @@ -136,6 +142,17 @@
735  BOOL lp_list(int );
736  BOOL lp_use_chroot(int );
737  BOOL lp_transfer_logging(int );
738 +BOOL lp_database_logging(int );
739 +char *lp_database_datasource(int );
740 +char *lp_database_username(int );
741 +char *lp_database_password(int );
742 +char *lp_transfer_table_name(int );
743 +char *lp_exit_table_name(int );
744 +char *lp_session_table_name(int );
745 +char *lp_sequence_name(int );
746 +char *lp_unique_id_method(int );
747 +char *lp_custom_unique_id_select(int );
748 +BOOL lp_get_custom_id_before_insert(int );
749  BOOL lp_ignore_errors(int );
750  BOOL lp_ignore_nonreadable(int );
751  char *lp_uid(int );
752 @@ -157,6 +174,7 @@
753  BOOL lp_load(char *pszFname, int globals_only);
754  int lp_numservices(void);
755  int lp_number(char *name);
756 +char const *rerr_name(int code);
757  void log_init(void);
758  void log_open(void);
759  void log_close(void);
760 --- receiver.c  23 Mar 2004 16:50:40 -0000      1.75
761 +++ receiver.c  8 Apr 2004 05:56:32 -0000
762 @@ -453,7 +453,9 @@
763                 recv_ok = receive_data(f_in,mapbuf,fd2,fname,file->length);
764  
765                 log_recv(file, &initial_stats);
766 -
767 +#ifdef HAVE_LIBODBC
768 +               db_log_transfer(file, &initial_stats,"receive");
769 +#endif
770                 if (mapbuf) unmap_file(mapbuf);
771                 if (fd1 != -1) {
772                         close(fd1);
773 --- sender.c    17 Feb 2004 21:57:44 -0000      1.38
774 +++ sender.c    8 Apr 2004 05:56:32 -0000
775 @@ -283,6 +283,9 @@
776                 } else  {
777                         match_sums(f_out, s, buf, st.st_size);
778                         log_send(file, &initial_stats);
779 +#ifdef HAVE_LIBODBC
780 +                       db_log_transfer(file, &initial_stats,"send");
781 +#endif
782                 }
783  
784                 if (!read_batch) {