Imported Upstream version 2.5.11
[libapache-mod-security.git] / apache2 / apache2_util.c
1 /*
2  * ModSecurity for Apache 2.x, http://www.modsecurity.org/
3  * Copyright (c) 2004-2009 Breach Security, Inc. (http://www.breach.com/)
4  *
5  * This product is released under the terms of the General Public Licence,
6  * version 2 (GPLv2). Please refer to the file LICENSE (included with this
7  * distribution) which contains the complete text of the licence.
8  *
9  * There are special exceptions to the terms and conditions of the GPL
10  * as it is applied to this software. View the full text of the exception in
11  * file MODSECURITY_LICENSING_EXCEPTION in the directory of this software
12  * distribution.
13  *
14  * If any of the files related to licensing are missing or if you have any
15  * other questions related to licensing please contact Breach Security, Inc.
16  * directly using the email address support@breach.com.
17  *
18  */
19 #include "modsecurity.h"
20 #include "apache2.h"
21 #include "http_core.h"
22 #include "util_script.h"
23
24 /**
25  * Sends a brigade with an error bucket down the filter chain.
26  */
27 apr_status_t send_error_bucket(modsec_rec *msr, ap_filter_t *f, int status) {
28     apr_bucket_brigade *brigade = NULL;
29     apr_bucket *bucket = NULL;
30
31     /* Set the status line explicitly for the error document */
32     f->r->status_line = ap_get_status_line(status);
33
34     brigade = apr_brigade_create(f->r->pool, f->r->connection->bucket_alloc);
35     if (brigade == NULL) return APR_EGENERAL;
36
37     bucket = ap_bucket_error_create(status, NULL, f->r->pool, f->r->connection->bucket_alloc);
38     if (bucket == NULL) return APR_EGENERAL;
39
40     APR_BRIGADE_INSERT_TAIL(brigade, bucket);
41
42     bucket = apr_bucket_eos_create(f->r->connection->bucket_alloc);
43     if (bucket == NULL) return APR_EGENERAL;
44
45     APR_BRIGADE_INSERT_TAIL(brigade, bucket);
46
47     ap_pass_brigade(f->next, brigade);
48
49     /* NOTE:
50      * It may not matter what we do from the filter as it may be too
51      * late to even generate an error (already sent to client).  Nick Kew
52      * recommends to return APR_EGENERAL in hopes that the handler in control
53      * will notice and do The Right Thing.  So, that is what we do now.
54      */
55
56     return APR_EGENERAL;
57 }
58
59 /**
60  * Execute system command. First line of the output will be returned in
61  * the "output" parameter.
62  */
63 int apache2_exec(modsec_rec *msr, const char *command, const char **argv, char **output) {
64     apr_procattr_t *procattr = NULL;
65     apr_proc_t *procnew = NULL;
66     apr_status_t rc = APR_SUCCESS;
67     const char *const *env = NULL;
68     apr_file_t *script_out = NULL;
69     request_rec *r = msr->r;
70
71     if (argv == NULL) {
72         argv = apr_pcalloc(r->pool, 3 * sizeof(char *));
73         argv[0] = command;
74         argv[1] = NULL;
75     }
76
77     ap_add_cgi_vars(r);
78     ap_add_common_vars(r);
79
80     /* PHP hack, getting around its silly security checks. */
81     apr_table_add(r->subprocess_env, "PATH_TRANSLATED", command);
82     apr_table_add(r->subprocess_env, "REDIRECT_STATUS", "302");
83
84     env = (const char * const *)ap_create_environment(r->pool, r->subprocess_env);
85     if (env == NULL) {
86         msr_log(msr, 1, "Exec: Unable to create environment.");
87         return -1;
88     }
89
90     procnew = apr_pcalloc(r->pool, sizeof(*procnew));
91     if (procnew == NULL) {
92         msr_log(msr, 1, "Exec: Unable to allocate %lu bytes.", (unsigned long)sizeof(*procnew));
93         return -1;
94     }
95
96     apr_procattr_create(&procattr, r->pool);
97     if (procattr == NULL) {
98         msr_log(msr, 1, "Exec: Unable to create procattr.");
99         return -1;
100     }
101
102     apr_procattr_io_set(procattr, APR_NO_PIPE, APR_FULL_BLOCK, APR_NO_PIPE);
103     apr_procattr_cmdtype_set(procattr, APR_SHELLCMD);
104
105     if (msr->txcfg->debuglog_level >= 9) {
106         msr_log(msr, 9, "Exec: %s", log_escape_nq(r->pool, command));
107     }
108
109     rc = apr_proc_create(procnew, command, argv, env, procattr, r->pool);
110     if (rc != APR_SUCCESS) {
111         msr_log(msr, 1, "Exec: Execution failed: %s (%s)", log_escape_nq(r->pool, command),
112             get_apr_error(r->pool, rc));
113         return -1;
114     }
115
116     apr_pool_note_subprocess(r->pool, procnew, APR_KILL_AFTER_TIMEOUT);
117
118     script_out = procnew->out;
119     if (!script_out) {
120         msr_log(msr, 1, "Exec: Failed to get script output pipe.");
121         return -1;
122     }
123
124     apr_file_pipe_timeout_set(script_out, r->server->timeout);
125
126     /* Now read from the pipe. */
127     {
128         char buf[260] = "";
129         char *p = buf;
130         apr_size_t nbytes = 255;
131         apr_status_t rc2;
132
133         rc2 = apr_file_read(script_out, buf, &nbytes);
134         if (rc2 == APR_SUCCESS) {
135             buf[nbytes] = 0;
136
137             /* if there is more than one line ignore them */
138             while(*p != 0) {
139                 if (*p == 0x0a) *p = 0;
140                 p++;
141             }
142
143             if (msr->txcfg->debuglog_level >= 4) {
144                 msr_log(msr, 4, "Exec: First line from script output: \"%s\"",
145                     log_escape(r->pool, buf));
146             }
147
148             if (output != NULL) *output = apr_pstrdup(r->pool, buf);
149
150             /* Soak up the remaining data. */
151             nbytes = 255;
152             while(apr_file_read(script_out, buf, &nbytes) == APR_SUCCESS) nbytes = 255;
153         } else {
154             msr_log(msr, 1, "Exec: Execution failed while reading output: %s (%s)",
155                 log_escape_nq(r->pool, command),
156                 get_apr_error(r->pool, rc2));
157             return -1;
158         }
159     }
160
161     apr_proc_wait(procnew, NULL, NULL, APR_WAIT);
162
163     return 1;
164 }
165
166 /**
167  * Record the current time and store for later.
168  */
169 void record_time_checkpoint(modsec_rec *msr, int checkpoint_no) {
170     char note[100], note_name[100];
171     apr_time_t now;
172
173     now = apr_time_now();
174     switch(checkpoint_no) {
175         case 1 :
176             msr->time_checkpoint_1 = now;
177             break;
178         case 2 :
179             msr->time_checkpoint_2 = now;
180             break;
181         case 3 :
182             msr->time_checkpoint_3 = now;
183             break;
184         default :
185             msr_log(msr, 1, "Internal Error: Unknown checkpoint: %d", checkpoint_no);
186             return;
187             break;
188     }
189
190     /* Apache-specific stuff. */
191     apr_snprintf(note, 99, "%" APR_TIME_T_FMT, (now - msr->request_time));
192     apr_snprintf(note_name, 99, "mod_security-time%d", checkpoint_no);
193     apr_table_set(msr->r->notes, note_name, note);
194
195     if (msr->txcfg->debuglog_level >= 4) {
196         msr_log(msr, 4, "Time #%d: %s", checkpoint_no, note);
197     }
198 }
199
200 /**
201  * Returns a new string that contains the error
202  * message for the given return code.
203  */
204 char *get_apr_error(apr_pool_t *p, apr_status_t rc) {
205     char *text = apr_pcalloc(p, 201);
206     if (text == NULL) return NULL;
207     apr_strerror(rc, text, 200);
208     return text;
209 }
210
211 /**
212  * Retrieve named environment variable.
213  */
214 char *get_env_var(request_rec *r, char *name) {
215     char *result = (char *)apr_table_get(r->notes, name);
216
217     if (result == NULL) {
218         result = (char *)apr_table_get(r->subprocess_env, name);
219     }
220
221     if (result == NULL) {
222         result = getenv(name);
223     }
224
225     return result;
226 }
227
228 /**
229  * Extended internal log helper function. Use msr_log instead. If fixup is
230  * true, the message will be stripped of any trailing newline and any
231  * required bytes will be escaped.
232  */
233 void internal_log_ex(request_rec *r, directory_config *dcfg, modsec_rec *msr,
234     int level, int fixup, const char *text, va_list ap)
235 {
236     apr_size_t nbytes, nbytes_written;
237     apr_file_t *debuglog_fd = NULL;
238     int filter_debug_level = 0;
239     char str1[1024] = "";
240     char str2[1256] = "";
241
242     /* Find the logging FD and determine the logging level from configuration. */
243     if (dcfg != NULL) {
244         if ((dcfg->debuglog_fd != NULL)&&(dcfg->debuglog_fd != NOT_SET_P)) {
245             debuglog_fd = dcfg->debuglog_fd;
246         }
247
248         if (dcfg->debuglog_level != NOT_SET) {
249             filter_debug_level = dcfg->debuglog_level;
250         }
251     }
252
253     /* Return immediately if we don't have where to write
254      * or if the log level of the message is higher than
255      * wanted in the log.
256      */
257     if ((level > 3)&&( (debuglog_fd == NULL) || (level > filter_debug_level) )) return;
258
259     /* Construct the message. */
260     apr_vsnprintf(str1, sizeof(str1), text, ap);
261     if (fixup) {
262         int len = strlen(str1);
263
264         /* Strip line ending. */
265         if (len && str1[len - 1] == '\n') {
266             str1[len - 1] = '\0';
267         }
268         if (len > 1 && str1[len - 2] == '\r') {
269             str1[len - 2] = '\0';
270         }
271     }
272
273     /* Construct the log entry. */
274     apr_snprintf(str2, sizeof(str2), 
275         "[%s] [%s/sid#%pp][rid#%pp][%s][%d] %s\n",
276         current_logtime(msr->mp), ap_get_server_name(r), (r->server),
277         r, ((r->uri == NULL) ? "" : log_escape_nq(msr->mp, r->uri)),
278         level, (fixup ? log_escape_nq(msr->mp, str1) : str1));
279
280     /* Write to the debug log. */
281     if ((debuglog_fd != NULL)&&(level <= filter_debug_level)) {
282         nbytes = strlen(str2);
283         apr_file_write_full(debuglog_fd, str2, nbytes, &nbytes_written);
284     }
285
286     /* Send message levels 1-3 to the Apache error log and 
287      * add it to the message list in the audit log. */
288     if (level <= 3) {
289         char *unique_id = (char *)get_env_var(r, "UNIQUE_ID");
290         char *hostname = (char *)msr->hostname;
291
292         if (unique_id != NULL) {
293             unique_id = apr_psprintf(msr->mp, " [unique_id \"%s\"]",
294                 log_escape(msr->mp, unique_id));
295         }
296         else unique_id = "";
297
298         if (hostname != NULL) {
299             hostname = apr_psprintf(msr->mp, " [hostname \"%s\"]",
300                 log_escape(msr->mp, hostname));
301         }
302         else hostname = "";
303
304         ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r->server,
305             "[client %s] ModSecurity: %s%s [uri \"%s\"]%s", r->connection->remote_ip, str1,
306             hostname, log_escape(msr->mp, r->uri), unique_id);
307
308         /* Add this message to the list. */
309         if (msr != NULL) {
310             /* Force relevency if this is an alert */
311             msr->is_relevant++;
312
313             *(const char **)apr_array_push(msr->alerts) = apr_pstrdup(msr->mp, str1);
314         }
315     }
316
317     return;
318 }
319
320 /**
321  * Internal log helper function. Use msr_log instead.
322  */
323 void internal_log(request_rec *r, directory_config *dcfg, modsec_rec *msr,
324     int level, const char *text, va_list ap)
325 {
326     internal_log_ex(r, dcfg, msr, level, 0, text, ap);
327 }
328
329 /**
330  * Logs one message at the given level to the debug log (and to the
331  * Apache error log if the message is important enough.
332  */
333 void msr_log(modsec_rec *msr, int level, const char *text, ...) {
334     va_list ap;
335
336     va_start(ap, text);
337     internal_log_ex(msr->r, msr->txcfg, msr, level, 0, text, ap);
338     va_end(ap);
339 }
340
341
342 /**
343  * Logs one message at level 3 to the debug log and to the
344  * Apache error log. This is intended for error callbacks.
345  */
346 void msr_log_error(modsec_rec *msr, const char *text, ...) {
347     va_list ap;
348
349     va_start(ap, text);
350     internal_log_ex(msr->r, msr->txcfg, msr, 3, 1, text, ap);
351     va_end(ap);
352 }
353
354 /**
355  * Logs one message at level 4 to the debug log and to the
356  * Apache error log. This is intended for warning callbacks.
357  *
358  * The 'text' will first be escaped.
359  */
360 void msr_log_warn(modsec_rec *msr, const char *text, ...) {
361     va_list ap;
362
363     va_start(ap, text);
364     internal_log_ex(msr->r, msr->txcfg, msr, 4, 1, text, ap);
365     va_end(ap);
366 }
367
368
369 /**
370  * Converts an Apache error log message into one line of text.
371  */
372 char *format_error_log_message(apr_pool_t *mp, error_message *em) {
373     char *s_file = "", *s_line = "", *s_level = "";
374     char *s_status = "", *s_message = "";
375     char *msg = NULL;
376
377     if (em == NULL) return NULL;
378
379     if (em->file != NULL) {
380         s_file = apr_psprintf(mp, "[file \"%s\"] ",
381             log_escape(mp, (char *)em->file));
382         if (s_file == NULL) return NULL;
383     }
384
385     if (em->line > 0) {
386         s_line = apr_psprintf(mp, "[line %d] ", em->line);
387         if (s_line == NULL) return NULL;
388     }
389
390     s_level = apr_psprintf(mp, "[level %d] ", em->level);
391     if (s_level == NULL) return NULL;
392
393     if (em->status != 0) {
394         s_status = apr_psprintf(mp, "[status %d] ", em->status);
395         if (s_status == NULL) return NULL;
396     }
397
398     if (em->message != NULL) {
399         s_message = log_escape_nq(mp, em->message);
400         if (s_message == NULL) return NULL;
401     }
402
403     msg = apr_psprintf(mp, "%s%s%s%s%s", s_file, s_line, s_level, s_status, s_message);
404     if (msg == NULL) return NULL;
405
406     return msg;
407 }
408
409 /**
410  * Determines the reponse protocol Apache will use (or has used)
411  * to respond to the given request.
412  */
413 const char *get_response_protocol(request_rec *r) {
414     int proto_num = r->proto_num;
415
416     if (r->assbackwards) {
417         return NULL;
418     }
419
420     if (proto_num > HTTP_VERSION(1,0)
421         && apr_table_get(r->subprocess_env, "downgrade-1.0"))
422     {
423         proto_num = HTTP_VERSION(1,0);
424     }
425
426     if (proto_num == HTTP_VERSION(1,0)
427         && apr_table_get(r->subprocess_env, "force-response-1.0"))
428     {
429         return "HTTP/1.0";
430     }
431
432     return AP_SERVER_PROTOCOL;
433 }