From b63c5d2438aabf0d7721c38387995cb4fb98345f Mon Sep 17 00:00:00 2001 From: Christopher Powell Date: Sun, 21 Apr 2002 23:01:53 +0000 Subject: Significant bugfixes and feature additions on the way to 1.16... --- CHANGELOG | 45 +++- INSTALL | 45 ++-- Makefile | 26 +-- README | 151 +++---------- mod_log_sql.c | 674 +++++++++++++++++++++++++++++++++------------------------- 5 files changed, 493 insertions(+), 448 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index a1cab46..a5a4c4f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,14 +1,55 @@ -$Id: CHANGELOG,v 1.8 2002/04/08 06:35:04 helios Exp $ +$Id: CHANGELOG,v 1.9 2002/04/21 23:01:52 helios Exp $ TODO: * Full commenting of the code. * Rethink documentation flow and rewrite? -* Port connection portion to other DBMS? Genericize the module? +* Port connection portion to other DBMS? Genericize the module? Start with PostgreSQL. +* Fully test create-table +* Document new features +* preserve and socket configurable in .conf +* segfault when mvh on CHANGES: +1.16: +* Moved all the user DEFINEs inside the .c file -- splitting them + between the C and the Makefile was getting just too cumbersome. +* A new MySQLPreserveFile runtime config directive. In the last + version the name of the preserve-file was hardcoded and therefore + global across all Apache virtual servers. Now the user can configure + this on a per-virthost basis. It defaults to a hardcoded value + if the user does not define it. +* A new MySQLSocketFile runtime config directive. In the last + version the name of the MySQL socket was hardcoded. Now the user + can configure this at Apache runtime. However, it is a global + setting (set once) just like the rest of the actual database info is. + It defaults ot a hardcoded value if the user does not define it. +* A new MySQLCreateTables runtime config directive. Module can now + create the access table on-the-fly. Table creation takes place + during the virtual server's first request and is flagged after that. +* A new MySQLMassVirtualHosting runtime config directive. This flag + currently only activates a single feature: each virtual server + gets its very own exclusive table prefixed 'access_' with the + server's name following. It also implies MySQLCreateTables On. +* escape_query (was mysql_escape_log) is now called on every item + rather than first checking to see if it needs to be called, which + was probably a big waste of time. Furthermore the routine now + uses a native MySQL API call to do the escaping instead of doing + this 'manually.' It attempts to use the charset-respectful MySQL + call first, but falls back on a more generic call if the MySQL + server is unavailable (e.g. if it goes offline). +* Open preserve file with pfopen instead of regular fopen to + take advantage of pool structure. +* As forewarned, I finally got rid of the code to support separate + Referer and Agent logs. +* Finally brought the make process up-to-date with the way Apache + likes modules to be done. +* Cookies are now configurable on a per-virtualserver basis. Before + they were on a global basis. + + 1.15: * Vastly improved error reporting is a lot clearer about lost db connections, etc. Some unreachable code has been corrected. diff --git a/INSTALL b/INSTALL index 3226e89..c620963 100644 --- a/INSTALL +++ b/INSTALL @@ -1,4 +1,4 @@ -$Id: INSTALL,v 1.6 2002/04/02 20:19:30 helios Exp $ +$Id: INSTALL,v 1.7 2002/04/21 23:01:52 helios Exp $ Requirements @@ -7,7 +7,7 @@ Requirements * I run a Red Hat 6.2 system, but these instructions should easily adapt to any modern distro. -* Apache 1.2.x or higher installed. (I run 1.3.22 and it works fine). +* Apache 1.2 or 1.3 installed. (I run 1.3.22 and it works fine). You should have already successfully compiled Apache and know what you're doing there. In fact, you should already have any other modules and add-ons like mod_ssl or PHP configured and installed @@ -15,9 +15,9 @@ Requirements * The MySQL development headers. (I run MySQL-devel-3.23.44-1.i386.rpm). -* MySQL configured, installed and running on either localhost or an - accessible networked machine. You should already have a basic - understanding of MySQL and how it functions. +* MySQL >= 3.23.15 configured, installed and running on either + localhost or an accessible networked machine. You should already + have a basic understanding of MySQL and how it functions. * Again, basic administrative skills with Apache and MySQL. I try to make things as easy as possible in this README, but its purpose is @@ -55,19 +55,15 @@ For folks interested in using this module as an Apache DSO: fully explained in the Makefile. 3) Instruct apxs to compile and install the module as a DSO. You need - to know three things before you run apxs: + to know two things before you run apxs: - The location of the apxs binary, find using 'locate apxs' - The location of your MySQL libraries, find using 'locate libmysqlclient' - - The location of your mysql.sock socket file. If your MySQL is running on - a different machine (in other words, communication is via TCP/IP and not - sockets), this value will be ignored BUT IT STILL MUST BE DEFINED - AS SOMETHING/ANYTHING FOR COMPILATION TO WORK. FORMAT: - # /apxs -i -c -DMYSQLSOCKET='\"/path/to/mysql.sock\"' -L/path/to/mysqllibs -lmysqlclient -lz mod_log_mysql.c + # /apxs -i -c -L/path/to/mysqllibs -lmysqlclient -lz mod_log_mysql.c EXAMPLE: - # /usr/sbin/apxs -i -c -DMYSQLSOCKET='\"/var/lib/mysql/mysql.sock\"' -L/usr/lib/mysql -lmysqlclient -lz mod_log_mysql.c + # /usr/sbin/apxs -i -c -L/usr/lib/mysql -lmysqlclient -lz mod_log_mysql.c You should see something like this: @@ -106,8 +102,9 @@ Installation (as a static module compiled into httpd) # tar zxf mod_log_mysql.tar.gz -C /usr/local/src # cd /usr/local/src/mod_log_mysql -2) Edit Makefile and make any adjustments for your system. These are - fully explained in the Makefile. +2) Examine the DEFINEs at the top of mod_log_mysql.c and alter any that + you choose. Edit Makefile and make any adjustments for your system. + These are fully explained in the Makefile. 3) # make all (You should receive NO warnings or errors of any kind. @@ -128,8 +125,8 @@ Installation (as a static module compiled into httpd) * Append the following string to the EXTRA_LIBS= line. (/usr/lib/mysql is where your libmysqlclient.a file lives): -L/usr/lib/mysql -lmysqlclient -lm -lz - * Add this line at the end of the file: - Module mysql_log_module mod_log_mysql.o + * Find the mod_log_config.o line, and add this line immediately after it: + AddModule modules/sql/mod_log_mysql.o 6b) # cp Configuration.apaci Configuration @@ -196,7 +193,10 @@ Installation (as a static module compiled into httpd) AddModule mod_log_mysql.c -10) Create a database and table to hold the new log data. I log the +10) If you compiled mod_log_mysql with the ability to make its own tables + then you can skip this step. Otherwise you need to do it by hand: + + Create a database and table to hold the new log data. I log the same data as the regular "combined log" plus a little extra information that can be useful. @@ -218,9 +218,14 @@ Installation (as a static module compiled into httpd) 11) Create a specific MySQL userid that httpd will use to authenticate and enter data. This userid need not be an actual Unix user. It - is a userid internal to MySQL with specific privileges. To create a - user called "loguser" with the password "l0gger" with only the - capability of INSERT to "access_log": + is a userid internal to MySQL with specific privileges. + + mysql> grant insert,create on apache.* to loguser@my.apachemachine.com identified by 'l0gger'; + + Security is a very real concern. mod_log_mysql by default is + set up to create the SQL tables it needs. If you have deactivated + this capability, then create a user called "loguser" with the password + "l0gger" with only the capability of INSERT to "access_log": mysql> grant insert on apache.access_log to loguser@my.apachemachine.com identified by 'l0gger'; diff --git a/Makefile b/Makefile index be6f06c..6c3c98f 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ -# $Id: Makefile,v 1.8 2002/04/08 06:37:14 helios Exp $ -MLMVERS = 1.15 +# $Id: Makefile,v 1.9 2002/04/21 23:01:52 helios Exp $ +MLMVERS = 1.16 # Where you unpacked your Apache tarball -- the source. APACHESOURCE = /usr/local/src/apache_1.3.22 @@ -7,18 +7,9 @@ APACHESOURCE = /usr/local/src/apache_1.3.22 # Where Apache [got|will get] installed APACHEINST = /usr/local/Apache -# Use the first DEFS line if you want mod_log_mysql to be able to log SSL -# variables like keysize or cipher. Use the second one if you don't use SSL -# or don't care to log it. -# -# If your MySQL db is running on the same machine as Apache, modify the -# MYSQLSOCKET path to point to your MySQL socket. This define has no effect -# if your MySQL machine is a networked (TCP/IP) machine. - -DEFS = -DMYSQLSOCKET="\"/var/lib/mysql/mysql.sock\"" -DWANT_SSL_LOGGING -#DEFS = -DMYSQLSOCKET="\"/var/lib/mysql/mysql.sock\"" - -# Use the first CFLAGS if you *do* WANT_SSL_LOGGING, and confirm the paths. +# Set the WANT_SSL_LOGGING define in mod_log_mysql.c if you want to log SSL +# info, or #undef it if you don't. Then use the first CFLAGS if you *do* +# WANT_SSL_LOGGING, and confirm the paths. # # Modify "/usr/local/ssl/include" to where YOUR openssl/*.h files are, # Modify "/usr/include/db1" to where YOUR ndbm.h can be found, @@ -53,10 +44,13 @@ INSTALL = /usr/bin/install -m 664 all: mod_log_mysql.o mod_log_mysql.o: mod_log_mysql.c Makefile - $(CC) ${CFLAGS} ${DEFS} -c mod_log_mysql.c + $(CC) ${CFLAGS} -c mod_log_mysql.c install: all - $(INSTALL) mod_log_mysql.o ${APACHESOURCE}/src/mod_log_mysql.o + $(INSTALL) -d -m 755 ${APACHESOURCE}/src/modules/sql + $(INSTALL) mod_log_mysql.c ${APACHESOURCE}/src/modules/sql/mod_log_mysql.c + $(INSTALL) Makefile ${APACHESOURCE}/src/modules/sql/Makefile + $(INSTALL) mod_log_mysql.o ${APACHESOURCE}/src/modules/sql/mod_log_mysql.o distro: all cp -f INSTALL ${APACHEINST}/html/mod_log_mysql/ diff --git a/README b/README index 977080f..7bea822 100644 --- a/README +++ b/README @@ -1,4 +1,4 @@ -$Id: README,v 1.4 2002/04/08 07:06:20 helios Exp $ +$Id: README,v 1.5 2002/04/21 23:01:53 helios Exp $ Homepage @@ -13,35 +13,25 @@ Approach In order to save speed and overhead, links are kept alive in between queries. This module uses one SQL link per httpd process. Among other things, this means that this module supports logging into only one -MySQL server, and for now, also, only one SQL database (although the -latter limitation can be relatively easily removed). - -Different data can be sent to different tables. i.e., it's possible to -define one table for TransferLog, one for RefererLog, and a 3rd for -AgentLog. [ Note: this is now deprecated behavior. Please consider -logging Agent and Referer to the same table as your transfers. ] +MySQL server, and for now, also, only one SQL database. Virtual hosts are supported in the same manner they are in the regular logging modules. If you specify a different table for a virtual -host it will be used, otherwise the 'general' would be used. Note: -since all 3 types of logs are implemented within the same module, if -you specify an overriding table for a virtual host for one type of log, -it'll ignore any previous 'general' defaults (see the example in the -end). - -SQL links are opened on demand (i.e., the first time each httpd needs -to log something to SQL, the link is opened). In case the SQL server -is down when trying to connect to it, the module remains silent and -logs no error (I didn't want thousands of error messages in the -logfile). In case the SQL link is broken ("mysql server has gone -away") a proper error message is kept to the error log (textual :), and -the module tries to reestablish the concact (and reports whether it -succeeded or not in the error log). If the link cannot be -reestablished, the module will, again, remain silent. Technical note: -The SQL link is registered using apache's pool mechanism, so SQL links -are properly closed on any normal shutdown, kill -HUP or kill -TERM. -This also means that if you restart the MySQL daemon for any reason you -should restart Apache. +host it will be used, otherwise the 'general' would be used. + +SQL links are opened by each child process when it is born. Error reporting +is robust throughout and will let you know about database issues +in the standard Apache error-log for the server or virtual server. + +A robust "preserve" capability has now been implemented as well. This +permits the module to preserve any failed INSERT commands to a local +file on its machine. In any situation that the database is unavailable -- +e.g. the network fails, you reboot the machine, etc. -- mod_log_mysql +will note this in the error log and begin appending its log entries to +the preserve file. At the time that your MySQL server returns to service, +each of these preserve files is easily imported because it is stored in SQL: + + # mysql -uadminuser -p mydbname < /tmp/mysql-preserve @@ -63,85 +53,22 @@ What gets logged by default? All the data that would be contained in the "Combined Log Format" is logged by default, plus a little extra. Your best bet is to -accept this default and employ the enclosed access_log.sql to -format your table. Customize your logging format after you've -had a chance to experiment with the default first. - -If you just want to log enough data to be able to reconstruct -a Combined Log Format log, log these: - -+------------------+------------------+ -| Field | Type | -+------------------+------------------+ -| remote_host | varchar(50) | -| remote_user | varchar(50) | -| request_uri | varchar(50) | -| virtual_host | varchar(50) | -| time_stamp | int(10) unsigned | -| status | smallint(6) | -| bytes_sent | int(11) | -| referer | varchar(255) | -| agent | varchar(255) | -| request_method | varchar(6) | -| request_protocol | varchar(10) | -+------------------+------------------+ - -remote_host: corresponds to the Apache %h directive. Contains the remote - hostname or IP of the machine accessing your server. - Example: si4002.inktomi.com - -remote_user: corresponds to the Apache %u directive. Contains the - userid of people who have authenticated to your server, if applicable. - Example: freddy - -request_uri: corresponds to the Apache %U directive. Contains the - URL path requested, excluding any query string. This is different than - the %r information you might be used to seeing: - - %r: GET /cgi-bin/neomail.pl?sessionid=freddy-session-0.742143231719&sort=date_rev HTTP/1.1 - %U: /cgi-bin/neomail.pl - - We log %U because it contains the real meat of the information that is - needed for log analysis, and saves the database a LOT of wasted growth - on unneeded bytes. - -virtual_host: contains the VirtualHost that is making the log entry. This - allows you to log multiple VirtualHosts to a single MySQL database and - yet still be able to extract them for separate analysis. - Example: www.grubbybaby.com - -time_stamp: contains the time that the request was logged. Please see - "Notes" below to get a better understanding of this. - Example: 1014249231 - -status: corresponds to the Apache %t directive. Contains the HTTP status - of the request. - Example: 404 - -bytes_sent: corresponds to the Apache %b directive. Contains the number - of bytes sent to service the request. - Example: 23123 - -referer: corresponds to the Apache "%{Referer}i" directive. Contains the - referring HTML page's URL, if applicable. - Example: http://www.foobar.com/links.html - -agent: corresponds to the Apache "%{User-Agent}" directive. Contains the - broswer type (user agent) of the software that made the request. - Example: Mozilla/3.0 (Slurp/si; slurp@inktomi.com; http://www.inktomi.com/slurp.html) - -request_method: corresponds to the Apache %m directive. Contains the type - of request sent: GET, PUT, etc. - Example: GET - -request_protocol: corresponds to the Apache %H directive. Contains the HTTP - protocol that was used. - Example: HTTP/1.1 +begin by accepting this default, then later customize the log +configuration based on your needs. + +The online documentation of the run-time directives includes a full +explanation of what you can log, including examples. Notes ----- +* You will customarily set most of your run-time configuration directives + on a per-virtualserver basis, with only MySQLMassVirtualHosting, + MySQLLoginInfo and MySQLDatabase 'outside' in the main server config. + Any directives other than those in the main config do NOT get inherited + by the virutal servers. + * The 'time_stamp' field is stored in an UNSIGNED INTEGER column, in the standard unix "seconds since 1/1/1970 12:00:00" format. This is superior to storing the access time as a string due to size @@ -161,20 +88,15 @@ Notes Log Format compliant. You can then feed this to your favorite web log analysis tool. - * The table's string values can be CHAR or VARCHAR, at a length of your choice. VARCHAR is superior because it truncates long strings; CHAR types are fixed-length and will be padded with spaces. Just like the time_stamp described above, that kind of space waste will add up over thousands of records. - -* Most fields should probably be set to NOT NULL. The only ones that - shouldn't are extra fields that you don't intend the logging module - to update. (You can have other fields in the logging tables if you'd - like, but if they're set to NOT NULL then the logging module won't be - able to insert rows to these tables.) - +* Be careful not to go overboard setting fields to NOT NULL. If a field is + marked NOT NULL then it must contain data in the INSERT or the INSERT + will fail. * Apache normally logs numeric fields with a '-' character to mean "not applicable," e.g. bytes_sent on a request with a 304 response code. @@ -183,15 +105,6 @@ Notes makes perfect sense anyway. -* If your database goes offline and Apache cannot log to it, mod_log_mysql - intelligently preserves any queries to a local text file. (By - default the file is /tmp/mysql-preserve.) This will allow you to not - miss those entries; when you bring your database back online it is a - simple matter to import the contents of this preserve file. To do - this simply copy the file to your MySQL server and run an import - as follows: - # mysql -uadminuser -p mydbname < mysql-preserve - Author / Maintainer ------------------- @@ -202,7 +115,7 @@ text modules, so all that credit goes to the Apache Server group. The MySQL routines and directives were added by Zeev Suraski . -Changes from 1.06 on and the new documentation were added by +All changes from 1.06+ and the new documentation were added by Chris Powell . It seems that the module had fallen into the "unmaintained" category -- it hadn't been updated since 1998 -- so Chris adopted it as the new maintainer. diff --git a/mod_log_sql.c b/mod_log_sql.c index 16deada..02b0dd7 100644 --- a/mod_log_sql.c +++ b/mod_log_sql.c @@ -1,52 +1,72 @@ -/* $Id: mod_log_sql.c,v 1.8 2002/04/08 07:06:20 helios Exp $ */ +/* $Id: mod_log_sql.c,v 1.9 2002/04/21 23:01:53 helios Exp $ */ +/* --------* + * DEFINES * + * --------*/ -/* DEFINES */ +/* The enduser probably won't modify these */ #define MYSQL_ERROR(mysql) ((mysql)?(mysql_error(mysql)):"MySQL server has gone away") #define ERRLEVEL APLOG_ERR|APLOG_NOERRNO #define WARNINGLEVEL APLOG_WARNING|APLOG_NOERRNO #define NOTICELEVEL APLOG_NOTICE|APLOG_NOERRNO -#define DEBUGLEVEL APLOG_INFO|APLOG_NOERRNO -#define PRESERVEFILE "/tmp/mysql-preserve" -/* (MYSQLSOCKET, DEBUG and WANT_SSL_LOGGING are defined in the Makefile DEFS line.) */ +#define DEBUGLEVEL APLOG_DEBUG|APLOG_NOERRNO +/* The enduser may wish to modify these */ +#define WANT_SSL_LOGGING +#undef DEBUG -/* INCLUDES */ +/* ---------* + * INCLUDES * + * ---------*/ #include #include #include - #include "httpd.h" #include "http_config.h" #include "http_log.h" #include "http_core.h" -/* M_M_N is defined in /usr/local/Apache/include/ap_mmn.h, 19990320 as of this writing. */ -#if MODULE_MAGIC_NUMBER >= 19980324 /* 1.3b6 or later */ +#if MODULE_MAGIC_NUMBER >= 19980324 /* M_M_N is defined in /usr/local/Apache/include/ap_mmn.h, 19990320 as of this writing. */ #include "ap_compat.h" #endif -#ifdef WANT_SSL_LOGGING /* Defined in Makefile */ +#ifdef WANT_SSL_LOGGING #include "mod_ssl.h" #endif +/* -------------* + * DECLARATIONS * + * -------------*/ -/* DECLARATIONS */ +/* Declare ourselves so the configuration routines can find and know us. */ module mysql_log_module; +/* The contents of these are known 'Apache wide' and are not variable + * on a per-virtual-server basis. Every virtual server 'knows' the + * same versions of these variables. + */ MYSQL sql_server, *mysql_log = NULL; +int massvirtual = 0; char *db_name = NULL; char *db_host = NULL; char *db_user = NULL; char *db_pwd = NULL; -char *cookie_name = NULL; +char *socket_file = "/var/lib/mysql/mysql.sock"; typedef const char *(*item_key_func) (request_rec *, char *); +/* But the contents of this structure will vary by virtual server. + * This permits each virtual server to vary its configuration slightly + * for per-server customization. + * + * Each child process has its own segregated copy of this structure. + */ typedef struct { + int create_tables; + int table_made; char *referer_table_name; char *agent_table_name; char *transfer_table_name; @@ -54,11 +74,14 @@ typedef struct { array_header *transfer_ignore_list; array_header *remhost_ignore_list; char *transfer_log_format; + char *preserve_file; + char *cookie_name; } log_mysql_state; - -/* FUNCTIONS */ +/* -----------------* + * HELPER FUNCTIONS * + * -----------------*/ static char *format_integer(pool *p, int i) { char dummy[40]; @@ -110,7 +133,7 @@ static const char *extract_ssl_keysize(request_rec *r, char *a) if (ap_ctx_get(r->connection->client->ctx, "ssl") != NULL) { result = ssl_var_lookup(r->pool, r->server, r->connection, r, "SSL_CIPHER_USEKEYSIZE"); #ifdef DEBUG - ap_log_error(APLOG_MARK,DEBUGLEVEL,r->server,"mod_log_mysql: SSL_KEYSIZE: %s", result); + ap_log_error(APLOG_MARK,DEBUGLEVEL,r->server,"SSL_KEYSIZE: %s", result); #endif if (result != NULL && result[0] == '\0') result = NULL; @@ -127,7 +150,7 @@ static const char *extract_ssl_maxkeysize(request_rec *r, char *a) if (ap_ctx_get(r->connection->client->ctx, "ssl") != NULL) { result = ssl_var_lookup(r->pool, r->server, r->connection, r, "SSL_CIPHER_ALGKEYSIZE"); #ifdef DEBUG - ap_log_error(APLOG_MARK,DEBUGLEVEL,r->server,"mod_log_mysql: SSL_ALGKEYSIZE: %s", result); + ap_log_error(APLOG_MARK,DEBUGLEVEL,r->server,"SSL_ALGKEYSIZE: %s", result); #endif if (result != NULL && result[0] == '\0') result = NULL; @@ -144,7 +167,7 @@ static const char *extract_ssl_cipher(request_rec *r, char *a) if (ap_ctx_get(r->connection->client->ctx, "ssl") != NULL) { result = ssl_var_lookup(r->pool, r->server, r->connection, r, "SSL_CIPHER"); #ifdef DEBUG - ap_log_error(APLOG_MARK,DEBUGLEVEL,r->server,"mod_log_mysql: SSL_CIPHER: %s", result); + ap_log_error(APLOG_MARK,DEBUGLEVEL,r->server,"SSL_CIPHER: %s", result); #endif if (result != NULL && result[0] == '\0') result = NULL; @@ -299,15 +322,27 @@ static const char *extract_cookie(request_rec *r, char *a) char *isvalid; char *cookiebuf; + log_mysql_state *cls = get_module_config(r->server->module_config, &mysql_log_module); + + #ifdef DEBUG + ap_log_error(APLOG_MARK,DEBUGLEVEL,r->server,"watching for cookie '%s'", cls->cookie_name); + #endif + + /* Fetch out the cookie header */ cookiestr = (char *)table_get(r->headers_in, "cookie2"); if (cookiestr != NULL) { #ifdef DEBUG - ap_log_error(APLOG_MARK,DEBUGLEVEL,r->server,"mod_log_mysql: Cookie2: [%s]", cookiestr); + ap_log_error(APLOG_MARK,DEBUGLEVEL,r->server,"Cookie2: [%s]", cookiestr); #endif - isvalid = strstr(cookiestr, cookie_name); + /* Does the cookie string contain one with our name? */ + isvalid = strstr(cookiestr, cls->cookie_name); if (isvalid != NULL) { - isvalid += strlen(cookie_name) + 1; + /* Move past the cookie name and equal sign */ + isvalid += strlen(cls->cookie_name) + 1; + /* Duplicate it into the pool */ cookiebuf = ap_pstrdup(r->pool, isvalid); + /* Segregate just this cookie out of the string + * with a terminating nul at the first semicolon */ cookieend = strchr(cookiebuf, ';'); if (cookieend != NULL) *cookieend = '\0'; @@ -318,11 +353,11 @@ static const char *extract_cookie(request_rec *r, char *a) cookiestr = (char *)table_get(r->headers_in, "cookie"); if (cookiestr != NULL) { #ifdef DEBUG - ap_log_error(APLOG_MARK,DEBUGLEVEL,r->server,"mod_log_mysql: Cookie: [%s]", cookiestr); + ap_log_error(APLOG_MARK,DEBUGLEVEL,r->server,"Cookie: [%s]", cookiestr); #endif - isvalid = strstr(cookiestr, cookie_name); + isvalid = strstr(cookiestr, cls->cookie_name); if (isvalid != NULL) { - isvalid += strlen(cookie_name) + 1; + isvalid += strlen(cls->cookie_name) + 1; cookiebuf = ap_pstrdup(r->pool, isvalid); cookieend = strchr(cookiebuf, ';'); if (cookieend != NULL) @@ -334,11 +369,11 @@ static const char *extract_cookie(request_rec *r, char *a) cookiestr = table_get(r->headers_out, "set-cookie"); if (cookiestr != NULL) { #ifdef DEBUG - ap_log_error(APLOG_MARK,DEBUGLEVEL,r->server,"mod_log_mysql: Set-Cookie: [%s]", cookiestr); + ap_log_error(APLOG_MARK,DEBUGLEVEL,r->server,"Set-Cookie: [%s]", cookiestr); #endif - isvalid = strstr(cookiestr, cookie_name); + isvalid = strstr(cookiestr, cls->cookie_name); if (isvalid != NULL) { - isvalid += strlen(cookie_name) + 1; + isvalid += strlen(cls->cookie_name) + 1; cookiebuf = ap_pstrdup(r->pool, isvalid); cookieend = strchr(cookiebuf, ';'); if (cookieend != NULL) @@ -350,23 +385,6 @@ static const char *extract_cookie(request_rec *r, char *a) return "-"; } -/* -static const char *extract_forwarded(request_rec *r, char *a) -{ - return table_get(r->subprocess_env, "HTTP_FORWARDED"); -} - -static const char *extract_via(request_rec *r, char *a) -{ - return table_get(r->subprocess_env, "HTTP_VIA"); -} - -static const char *extract_forwarded_for(request_rec *r, char *a) -{ - return table_get(r->subprocess_env, "HTTP_X_FORWARDED_FOR"); -} -*/ - static const char *extract_request_timestamp(request_rec *r, char *a) { char tstr[32]; @@ -390,11 +408,11 @@ static const char *extract_env_var(request_rec *r, char *a) struct log_mysql_item_list { - char ch; - item_key_func func; - const char *sql_field_name; - int want_orig_default; - int string_contents; + char ch; /* its letter code */ + item_key_func func; /* its extraction function */ + const char *sql_field_name; /* its column in SQL */ + int want_orig_default; /* if it requires the original request prior to internal redirection */ + int string_contents; /* if it returns a string */ } log_mysql_item_keys[] = { { 'A', extract_agent, "agent", 1, 1 }, @@ -420,10 +438,6 @@ struct log_mysql_item_list { { 'u', extract_remote_user, "remote_user", 0, 1 }, { 'U', extract_request_uri, "request_uri", 1, 1 }, { 'v', extract_virtual_host, "virtual_host", 0, 1 }, -/* { 'V', extract_via, "via", 0, 1 }, - { 'w', extract_forwarded, "forwarded", 0, 1 }, - { 'W', extract_forwarded_for, "forwarded_for", 0, 1 }, - */ #ifdef WANT_SSL_LOGGING { 'q', extract_ssl_keysize, "ssl_keysize", 0, 1 }, { 'Q', extract_ssl_maxkeysize, "ssl_maxkeysize", 0, 1 }, @@ -436,66 +450,54 @@ struct log_mysql_item_list { /* Routine to escape the 'dangerous' characters that would otherwise * corrupt the INSERT string: ', \, and " */ -const char *mysql_escape_log(const char *str, pool *p) +const char *escape_query(const char *from_str, pool *p) { - register int i = 0, j = 0; - int need_to_escape = 0; - - if (!str) { + if (!from_str) return NULL; - } - - /* First find out if we need to escape. */ - i = 0; - while (str[i]) { - /* WAS THIS WRONG in 1.05?!? if (str[i] != '\'' || str[i] != '\\' || str[i] != '\"') { */ - if (str[i] == '\'' || str[i] == '\\' || str[i] == '\"') { - need_to_escape = 1; - break; - } - i++; - } - - if (need_to_escape) { - char *tmp_str; - int length = strlen(str); - + else { + char *to_str; + unsigned long length = strlen(from_str); + unsigned long retval; + /* Pre-allocate a new string that could hold twice the original, which would only * happen if the whole original string was 'dangerous' characters. */ - tmp_str = (char *) palloc(p, length * 2 + 1); - if (!tmp_str) { - return str; + to_str = (char *) ap_palloc(p, length * 2 + 1); + if (!to_str) { + return from_str; } - /* Walk through character-by-character, escaping any dangerous characters found. */ - for (i = 0, j = 0; i < length; i++, j++) { - switch (str[i]) { - case '\'': - case '\"': - case '\\': - tmp_str[j] = '\\'; - j++; - default: - tmp_str[j] = str[i]; - } + if (!mysql_log) { + /* Well, I would have liked to use the current database charset. mysql is + * unavailable, however, so I fall back to the slightly less respectful + * mysql_escape_string() function that uses the default charset. + */ + retval = mysql_escape_string(to_str, from_str, length); + } else { + /* MySQL is available, so I'll go ahead and respect the current charset when + * I perform the escape. + */ + retval = mysql_real_escape_string(mysql_log, to_str, from_str, length); } - tmp_str[j] = '\0'; - return tmp_str; - } else { - return str; + + if (retval) + return to_str; + else + return from_str; } } int open_logdb_link() { + /* Returns 2 if already connected, 1 if successful, 0 if unsuccessful */ + if (mysql_log != NULL) { return 2; } if (db_name) { mysql_init(&sql_server); - mysql_log = mysql_real_connect(&sql_server, db_host, db_user, db_pwd, db_name, 0, MYSQLSOCKET, 0); + mysql_log = mysql_real_connect(&sql_server, db_host, db_user, db_pwd, db_name, 0, socket_file, 0); if (mysql_log != NULL) { return 1; @@ -509,18 +511,27 @@ int open_logdb_link() void preserve_entry(request_rec *r, const char *query) { FILE *fp; + log_mysql_state *cls = get_module_config(r->server->module_config, &mysql_log_module); - fp = fopen(PRESERVEFILE, "a"); + fp = pfopen(r->pool, cls->preserve_file, "a"); if (fp == NULL) - ap_log_error(APLOG_MARK,ERRLEVEL,r->server,"MySQL: attempted append of local offline file but failed."); + ap_log_error(APLOG_MARK,ERRLEVEL,r->server,"attempted append of local offline file but failed."); else fprintf(fp,"%s;\n", query); - fclose(fp); + pfclose(r->pool, fp); } +/*-----------------------------------------------------*/ +/* safe_mysql_query: perform a database insert with */ +/* a degree of safety and error checking. */ +/* */ +/* Parms: request record, SQL insert statement */ +/* Returns: 0 (OK) on success */ +/* mysql return code on error */ +/*-----------------------------------------------------*/ int safe_mysql_query(request_rec *r, const char *query) { - int retval = 1; + int retval; struct timespec delay, remainder; int ret; char *str; @@ -539,19 +550,22 @@ int safe_mysql_query(request_rec *r, const char *query) * at any time, hence the check. */ if ( retval != 0 ) { + log_mysql_state *cls = get_module_config(r->server->module_config, &mysql_log_module); + /* Something went wrong, so start by trying to restart the db link. */ - ap_log_error(APLOG_MARK,ERRLEVEL,r->server,"MySQL: attempting reconnect because API said: %s", MYSQL_ERROR(mysql_log)); + ap_log_error(APLOG_MARK,ERRLEVEL,r->server,"attempting reconnect because API said: %s", mysql_error(mysql_log)); mysql_log = NULL; - open_logdb_link(); + open_logdb_link(); if (mysql_log == NULL) { /* still unable to link */ signal(SIGPIPE, handler); - ap_log_error(APLOG_MARK,ERRLEVEL,r->server,"MySQL: httpd child reconnect failed, unable to reach database. SQL logging stopped until an httpd child regains a db connection."); - ap_log_error(APLOG_MARK,ERRLEVEL,r->server,"MySQL: log entries are being preserved in %s",PRESERVEFILE); + ap_log_error(APLOG_MARK,ERRLEVEL,r->server,"httpd child reconnect failed, unable to reach database. SQL logging stopped until an httpd child regains a db connection."); + ap_log_error(APLOG_MARK,ERRLEVEL,r->server,"log entries are being preserved in %s", cls->preserve_file); + preserve_entry(r, query); return retval; } else { - ap_log_error(APLOG_MARK,ERRLEVEL,r->server,"MySQL: reconnect successful."); + ap_log_error(APLOG_MARK,ERRLEVEL,r->server,"reconnect successful."); } /* Attempt a single re-try... First sleep for a tiny amount of time. */ @@ -559,7 +573,7 @@ int safe_mysql_query(request_rec *r, const char *query) delay.tv_nsec = 500000000; /* max is 999999999 (nine nines) */ ret = nanosleep(&delay, &remainder); if (ret && errno != EINTR) - perror("nanosleep"); + ap_log_error(APLOG_MARK,ERRLEVEL,r->server,"nanosleep unsuccessful."); /* Now make our second attempt */ retval = mysql_query(mysql_log,query); @@ -567,13 +581,13 @@ int safe_mysql_query(request_rec *r, const char *query) /* If this one also failed, log that and append to our local offline file */ if ( retval != 0 ) { - str = pstrcat(r->pool, "MySQL delayed insert attempt failed, API said: ", MYSQL_ERROR(mysql_log), NULL); + str = ap_pstrcat(r->pool, "delayed insert attempt failed, API said: ", MYSQL_ERROR(mysql_log), NULL); ap_log_error(APLOG_MARK,ERRLEVEL,r->server,str); preserve_entry(r, query); - ap_log_error(APLOG_MARK,ERRLEVEL,r->server,"MySQL: entry preserved in %s",PRESERVEFILE); + ap_log_error(APLOG_MARK,ERRLEVEL,r->server,"entry preserved in %s", cls->preserve_file); } else { - ap_log_error(APLOG_MARK,ERRLEVEL,r->server,"MySQL: insert successful after a delayed retry."); + ap_log_error(APLOG_MARK,ERRLEVEL,r->server,"insert successful after a delayed retry."); } } @@ -584,52 +598,44 @@ int safe_mysql_query(request_rec *r, const char *query) } +/* ------------------------------------------------* + * Command handlers that are called according * + * to the directives found at Apache runtime. * + * ------------------------------------------------*/ -const char *set_referer_log_mysql_table(cmd_parms *parms, void *dummy, char *arg) +const char *set_massvirtual(cmd_parms *parms, void *dummy, int flag) { - log_mysql_state *cls = get_module_config(parms->server->module_config, &mysql_log_module); - - cls->referer_table_name = arg; + massvirtual = ( flag ? 1 : 0); return NULL; } - -const char *set_agent_log_mysql_table(cmd_parms *parms, void *dummy, char *arg) +const char *set_log_mysql_create(cmd_parms *parms, void *dummy, int flag) { log_mysql_state *cls = get_module_config(parms->server->module_config, &mysql_log_module); - - cls->agent_table_name = arg; + + cls->create_tables = ( flag ? 1 : 0); return NULL; } - -const char *set_transfer_log_mysql_table(cmd_parms *parms, void *dummy, char *arg) +const char *set_log_mysql_db(cmd_parms *parms, void *dummy, char *arg) { - log_mysql_state *cls = get_module_config(parms->server->module_config, &mysql_log_module); - - cls->transfer_table_name = arg; + db_name = arg; return NULL; } - -const char *set_transfer_log_format(cmd_parms *parms, void *dummy, char *arg) +const char *set_log_mysql_cookie(cmd_parms *parms, void *dummy, char *arg) { log_mysql_state *cls = get_module_config(parms->server->module_config, &mysql_log_module); - cls->transfer_log_format = arg; + cls->cookie_name = arg; return NULL; } - -const char *set_log_mysql_db(cmd_parms *parms, void *dummy, char *arg) +const char *set_log_mysql_preserve_file(cmd_parms *parms, void *dummy, char *arg) { - db_name = arg; - return NULL; -} + log_mysql_state *cls = get_module_config(parms->server->module_config, &mysql_log_module); -const char *set_log_mysql_cookie(cmd_parms *parms, void *dummy, char *arg) -{ - cookie_name = arg; + cls->preserve_file = arg; return NULL; } @@ -647,12 +653,59 @@ const char *set_log_mysql_info(cmd_parms *parms, void *dummy, char *host, char * return NULL; } +const char *set_transfer_log_mysql_table(cmd_parms *parms, void *dummy, char *arg) +{ + log_mysql_state *cls = get_module_config(parms->server->module_config, &mysql_log_module); + + if (massvirtual == 1) { + char *base = "access_"; + char *tablename; + int i; + + /* Find memory long enough to hold the table name + \0. */ + /* old way: */ + /* tablename = (char*)ap_palloc(parms->pool, (strlen(base) + strlen(parms->server->server_hostname) + 1) * sizeof(char));*/ + /* strcpy(tablename, base);*/ + /* strcat(tablename, parms->server->server_hostname);*/ + + tablename = ap_pstrcat(parms->pool, base, parms->server->server_hostname, NULL); + + /* Transform any dots to underscores */ + for (i = 0; i < strlen(tablename); i++) { + if (tablename[i] == '.') + tablename[i] = '_'; + } + + /* Tell this virtual server its transfer table name, and + * turn on create_tables, which is implied by massvirtual. + */ + cls->transfer_table_name = tablename; + cls->create_tables = 1; + } else { + cls->transfer_table_name = arg; + } + return NULL; +} + +const char *set_transfer_log_format(cmd_parms *parms, void *dummy, char *arg) +{ + log_mysql_state *cls = get_module_config(parms->server->module_config, &mysql_log_module); + + cls->transfer_log_format = arg; + return NULL; +} + +const char *set_mysql_socket_file(cmd_parms *parms, void *dummy, char *arg) +{ + socket_file = arg; + + return NULL; +} const char *add_referer_mysql_ignore(cmd_parms *parms, void *dummy, char *arg) { char **addme; - log_mysql_state *cls = get_module_config(parms->server->module_config, - &mysql_log_module); + log_mysql_state *cls = get_module_config(parms->server->module_config, &mysql_log_module); addme = push_array(cls->referer_ignore_list); *addme = pstrdup(cls->referer_ignore_list->pool, arg); @@ -662,8 +715,7 @@ const char *add_referer_mysql_ignore(cmd_parms *parms, void *dummy, char *arg) const char *add_transfer_mysql_ignore(cmd_parms *parms, void *dummy, char *arg) { char **addme; - log_mysql_state *cls = get_module_config(parms->server->module_config, - &mysql_log_module); + log_mysql_state *cls = get_module_config(parms->server->module_config, &mysql_log_module); addme = push_array(cls->transfer_ignore_list); *addme = pstrdup(cls->transfer_ignore_list->pool, arg); @@ -673,8 +725,7 @@ const char *add_transfer_mysql_ignore(cmd_parms *parms, void *dummy, char *arg) const char *add_remhost_mysql_ignore(cmd_parms *parms, void *dummy, char *arg) { char **addme; - log_mysql_state *cls = get_module_config(parms->server->module_config, - &mysql_log_module); + log_mysql_state *cls = get_module_config(parms->server->module_config, &mysql_log_module); addme = push_array(cls->remhost_ignore_list); *addme = pstrdup(cls->remhost_ignore_list->pool, arg); @@ -682,24 +733,91 @@ const char *add_remhost_mysql_ignore(cmd_parms *parms, void *dummy, char *arg) } -/* - * Apache-specific hooks into the module code - * that are defined in the array 'mysql_lgog_module' (at EOF) + + +/*------------------------------------------------------------* + * Apache-specific hooks into the module code * + * that are defined in the array 'mysql_lgog_module' (at EOF) * + *------------------------------------------------------------*/ + + +/* + * This function is called during server initialisation when an heavy-weight + * process (such as a child) is being initialised. As with the + * module-initialisation function, any information that needs to be recorded + * must be in static cells, since there's no configuration record. + * + * There is no return value. */ +static void log_mysql_child_init(server_rec *s, pool *p) +{ + int retval; + + retval = open_logdb_link(); + #ifdef DEBUG + if (retval > 0) { + ap_log_error(APLOG_MARK,DEBUGLEVEL,s,"open_logdb_link successful"); + } + #endif +} +/* + * This function is called when an heavy-weight process (such as a child) is + * being run down or destroyed. As with the child-initialisation function, + * any information that needs to be recorded must be in static cells, since + * there's no configuration record. + * + * There is no return value. + */ +static void log_mysql_child_exit(server_rec *s, pool *p) +{ + mysql_close(mysql_log); +} + + +/* +void *log_mysql_initializer(server_rec *main_server, pool *p) +{ + server_rec *s; + + log_mysql_state main_conf = ap_get_module_config(main_server->module_config, &mysql_log_module); -/* Set up space for the various major configuration options */ + for (server_rec *s = main_server; s; s = s->next) { + conf = ap_get_module_config(s->module_config, &mysql_log_module); + if (conf->transfer_log_format == NULL && s != main_server) { + *conf = *main_conf; + } + +} + */ + +/* + * This function gets called to create a per-server configuration + * record. It will always be called for the main server and + * for each virtual server that is established. Each server maintains + * its own state that is separate from the others' states. + * + * The return value is a pointer to the created module-specific + * structure. + */ void *log_mysql_make_state(pool *p, server_rec *s) { - log_mysql_state *cls = (log_mysql_state *) palloc(p, sizeof(log_mysql_state)); + + log_mysql_state *cls = (log_mysql_state *) ap_palloc(p, sizeof(log_mysql_state)); + - cls->referer_table_name = cls->agent_table_name = cls->transfer_table_name = ""; + cls->transfer_table_name = NULL; + cls->transfer_log_format = NULL; cls->referer_ignore_list = make_array(p, 1, sizeof(char *)); cls->transfer_ignore_list = make_array(p, 1, sizeof(char *)); cls->remhost_ignore_list = make_array(p, 1, sizeof(char *)); - - cls->transfer_log_format = ""; + + cls->table_made = 0; + cls->create_tables = 0; + + cls->preserve_file = "/tmp/mysql-preserve"; + return (void *) cls; } @@ -708,12 +826,6 @@ void *log_mysql_make_state(pool *p, server_rec *s) * Structure: command, function called, NULL, where available, how many arguments, verbose description */ command_rec log_mysql_cmds[] = { - {"MySQLRefererLogTable", set_referer_log_mysql_table, NULL, RSRC_CONF, TAKE1, - "The MySQL table that holds the referer log"} - , - {"MySQLAgentLogTable", set_agent_log_mysql_table, NULL, RSRC_CONF, TAKE1, - "The MySQL table that holds the agent log"} - , {"MySQLTransferLogTable", set_transfer_log_mysql_table, NULL, RSRC_CONF, TAKE1, "The MySQL table that holds the transfer log"} , @@ -738,6 +850,18 @@ command_rec log_mysql_cmds[] = { {"MySQLLoginInfo", set_log_mysql_info, NULL, RSRC_CONF, TAKE3, "The MySQL host, user-id and password for logging"} , + {"MySQLCreateTables", set_log_mysql_create, NULL, RSRC_CONF, FLAG, + "Turn on module's capability to create its SQL tables on the fly"} + , + {"MySQLMassVirtualHosting", set_massvirtual, NULL, RSRC_CONF, FLAG, + "Activates option(s) useful for ISPs performing mass virutal hosting"} + , + {"MySQLPreserveFile", set_log_mysql_preserve_file, NULL, RSRC_CONF, TAKE1, + "Name of the file to use for data preservation during database downtime"} + , + {"MySQLSocketFile", set_mysql_socket_file, NULL, RSRC_CONF, TAKE1, + "Name of the file to employ for socket connections to MySQL"} + , {NULL} }; @@ -749,102 +873,25 @@ command_rec log_mysql_cmds[] = { int log_mysql_transaction(request_rec *orig) { char **ptrptr, **ptrptr2; - log_mysql_state *cls = get_module_config(orig->server->module_config, - &mysql_log_module); - char *str; - const char *referer; + log_mysql_state *cls = get_module_config(orig->server->module_config, &mysql_log_module); + const char *str; request_rec *r; - int retvalue = DECLINED; - int referer_needed, agent_needed, transfer_needed; - /* Are there configuration directives for these SQL logs? For each found * config directive that is found, mark that type as 'needed'. */ - referer_needed = ((cls->referer_table_name[0] != '\0') ? 1 : 0); - agent_needed = ((cls->agent_table_name[0] != '\0') ? 1 : 0); - transfer_needed = ((cls->transfer_table_name[0] != '\0') ? 1 : 0); - - if (!referer_needed && !agent_needed && !transfer_needed) { - return OK; - } - - - for (r = orig; r->next; r = r->next) { - continue; - } - - /* Log the 'referer' to its own log if configured to do so. */ - if (referer_needed) { - retvalue = OK; - referer = table_get(orig->headers_in, "Referer"); - if (referer != NULL) { - - /* The following is an upsetting mess of pointers, I'm sorry - * Anyone with the motiviation and/or the time should feel free - * to make this cleaner... */ - ptrptr2 = (char **) (cls->referer_ignore_list->elts + (cls->referer_ignore_list->nelts * cls->referer_ignore_list->elt_size)); - - /* Go through each element of the ignore list and compare it to the - * referer_host. If we get a match, return without logging */ - for (ptrptr = (char **) cls->referer_ignore_list->elts; ptrptr < ptrptr2; ptrptr = (char **) ((char *) ptrptr + cls->referer_ignore_list->elt_size)) { - if (strstr(referer, *ptrptr)) { - return OK; - } - } - str = pstrcat(orig->pool, "insert into ", cls->referer_table_name, " (referer,url,time_stamp) values ('", mysql_escape_log(referer, orig->pool), "','", mysql_escape_log(r->uri, orig->pool), "',unix_timestamp(now()) )", NULL); - - if (mysql_log == NULL) { /* child's mysql link not up, re-establish it */ - open_logdb_link(); - if (mysql_log == NULL) { - preserve_entry(r, str); - return OK; - } else { - ap_log_error(APLOG_MARK,NOTICELEVEL,orig->server,"MySQL: httpd child established database connection"); - safe_mysql_query(orig, str); - } - } else { - safe_mysql_query(orig, str); - } - - } - } - - /* Log the 'user agent' to its own log if configured to do so. */ - if (agent_needed) { - const char *agent, *str; - - retvalue = OK; - agent = table_get(orig->headers_in, "User-Agent"); - - if (agent != NULL) { - str = pstrcat(orig->pool, "insert into ", cls->agent_table_name, "(agent,time_stamp) values ('", mysql_escape_log(agent, orig->pool), "',unix_timestamp(now()) )", NULL); - - if (mysql_log == NULL) { /* child's mysql link not up, re-establish it */ - open_logdb_link(); - if (mysql_log == NULL) { - preserve_entry(r, str); - return OK; - } else { - ap_log_error(APLOG_MARK,NOTICELEVEL,orig->server,"MySQL: httpd child established database connection"); - safe_mysql_query(orig, str); - } - } else { - safe_mysql_query(orig, str); - } - } - } - - /* Log the transfer to its own log if configured to do so. */ - if (transfer_needed) { + if ( ((cls->transfer_table_name == NULL) ? 1 : 0) ) { + return DECLINED; + } else { const char *thehost; - char *fields = "", *values = ""; const char *formatted_item; int i, j, length; + char *createstring = NULL; - retvalue = OK; - + for (r = orig; r->next; r = r->next) { + continue; + } /* The following is a stolen upsetting mess of pointers, I'm sorry * Anyone with the motiviation and/or the time should feel free @@ -856,7 +903,7 @@ int log_mysql_transaction(request_rec *orig) if (r->uri) { for (ptrptr = (char **) cls->transfer_ignore_list->elts; ptrptr < ptrptr2; ptrptr = (char **) ((char *) ptrptr + cls->transfer_ignore_list->elt_size)) { if (strstr(r->uri, *ptrptr)) { - return retvalue; + return OK; } } } @@ -868,13 +915,13 @@ int log_mysql_transaction(request_rec *orig) if (thehost) { for (ptrptr = (char **) cls->remhost_ignore_list->elts; ptrptr < ptrptr2; ptrptr = (char **) ((char *) ptrptr + cls->remhost_ignore_list->elt_size)) { if (strstr(thehost, *ptrptr)) { - return retvalue; + return OK; } } } /* If not specified by the user, use the default format */ - if (cls->transfer_log_format[0] == '\0') { + if (cls->transfer_log_format == NULL) { cls->transfer_log_format = "AbHhmRSsTUuv"; } length = strlen(cls->transfer_log_format); @@ -883,8 +930,10 @@ int log_mysql_transaction(request_rec *orig) * what the user has configured. */ for (i = 0; i < length; i++) { j = 0; + while (log_mysql_item_keys[j].ch) { - if (log_mysql_item_keys[j].ch == cls->transfer_log_format[i]) { + + if (log_mysql_item_keys[j].ch == cls->transfer_log_format[i]) { /* Yes, this key is one of the configured keys. * Call the key's function and put the returned value into 'formatted_item' */ formatted_item = log_mysql_item_keys[j].func(log_mysql_item_keys[j].want_orig_default ? orig : r, ""); @@ -894,91 +943,134 @@ int log_mysql_transaction(request_rec *orig) formatted_item = ""; } else if (formatted_item[0] == '-' && formatted_item[1] == '\0' && !log_mysql_item_keys[j].string_contents) { /* If apache tried to log a '-' character for a numeric field, convert that to a zero - * because the database expects an integer. */ + * because the database expects a numeral and will reject the '-' character. */ formatted_item = "0"; } - /* Append the fieldname and value-to-insert to teh appropriate strings, quoting stringvals with ' as appropriate */ - fields = pstrcat(orig->pool, fields, (i > 0 ? "," : ""), log_mysql_item_keys[j].sql_field_name, NULL); - values = pstrcat(orig->pool, values, (i > 0 ? "," : ""), (log_mysql_item_keys[j].string_contents ? "'" : ""), mysql_escape_log(formatted_item, orig->pool), (log_mysql_item_keys[j].string_contents ? "'" : ""), NULL); + /* Append the fieldname and value-to-insert to the appropriate strings, quoting stringvals with ' as appropriate */ + fields = pstrcat(r->pool, fields, (i > 0 ? "," : ""), + log_mysql_item_keys[j].sql_field_name, NULL); + + values = pstrcat(r->pool, values, (i > 0 ? "," : ""), + (log_mysql_item_keys[j].string_contents ? "'" : ""), + escape_query(formatted_item, r->pool), + (log_mysql_item_keys[j].string_contents ? "'" : ""), NULL); break; } j++; + } } - /* Set up the actual INSERT statement and execute it. */ - str = pstrcat(orig->pool, "insert into ", cls->transfer_table_name, " (", fields, ") values (", values, ")", NULL); + + /* Is this virtual server's table flagged as made? We flag it as such in order + * to avoid extra processing with each request. If it's not flagged as made, + * set up the CREATE string. + */ + if ((cls->table_made != 1) && (cls->create_tables != 0)) { + char *createprefix = "create table if not exists "; + char *createsuffix = + " (agent varchar(255),\ + bytes_sent int unsigned,\ + child_pid smallint unsigned,\ + cookie varchar(255),\ + request_file varchar(255),\ + referer varchar(255),\ + remote_host varchar(50),\ + remote_logname varchar(50),\ + remote_user varchar(50),\ + request_duration smallint unsigned,\ + request_line varchar(255),\ + request_method varchar(6),\ + request_protocol varchar(10),\ + request_time char(28),\ + request_uri varchar(50),\ + server_port smallint unsigned,\ + ssl_cipher varchar(25),\ + ssl_keysize smallint unsigned,\ + ssl_maxkeysize smallint unsigned,\ + status smallint unsigned,\ + time_stamp int unsigned,\ + virtual_host varchar(50))"; + + /* Find memory long enough to hold the whole CREATE string + \0 */ + /* old way: + * createstring = (char*)ap_palloc(orig->pool,(strlen(createprefix) + strlen(cls->transfer_table_name) + strlen(createsuffix) + 1) * sizeof(char)); + * strcpy (createstring, createprefix); + * strcat (createstring, cls->transfer_table_name); + * strcat (createstring, createsuffix); */ + + createstring = ap_pstrcat(orig->pool, createprefix, cls->transfer_table_name, createsuffix, NULL); + + #ifdef DEBUG + ap_log_error(APLOG_MARK,DEBUGLEVEL,orig->server,"create string: %s", createstring); + #endif + } + + /* Set up the actual INSERT statement and escape it. */ + str = ap_pstrcat(r->pool, "insert into ", cls->transfer_table_name, " (", fields, ") values (", values, ")", NULL); + #ifdef DEBUG + ap_log_error(APLOG_MARK,DEBUGLEVEL,r->server,"insert string: %s", str); + #endif + + /* How's our mysql link integrity? */ if (mysql_log == NULL) { - /* Try to regain the link */ + /* Make a try to establish the link */ open_logdb_link(); if (mysql_log == NULL) { /* Unable to re-establish a DB link, so assume that it's really - * gone and send the entry to the preserve file instead. */ - preserve_entry(r, str); + * gone and send the entry to the preserve file instead. + * Note that we don't keep logging the db error over and over. */ + preserve_entry(orig, str); return OK; } else { /* Whew, we got the DB link back */ - ap_log_error(APLOG_MARK,NOTICELEVEL,orig->server,"MySQL: httpd child established database connection"); - safe_mysql_query(orig, str); + ap_log_error(APLOG_MARK,NOTICELEVEL,orig->server,"httpd child established database connection"); } - } else { - /* Everything was fine */ - safe_mysql_query(orig, str); } + + if ((cls->table_made != 1) && (cls->create_tables != 0)) { + mysql_query(mysql_log,createstring); + cls->table_made = 1; + } + + /* Make the insert */ + safe_mysql_query(orig, str); + return OK; } - return retvalue; } -/* Called on the exit of an httpd child process */ -static void log_mysql_child_exit(server_rec *s, pool *p) -{ - mysql_close(mysql_log); -} - -/* Called on the init of an httpd child process */ -static void log_mysql_child_init(server_rec *s, pool *p) -{ - int retval; - - retval = open_logdb_link(); - #ifdef DEBUG - if (retval > 0) { - ap_log_error(APLOG_MARK,DEBUGLEVEL,s,"MySQL: open_logdb_link successful"); - } - #endif -} /* The configuration array that sets up the hooks into the module. */ module mysql_log_module = { STANDARD_MODULE_STUFF, - NULL, /* initializer */ - NULL, /* create per-dir config */ - NULL, /* merge per-dir config */ - log_mysql_make_state, /* server config */ - NULL, /* merge server config */ - log_mysql_cmds, /* command table */ - NULL, /* handlers */ - NULL, /* filename translation */ - NULL, /* check_user_id */ - NULL, /* check auth */ - NULL, /* check access */ - NULL, /* type_checker */ - NULL, /* fixups */ - log_mysql_transaction, /* logger */ - NULL, /* header parser */ + NULL, /* module initializer */ + NULL, /* create per-dir config */ + NULL, /* merge per-dir config */ + log_mysql_make_state, /* create server config */ + NULL, /* merge server config */ + log_mysql_cmds, /* config directive table */ + NULL, /* [9] content handlers */ + NULL, /* [2] URI-to-filename translation */ + NULL, /* [5] check/validate user_id */ + NULL, /* [6] check authorization */ + NULL, /* [4] check access by host */ + NULL, /* [7] MIME type checker/setter */ + NULL, /* [8] fixups */ + log_mysql_transaction, /* [10] logger */ + NULL /* [3] header parser */ #if MODULE_MAGIC_NUMBER >= 19970728 /* 1.3-dev or later support these additionals... */ - log_mysql_child_init, /* child_init */ - log_mysql_child_exit, /* process exit/cleanup */ - NULL /* [#0] post read-request */ + ,log_mysql_child_init, /* child process initializer */ + log_mysql_child_exit, /* process exit/cleanup */ + NULL /* [1] post read-request */ #endif }; -- cgit