· 8 years ago · Jan 15, 2018, 09:40 PM
1/*******************************************************************************
2 * Copyright (c) 2012 Hoang-Vu Dang <danghvu@gmail.com>
3 * This file is part of mod_dumpost
4 *
5 * mod_dumpost is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * mod_dumpost is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with mod_dumpost. If not, see <http://www.gnu.org/licenses/>.
17 ******************************************************************************/
18#include <sqlite3.h>
19
20#include "httpd.h"
21#include "http_connection.h"
22#include "http_config.h"
23#include "http_core.h"
24#include "http_log.h"
25#include "http_request.h"
26
27#include "apr_strings.h"
28#include "mod_dumpost.h"
29
30#define DEBUG(request, format, ...) ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, request, format, __VA_ARGS__);
31
32module AP_MODULE_DECLARE_DATA dumpost_module;
33
34static void dumpit(request_rec *r, apr_bucket *b, char *buf, apr_size_t *current_size) {
35 dumpost_cfg_t *cfg =
36 (dumpost_cfg_t *) ap_get_module_config(r->per_dir_config, &dumpost_module);
37
38 if (*current_size < cfg->max_size && !(APR_BUCKET_IS_METADATA(b))) {
39 const char * ibuf;
40 apr_size_t nbytes;
41 if (apr_bucket_read(b, &ibuf, &nbytes, APR_BLOCK_READ) == APR_SUCCESS) {
42 if (nbytes) {
43 DEBUG(r, "%ld bytes read from bucket for request %s", nbytes, r->the_request);
44 nbytes = min(nbytes, cfg->max_size - *current_size);
45 //strncpy(buf, ibuf, nbytes);
46 for (int kk = 0; kk < nbytes; kk++) buf[kk]=ibuf[kk];
47 *current_size += nbytes;
48 }
49 } else {
50 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
51 "mod_dumpost: error reading data");
52 }
53 }
54 else {
55 if (APR_BUCKET_IS_EOS(b)) {
56 DEBUG(r, "EOS bucket detected for request %s", r->the_request);
57 }
58 }
59}
60
61void hexArrayToStr(unsigned char* info, unsigned int infoLength, char **buffer, unsigned int start) {
62 const char* pszNibbleToHex = {"0123456789ABCDEF"};
63 int nNibble, i;
64 for (i = 0; i < infoLength; i++) {
65 nNibble = info[i] >> 4;
66 buffer[0][2 * i + start] = pszNibbleToHex[nNibble];
67 nNibble = info[i] & 0x0F;
68 buffer[0][2 * i + 1 + start] = pszNibbleToHex[nNibble];
69 }
70}
71
72/* table iteration callback function */
73static int tab_cb(void *data, const char *key, const char *value)
74{
75 //printf("callback[%s]: %s %s\n", label, key, value);
76 strcat(data, key);
77 strcat(data, ": ");
78 strcat(data, value);
79 strcat(data, "\r\n");
80 return TRUE;/* TRUE:continue iteration. FALSE:stop iteration */
81}
82
83apr_status_t logit(ap_filter_t *f) {
84 request_state *state = f->ctx;
85 request_rec *r = f->r;
86
87 if (state == NULL || state->log_size == 0) return -1;
88 state->buffer[state->log_size] = 0;
89
90 if (state->fd == NULL) {
91 // no file to write to, write to error log
92 // data is truncated to MAX_STRING_LEN ~ 8192 in apache
93 ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
94 "\"%s\" %s", r->the_request, state->buffer);
95 } else {
96 // need to manually get the time and ip address -- too lazy to make these cusomizable
97 char *time = apr_palloc(r->pool, 50);
98 apr_ctime(time, r->request_time);
99
100 // condition taken from mod_security
101 #if AP_SERVER_MAJORVERSION_NUMBER > 1 && AP_SERVER_MINORVERSION_NUMBER > 2
102 char *ip = r->connection->client_ip;
103 #else
104 char *ip = r->connection->remote_ip;
105 #endif
106 apr_size_t nbytes_written;
107 char *text = apr_psprintf(r->pool, "[%s] %s \"%s\" ",time, ip, r->the_request);
108
109 //Test buffer to search non ASCII characters
110 int not_bin=1;
111 for (int i = 0; i < state->log_size; i++)
112 {
113 //if (state->buffer[i]==0) {
114 if (state->buffer[i]<0x20 || state->buffer[i]>0x7E) {
115 if (state->buffer[i]=='\n') continue;
116 if (state->buffer[i]=='\r') continue;
117 not_bin=0;
118 break;
119 }
120 }
121
122 int jj=strlen(text);
123
124 sqlite3 *db = NULL;
125 int src = sqlite3_open_v2("/home/pablo/git/mod_dumpost/post.db", &db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL);
126 if (src != SQLITE_OK) {
127 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "mod_dumpost: cant open sqlite database: %s", sqlite3_errmsg(db));
128 } else {
129 sqlite3_stmt *stmt = NULL;
130 //id INTEGER PRIMARY KEY, url TEXT, headers TEXT, body BLOB, fecha DATETIME, ip TEXT, method TEXT
131 src = sqlite3_prepare_v2(db, "INSERT INTO log(id, url, headers, fecha, body, ip, method) VALUES (NULL, ?, ?, ?, ?, ?, ?);", -1, &stmt, NULL);
132 if (src != SQLITE_OK) {
133 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "mod_dumpost: cant prepare sqlite statement: %s", sqlite3_errmsg(db));
134 } else {
135 src = sqlite3_bind_text(stmt, 1, r->unparsed_uri, strlen(r->unparsed_uri), SQLITE_STATIC);
136
137 char *text3 = apr_palloc(r->pool, 2048); sprintf(text3, "");
138
139 apr_table_do(tab_cb, text3, r->headers_in, NULL);
140
141 src = sqlite3_bind_text(stmt, 2, text3, strlen(text3), SQLITE_STATIC);
142 //src = sqlite3_bind_text(stmt, 2, apr_array_pstrcat(r->pool, apr_table_elts(r->headers_in), "\r\n"), strlen(apr_array_pstrcat(r->pool, apr_table_elts(r->headers_in), "\r\n")), SQLITE_STATIC);
143 src = sqlite3_bind_text(stmt, 3, time, strlen(time), SQLITE_STATIC);
144 src = sqlite3_bind_blob(stmt, 4, state->buffer, state->log_size, SQLITE_STATIC);
145 src = sqlite3_bind_text(stmt, 5, ip, strlen(ip), SQLITE_STATIC);
146 src = sqlite3_bind_text(stmt, 6, r->method, strlen(r->method), SQLITE_STATIC);
147 if (src != SQLITE_OK) {
148 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "mod_dumpost: sqlite cant bind blob: %s", sqlite3_errmsg(db));
149 } else {
150 src = sqlite3_step(stmt);
151 if (src != SQLITE_DONE)
152 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "mod_dumpost: sqlite query failed: %s", sqlite3_errmsg(db));
153 }
154 }
155 sqlite3_finalize(stmt);
156 }
157 sqlite3_close(db);
158
159 dumpost_cfg_t *cfg =
160 (dumpost_cfg_t *) ap_get_module_config(f->r->per_dir_config, &dumpost_module);
161 apr_status_t rc;
162 if (not_bin==0 && cfg->log_bin) {
163 char *text2 = apr_palloc(r->pool, (state->log_size*2) + 2 + jj);
164 sprintf(text2, "%s", text);
165 hexArrayToStr((unsigned char*)state->buffer, state->log_size, &text2, jj);
166 jj=jj+(state->log_size*2);
167 text2[jj]='\n';
168 jj++;
169 text2[jj]='\0';
170 rc = apr_file_write_full(state->fd, text2, jj, &nbytes_written);
171 } else {
172 if (not_bin==0) {
173 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "mod_dumpost: binary output is disabled, not dumping this request.");
174 return APR_SUCCESS;
175 }
176 char *text2 = apr_palloc(r->pool, (state->log_size) + 2 + jj);
177 sprintf(text2, "%s%s\n", text, state->buffer);
178 jj=strlen(text2);
179 rc = apr_file_write_full(state->fd, text2, jj, &nbytes_written);
180 }
181 if (rc != APR_SUCCESS) {
182 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "mod_dumpost: error while writing to log");
183 return rc;
184 }
185 apr_file_close(state->fd);
186 }
187 return APR_SUCCESS;
188}
189
190apr_status_t dumpost_input_filter (ap_filter_t *f, apr_bucket_brigade *bb,
191 ap_input_mode_t mode, apr_read_type_e block, apr_off_t readbytes) {
192
193 dumpost_cfg_t *cfg =
194 (dumpost_cfg_t *) ap_get_module_config(f->r->per_dir_config, &dumpost_module);
195
196 apr_bucket *b;
197 apr_status_t ret;
198 //Default status dont filter anything, dump all requests
199 int filter_this = 0;
200 char **filters = (cfg->filters->nelts > 0)?(char **) cfg->filters->elts : NULL;
201 if (filters!=NULL) {
202 //If theres filters present in config, discard all request until check.
203 filter_this = 1;
204 int fi=0;
205 for (;fi<cfg->filters->nelts;fi++) {
206 if (strstr(f->r->the_request, filters[fi]) != NULL) {
207 //This request will be dumped.
208 filter_this = 0;
209 break;
210 }
211 }
212 }
213 //In case of filtering this request, exit filter.
214 if (filter_this==1) {
215 //Continue the filtering and exit filter before allocating memory for this request.
216 if ((ret = ap_get_brigade(f->next, bb, mode, block, readbytes)) != APR_SUCCESS)
217 return ret;
218 return APR_SUCCESS;
219 }
220
221 /* restoring state */
222 request_state *state = f->ctx;
223
224 if (state == NULL) {
225 /* create state if not yet */
226 apr_pool_t *mp;
227 if ((ret = apr_pool_create(&mp, f->r->pool)) != APR_SUCCESS) {
228 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "mod_dumpost: unable to create memory pool");
229 return ret;
230 }
231 f->ctx = state = (request_state *) apr_palloc(mp, sizeof *state);
232 state->mp = mp;
233 state->log_size = 0;
234 state->log_is_full = 0;
235 state->header_printed = 0;
236 state->buffer = apr_palloc(state->mp, cfg->max_size + 1); //1 byte more because string buffer is null terminated
237 state->fd = NULL;
238
239 if (cfg->file != 0) {
240 apr_status_t rc = apr_file_open(&state->fd, cfg->file,
241 APR_FOPEN_CREATE | APR_FOPEN_APPEND | APR_FOPEN_WRITE
242 , APR_OS_DEFAULT, state->mp);
243 if (rc != APR_SUCCESS) {
244 char buferr[50];
245 apr_strerror(rc, buferr, 50);
246 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "mod_dumpost: unable to open the log file: %s %s", cfg->file, buferr);
247 }
248 }
249 //This doesn't work for oldest apr versions but i can obtain same result in a cleaner way using macro APR_BUCKET_IS_EOS() in dumpit function to detect when data stream ends
250 apr_pool_pre_cleanup_register(state->mp, f, (apr_status_t (*)(void *))logit);
251 }
252
253 char *buf = state->buffer;
254 apr_size_t buf_len = state->log_size;
255 char **headers = (cfg->headers->nelts > 0)?(char **) cfg->headers->elts : NULL;
256
257 /* dump header if config */
258 if (!state->log_is_full && headers!=NULL && !state->header_printed) {
259 int i=0;
260 for (;i<cfg->headers->nelts;i++) {
261 const char *s = apr_table_get(f->r->headers_in, headers[i]);
262 if (s == NULL) continue;
263 int len = strlen(s);
264 len = min(len, cfg->max_size - buf_len);
265 strncpy(buf + buf_len, s, len);
266 buf_len += len + 1;
267 buf[buf_len-1] = ' ';
268 if (buf_len == cfg->max_size) break;
269 }
270 state->header_printed = 1;
271 }
272
273 if ((ret = ap_get_brigade(f->next, bb, mode, block, readbytes)) != APR_SUCCESS)
274 return ret;
275
276 /* dump body */
277 DEBUG(f->r, "Start brigade for request: %s", f->r->the_request)
278 for (b = APR_BRIGADE_FIRST(bb); b != APR_BRIGADE_SENTINEL(bb); b = APR_BUCKET_NEXT(b))
279 if (!state->log_is_full && buf_len < cfg->max_size)
280 dumpit(f->r, b, buf + buf_len, &buf_len);
281 DEBUG(f->r, "End brigade for request: %s, buffer: %ld bytes", f->r->the_request, buf_len)
282
283 if (buf_len && !state->log_is_full) {
284 buf_len = min(buf_len, cfg->max_size);
285 state->log_size = buf_len;
286
287 if (state->log_size == cfg->max_size){
288 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, f->r, "mod_dumpost: body limit reach");
289 state->log_is_full = 1;
290 }
291 }
292
293 return APR_SUCCESS;
294}
295
296static void dumpost_insert_filter( request_rec *req) {
297
298 sqlite3 *db = NULL;
299 int src = sqlite3_open_v2("/home/pablo/git/mod_dumpost/post.db", &db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL);
300 if (src != SQLITE_OK) {
301 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req, "mod_dumpost: cant open sqlite database: %s", sqlite3_errmsg(db));
302 } else {
303 sqlite3_stmt *stmt = NULL;
304 sqlite3_prepare_v2(db, "CREATE TABLE IF NOT EXISTS log (id INTEGER PRIMARY KEY, url TEXT, headers TEXT, body BLOB, fecha TEXT, ip TEXT, method TEXT);", -1, &stmt, NULL);
305 src = sqlite3_step(stmt);
306 if (src != SQLITE_DONE)
307 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req, "mod_dumpost: sqlite query failed: %s", sqlite3_errmsg(db));
308 sqlite3_finalize(stmt);
309 }
310 sqlite3_close(db);
311
312 ap_add_input_filter("DUMPOST_IN", NULL, req, req->connection);
313}
314
315static void dumpost_register_hooks(apr_pool_t *p) {
316 ap_hook_insert_filter(dumpost_insert_filter, NULL, NULL, APR_HOOK_FIRST);
317 ap_register_input_filter("DUMPOST_IN", dumpost_input_filter,
318 NULL, AP_FTYPE_CONTENT_SET);
319}
320
321static void *dumpost_create_dconfig(apr_pool_t *mp, char *path) {
322 dumpost_cfg_t *cfg = apr_pcalloc(mp, sizeof(dumpost_cfg_t));
323 cfg->max_size = DEFAULT_MAX_SIZE;
324 cfg->headers = apr_array_make(mp, 0, sizeof(char *));
325 cfg->pool = mp;
326 cfg->file = 0;
327 cfg->log_bin = 0;
328 cfg->filters = apr_array_make(mp, 0, sizeof(char *));
329 return cfg;
330}
331
332static const char *dumpost_set_max_size(cmd_parms *cmd, void *_cfg, const char *arg) {
333 dumpost_cfg_t *cfg = (dumpost_cfg_t *) _cfg; //ap_get_module_config(cmd->server->module_config, &dumpost_module);
334 cfg->max_size = atoi(arg);
335 if (cfg->max_size == 0)
336 cfg->max_size = DEFAULT_MAX_SIZE;
337 return NULL;
338}
339
340static const char *dumpost_add_header(cmd_parms *cmd, void *_cfg, const char *arg) {
341 dumpost_cfg_t *cfg = (dumpost_cfg_t *) _cfg;
342 *(const char**) apr_array_push(cfg->headers) = arg;
343 return NULL;
344}
345
346static const char *dumpost_log_file(cmd_parms *cmd, void *_cfg, const char *arg ){
347 dumpost_cfg_t *cfg = (dumpost_cfg_t *) _cfg;
348 cfg->file = (char *) arg;
349 return NULL;
350}
351
352static const char *dumpost_log_binary(cmd_parms *cmd, void *_cfg, const char *arg ){
353 dumpost_cfg_t *cfg = (dumpost_cfg_t *) _cfg;
354 if (strstr(arg, "On") != NULL || strstr(arg, "1") != NULL)
355 cfg->log_bin = 1;
356 else
357 cfg->log_bin = 0;
358 return NULL;
359}
360
361static const char *dumpost_filter(cmd_parms *cmd, void *_cfg, const char *arg) {
362 dumpost_cfg_t *cfg = (dumpost_cfg_t *) _cfg;
363 *(const char**) apr_array_push(cfg->filters) = arg;
364 return NULL;
365}
366
367static const command_rec dumpost_cmds[] = {
368 AP_INIT_TAKE1("DumpPostMaxSize", dumpost_set_max_size, NULL, RSRC_CONF, "Set maximum data size"),
369 AP_INIT_ITERATE("DumpPostHeaderAdd", dumpost_add_header, NULL, RSRC_CONF, "Add header to log"),
370 AP_INIT_TAKE1("DumpPostLogFile", dumpost_log_file, NULL, RSRC_CONF, "A custom file to log to"),
371 AP_INIT_TAKE1("DumpPostLogBinary", dumpost_log_binary, NULL, RSRC_CONF, "Should log binary data (On/Off)"),
372 AP_INIT_ITERATE("DumpPostFilter", dumpost_filter, NULL, RSRC_CONF, "Add matches to filter by text in the fist header"),
373 { NULL }
374};
375
376module AP_MODULE_DECLARE_DATA dumpost_module = {
377 STANDARD20_MODULE_STUFF,
378 dumpost_create_dconfig,
379 NULL,
380 NULL, //dumpost_create_sconfig,
381 NULL,
382 dumpost_cmds,
383 dumpost_register_hooks
384};