/src/libhtp/htp/htp_transaction.c
Line | Count | Source (jump to first uncovered line) |
1 | | /*************************************************************************** |
2 | | * Copyright (c) 2009-2010 Open Information Security Foundation |
3 | | * Copyright (c) 2010-2013 Qualys, Inc. |
4 | | * All rights reserved. |
5 | | * |
6 | | * Redistribution and use in source and binary forms, with or without |
7 | | * modification, are permitted provided that the following conditions are |
8 | | * met: |
9 | | * |
10 | | * - Redistributions of source code must retain the above copyright |
11 | | * notice, this list of conditions and the following disclaimer. |
12 | | |
13 | | * - Redistributions in binary form must reproduce the above copyright |
14 | | * notice, this list of conditions and the following disclaimer in the |
15 | | * documentation and/or other materials provided with the distribution. |
16 | | |
17 | | * - Neither the name of the Qualys, Inc. nor the names of its |
18 | | * contributors may be used to endorse or promote products derived from |
19 | | * this software without specific prior written permission. |
20 | | * |
21 | | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
22 | | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
23 | | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
24 | | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
25 | | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
26 | | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
27 | | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
28 | | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
29 | | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
30 | | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
31 | | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
32 | | ***************************************************************************/ |
33 | | |
34 | | /** |
35 | | * @file |
36 | | * @author Ivan Ristic <ivanr@webkreator.com> |
37 | | */ |
38 | | |
39 | | #include "htp_config_auto.h" |
40 | | |
41 | | #include "htp_private.h" |
42 | | |
43 | | static void htp_tx_req_destroy_decompressors(htp_connp_t *connp); |
44 | | static htp_status_t htp_tx_req_process_body_data_decompressor_callback(htp_tx_data_t *d); |
45 | | |
46 | 0 | static bstr *copy_or_wrap_mem(const void *data, size_t len, enum htp_alloc_strategy_t alloc) { |
47 | 0 | if (data == NULL) return NULL; |
48 | | |
49 | 0 | if (alloc == HTP_ALLOC_REUSE) { |
50 | 0 | return bstr_wrap_mem(data, len); |
51 | 0 | } else { |
52 | 0 | return bstr_dup_mem(data, len); |
53 | 0 | } |
54 | 0 | } |
55 | | |
56 | 93.1k | htp_tx_t *htp_tx_create(htp_connp_t *connp) { |
57 | 93.1k | if (connp == NULL) return NULL; |
58 | | |
59 | 93.1k | htp_tx_t *tx = calloc(1, sizeof (htp_tx_t)); |
60 | 93.1k | if (tx == NULL) return NULL; |
61 | | |
62 | 93.1k | tx->connp = connp; |
63 | 93.1k | tx->conn = connp->conn; |
64 | 93.1k | tx->index = htp_list_size(tx->conn->transactions); |
65 | 93.1k | tx->cfg = connp->cfg; |
66 | 93.1k | tx->is_config_shared = HTP_CONFIG_SHARED; |
67 | | |
68 | | // Request fields. |
69 | | |
70 | 93.1k | tx->request_progress = HTP_REQUEST_NOT_STARTED; |
71 | 93.1k | tx->request_protocol_number = HTP_PROTOCOL_UNKNOWN; |
72 | 93.1k | tx->request_content_length = -1; |
73 | | |
74 | 93.1k | tx->parsed_uri_raw = htp_uri_alloc(); |
75 | 93.1k | if (tx->parsed_uri_raw == NULL) { |
76 | 0 | htp_tx_destroy_incomplete(tx); |
77 | 0 | return NULL; |
78 | 0 | } |
79 | | |
80 | 93.1k | tx->request_headers = htp_table_create(32); |
81 | 93.1k | if (tx->request_headers == NULL) { |
82 | 0 | htp_tx_destroy_incomplete(tx); |
83 | 0 | return NULL; |
84 | 0 | } |
85 | | |
86 | 93.1k | tx->request_params = htp_table_create(32); |
87 | 93.1k | if (tx->request_params == NULL) { |
88 | 0 | htp_tx_destroy_incomplete(tx); |
89 | 0 | return NULL; |
90 | 0 | } |
91 | | |
92 | | // Response fields. |
93 | | |
94 | 93.1k | tx->response_progress = HTP_RESPONSE_NOT_STARTED; |
95 | 93.1k | tx->response_status = NULL; |
96 | 93.1k | tx->response_status_number = HTP_STATUS_UNKNOWN; |
97 | 93.1k | tx->response_protocol_number = HTP_PROTOCOL_UNKNOWN; |
98 | 93.1k | tx->response_content_length = -1; |
99 | | |
100 | 93.1k | tx->response_headers = htp_table_create(32); |
101 | 93.1k | if (tx->response_headers == NULL) { |
102 | 0 | htp_tx_destroy_incomplete(tx); |
103 | 0 | return NULL; |
104 | 0 | } |
105 | | |
106 | 93.1k | htp_list_add(tx->conn->transactions, tx); |
107 | | |
108 | 93.1k | return tx; |
109 | 93.1k | } |
110 | | |
111 | 0 | htp_status_t htp_tx_destroy(htp_tx_t *tx) { |
112 | 0 | if (tx == NULL) return HTP_ERROR; |
113 | | |
114 | 0 | if (!htp_tx_is_complete(tx)) return HTP_ERROR; |
115 | | |
116 | 0 | htp_tx_destroy_incomplete(tx); |
117 | |
|
118 | 0 | return HTP_OK; |
119 | 0 | } |
120 | | |
121 | 93.1k | void htp_tx_destroy_incomplete(htp_tx_t *tx) { |
122 | 93.1k | if (tx == NULL) return; |
123 | | |
124 | | // Disconnect transaction from other structures. |
125 | 93.1k | htp_conn_remove_tx(tx->conn, tx); |
126 | 93.1k | htp_connp_tx_remove(tx->connp, tx); |
127 | | |
128 | | // Request fields. |
129 | | |
130 | 93.1k | bstr_free(tx->request_line); |
131 | 93.1k | bstr_free(tx->request_method); |
132 | 93.1k | bstr_free(tx->request_uri); |
133 | 93.1k | bstr_free(tx->request_protocol); |
134 | 93.1k | bstr_free(tx->request_content_type); |
135 | 93.1k | bstr_free(tx->request_hostname); |
136 | 93.1k | htp_uri_free(tx->parsed_uri_raw); |
137 | 93.1k | htp_uri_free(tx->parsed_uri); |
138 | 93.1k | bstr_free(tx->request_auth_username); |
139 | 93.1k | bstr_free(tx->request_auth_password); |
140 | | |
141 | | // Request_headers. |
142 | 93.1k | if (tx->request_headers != NULL) { |
143 | 93.1k | htp_header_t *h = NULL; |
144 | 119k | for (size_t i = 0, n = htp_table_size(tx->request_headers); i < n; i++) { |
145 | 26.6k | h = htp_table_get_index(tx->request_headers, i, NULL); |
146 | 26.6k | bstr_free(h->name); |
147 | 26.6k | bstr_free(h->value); |
148 | 26.6k | free(h); |
149 | 26.6k | } |
150 | | |
151 | 93.1k | htp_table_destroy(tx->request_headers); |
152 | 93.1k | } |
153 | | |
154 | | // Request parsers. |
155 | | |
156 | 93.1k | htp_urlenp_destroy(tx->request_urlenp_query); |
157 | 93.1k | htp_urlenp_destroy(tx->request_urlenp_body); |
158 | 93.1k | htp_mpartp_destroy(tx->request_mpartp); |
159 | | |
160 | | // Request parameters. |
161 | | |
162 | 93.1k | htp_param_t *param = NULL; |
163 | 93.1k | for (size_t i = 0, n = htp_table_size(tx->request_params); i < n; i++) { |
164 | 0 | param = htp_table_get_index(tx->request_params, i, NULL); |
165 | 0 | bstr_free(param->name); |
166 | 0 | bstr_free(param->value); |
167 | 0 | free(param); |
168 | 0 | } |
169 | | |
170 | 93.1k | htp_table_destroy(tx->request_params); |
171 | | |
172 | | // Request cookies. |
173 | | |
174 | 93.1k | if (tx->request_cookies != NULL) { |
175 | 0 | bstr *b = NULL; |
176 | 0 | for (size_t i = 0, n = htp_table_size(tx->request_cookies); i < n; i++) { |
177 | 0 | b = htp_table_get_index(tx->request_cookies, i, NULL); |
178 | 0 | bstr_free(b); |
179 | 0 | } |
180 | |
|
181 | 0 | htp_table_destroy(tx->request_cookies); |
182 | 0 | } |
183 | | |
184 | 93.1k | htp_hook_destroy(tx->hook_request_body_data); |
185 | | |
186 | | // Response fields. |
187 | | |
188 | 93.1k | bstr_free(tx->response_line); |
189 | 93.1k | bstr_free(tx->response_protocol); |
190 | 93.1k | bstr_free(tx->response_status); |
191 | 93.1k | bstr_free(tx->response_message); |
192 | 93.1k | bstr_free(tx->response_content_type); |
193 | | |
194 | | // Destroy response headers. |
195 | 93.1k | if (tx->response_headers != NULL) { |
196 | 93.1k | htp_header_t *h = NULL; |
197 | 123k | for (size_t i = 0, n = htp_table_size(tx->response_headers); i < n; i++) { |
198 | 30.1k | h = htp_table_get_index(tx->response_headers, i, NULL); |
199 | 30.1k | bstr_free(h->name); |
200 | 30.1k | bstr_free(h->value); |
201 | 30.1k | free(h); |
202 | 30.1k | } |
203 | | |
204 | 93.1k | htp_table_destroy(tx->response_headers); |
205 | 93.1k | } |
206 | | |
207 | | // If we're using a private configuration structure, destroy it. |
208 | 93.1k | if (tx->is_config_shared == HTP_CONFIG_PRIVATE) { |
209 | 0 | htp_config_destroy(tx->cfg); |
210 | 0 | } |
211 | | |
212 | 93.1k | free(tx); |
213 | 93.1k | } |
214 | | |
215 | 0 | int htp_tx_get_is_config_shared(const htp_tx_t *tx) { |
216 | 0 | if (tx == NULL) return -1; |
217 | 0 | return tx->is_config_shared; |
218 | 0 | } |
219 | | |
220 | 0 | void *htp_tx_get_user_data(const htp_tx_t *tx) { |
221 | 0 | if (tx == NULL) return NULL; |
222 | 0 | return tx->user_data; |
223 | 0 | } |
224 | | |
225 | 0 | void htp_tx_set_config(htp_tx_t *tx, htp_cfg_t *cfg, int is_cfg_shared) { |
226 | 0 | if ((tx == NULL) || (cfg == NULL)) return; |
227 | | |
228 | 0 | if ((is_cfg_shared != HTP_CONFIG_PRIVATE) && (is_cfg_shared != HTP_CONFIG_SHARED)) return; |
229 | | |
230 | | // If we're using a private configuration, destroy it. |
231 | 0 | if (tx->is_config_shared == HTP_CONFIG_PRIVATE) { |
232 | 0 | htp_config_destroy(tx->cfg); |
233 | 0 | } |
234 | |
|
235 | 0 | tx->cfg = cfg; |
236 | 0 | tx->is_config_shared = is_cfg_shared; |
237 | 0 | } |
238 | | |
239 | 0 | void htp_tx_set_user_data(htp_tx_t *tx, void *user_data) { |
240 | 0 | if (tx == NULL) return; |
241 | 0 | tx->user_data = user_data; |
242 | 0 | } |
243 | | |
244 | 0 | htp_status_t htp_tx_req_add_param(htp_tx_t *tx, htp_param_t *param) { |
245 | 0 | if ((tx == NULL) || (param == NULL)) return HTP_ERROR; |
246 | | |
247 | 0 | if (tx->cfg->parameter_processor != NULL) { |
248 | 0 | if (tx->cfg->parameter_processor(param) != HTP_OK) return HTP_ERROR; |
249 | 0 | } |
250 | | |
251 | 0 | return htp_table_addk(tx->request_params, param->name, param); |
252 | 0 | } |
253 | | |
254 | 0 | htp_param_t *htp_tx_req_get_param(htp_tx_t *tx, const char *name, size_t name_len) { |
255 | 0 | if ((tx == NULL) || (name == NULL)) return NULL; |
256 | 0 | return htp_table_get_mem(tx->request_params, name, name_len); |
257 | 0 | } |
258 | | |
259 | 0 | htp_param_t *htp_tx_req_get_param_ex(htp_tx_t *tx, enum htp_data_source_t source, const char *name, size_t name_len) { |
260 | 0 | if ((tx == NULL) || (name == NULL)) return NULL; |
261 | | |
262 | 0 | htp_param_t *p = NULL; |
263 | |
|
264 | 0 | for (size_t i = 0, n = htp_table_size(tx->request_params); i < n; i++) { |
265 | 0 | p = htp_table_get_index(tx->request_params, i, NULL); |
266 | 0 | if (p->source != source) continue; |
267 | | |
268 | 0 | if (bstr_cmp_mem_nocase(p->name, name, name_len) == 0) return p; |
269 | 0 | } |
270 | | |
271 | 0 | return NULL; |
272 | 0 | } |
273 | | |
274 | 92.6k | int htp_tx_req_has_body(const htp_tx_t *tx) { |
275 | 92.6k | if (tx == NULL) return -1; |
276 | | |
277 | 92.6k | if ((tx->request_transfer_coding == HTP_CODING_IDENTITY) || (tx->request_transfer_coding == HTP_CODING_CHUNKED)) { |
278 | 0 | return 1; |
279 | 0 | } |
280 | | |
281 | 92.6k | return 0; |
282 | 92.6k | } |
283 | | |
284 | | htp_status_t htp_tx_req_set_header(htp_tx_t *tx, const char *name, size_t name_len, |
285 | 0 | const char *value, size_t value_len, enum htp_alloc_strategy_t alloc) { |
286 | 0 | if ((tx == NULL) || (name == NULL) || (value == NULL)) return HTP_ERROR; |
287 | | |
288 | 0 | htp_header_t *h = calloc(1, sizeof (htp_header_t)); |
289 | 0 | if (h == NULL) return HTP_ERROR; |
290 | | |
291 | 0 | h->name = copy_or_wrap_mem(name, name_len, alloc); |
292 | 0 | if (h->name == NULL) { |
293 | 0 | free(h); |
294 | 0 | return HTP_ERROR; |
295 | 0 | } |
296 | | |
297 | 0 | h->value = copy_or_wrap_mem(value, value_len, alloc); |
298 | 0 | if (h->value == NULL) { |
299 | 0 | bstr_free(h->name); |
300 | 0 | free(h); |
301 | 0 | return HTP_ERROR; |
302 | 0 | } |
303 | | |
304 | 0 | if (htp_table_add(tx->request_headers, h->name, h) != HTP_OK) { |
305 | 0 | bstr_free(h->name); |
306 | 0 | bstr_free(h->value); |
307 | 0 | free(h); |
308 | 0 | return HTP_ERROR; |
309 | 0 | } |
310 | | |
311 | 0 | return HTP_OK; |
312 | 0 | } |
313 | | |
314 | 0 | htp_status_t htp_tx_req_set_method(htp_tx_t *tx, const char *method, size_t method_len, enum htp_alloc_strategy_t alloc) { |
315 | 0 | if ((tx == NULL) || (method == NULL)) return HTP_ERROR; |
316 | | |
317 | 0 | tx->request_method = copy_or_wrap_mem(method, method_len, alloc); |
318 | 0 | if (tx->request_method == NULL) return HTP_ERROR; |
319 | | |
320 | 0 | return HTP_OK; |
321 | 0 | } |
322 | | |
323 | 0 | void htp_tx_req_set_method_number(htp_tx_t *tx, enum htp_method_t method_number) { |
324 | 0 | if (tx == NULL) return; |
325 | 0 | tx->request_method_number = method_number; |
326 | 0 | } |
327 | | |
328 | 0 | htp_status_t htp_tx_req_set_uri(htp_tx_t *tx, const char *uri, size_t uri_len, enum htp_alloc_strategy_t alloc) { |
329 | 0 | if ((tx == NULL) || (uri == NULL)) return HTP_ERROR; |
330 | | |
331 | 0 | tx->request_uri = copy_or_wrap_mem(uri, uri_len, alloc); |
332 | 0 | if (tx->request_uri == NULL) return HTP_ERROR; |
333 | | |
334 | 0 | return HTP_OK; |
335 | 0 | } |
336 | | |
337 | 0 | htp_status_t htp_tx_req_set_protocol(htp_tx_t *tx, const char *protocol, size_t protocol_len, enum htp_alloc_strategy_t alloc) { |
338 | 0 | if ((tx == NULL) || (protocol == NULL)) return HTP_ERROR; |
339 | | |
340 | 0 | tx->request_protocol = copy_or_wrap_mem(protocol, protocol_len, alloc); |
341 | 0 | if (tx->request_protocol == NULL) return HTP_ERROR; |
342 | | |
343 | 0 | return HTP_OK; |
344 | 0 | } |
345 | | |
346 | 0 | void htp_tx_req_set_protocol_number(htp_tx_t *tx, int protocol_number) { |
347 | 0 | if (tx == NULL) return; |
348 | 0 | tx->request_protocol_number = protocol_number; |
349 | 0 | } |
350 | | |
351 | 0 | void htp_tx_req_set_protocol_0_9(htp_tx_t *tx, int is_protocol_0_9) { |
352 | 0 | if (tx == NULL) return; |
353 | | |
354 | 0 | if (is_protocol_0_9) { |
355 | 0 | tx->is_protocol_0_9 = 1; |
356 | 0 | } else { |
357 | 0 | tx->is_protocol_0_9 = 0; |
358 | 0 | } |
359 | 0 | } |
360 | | |
361 | 59.8k | static htp_status_t htp_tx_process_request_headers(htp_tx_t *tx) { |
362 | 59.8k | if (tx == NULL) return HTP_ERROR; |
363 | | |
364 | | // Determine if we have a request body, and how it is packaged. |
365 | | |
366 | 59.8k | htp_status_t rc = HTP_OK; |
367 | | |
368 | 59.8k | if (tx->connp->cfg->request_decompression_enabled) { |
369 | 0 | tx->request_content_encoding = HTP_COMPRESSION_NONE; |
370 | 0 | htp_header_t *ce = htp_table_get_c(tx->request_headers, "content-encoding"); |
371 | 0 | if (ce != NULL) { |
372 | | /* fast paths: regular gzip and friends */ |
373 | 0 | if ((bstr_cmp_c_nocasenorzero(ce->value, "gzip") == 0) || |
374 | 0 | (bstr_cmp_c_nocasenorzero(ce->value, "x-gzip") == 0)) { |
375 | 0 | tx->request_content_encoding = HTP_COMPRESSION_GZIP; |
376 | 0 | } else if ((bstr_cmp_c_nocasenorzero(ce->value, "deflate") == 0) || |
377 | 0 | (bstr_cmp_c_nocasenorzero(ce->value, "x-deflate") == 0)) { |
378 | 0 | tx->request_content_encoding = HTP_COMPRESSION_DEFLATE; |
379 | 0 | } else if (bstr_cmp_c_nocasenorzero(ce->value, "lzma") == 0) { |
380 | 0 | tx->request_content_encoding = HTP_COMPRESSION_LZMA; |
381 | 0 | } |
382 | | //ignore other cases such as inflate, ot multiple layers |
383 | 0 | if ((tx->request_content_encoding != HTP_COMPRESSION_NONE)) |
384 | 0 | { |
385 | 0 | if (tx->connp->req_decompressor != NULL) { |
386 | 0 | htp_tx_req_destroy_decompressors(tx->connp); |
387 | 0 | } |
388 | 0 | tx->connp->req_decompressor = htp_gzip_decompressor_create(tx->connp, tx->request_content_encoding); |
389 | 0 | if (tx->connp->req_decompressor == NULL) |
390 | 0 | return HTP_ERROR; |
391 | | |
392 | 0 | tx->connp->req_decompressor->callback = htp_tx_req_process_body_data_decompressor_callback; |
393 | 0 | } |
394 | 0 | } |
395 | 0 | } |
396 | | |
397 | 59.8k | htp_header_t *cl = htp_table_get_c(tx->request_headers, "content-length"); |
398 | 59.8k | htp_header_t *te = htp_table_get_c(tx->request_headers, "transfer-encoding"); |
399 | | |
400 | | // Check for the Transfer-Encoding header, which would indicate a chunked request body. |
401 | 59.8k | if (te != NULL) { |
402 | | // Make sure it contains "chunked" only. |
403 | | // TODO The HTTP/1.1 RFC also allows the T-E header to contain "identity", which |
404 | | // presumably should have the same effect as T-E header absence. However, Apache |
405 | | // (2.2.22 on Ubuntu 12.04 LTS) instead errors out with "Unknown Transfer-Encoding: identity". |
406 | | // And it behaves strangely, too, sending a 501 and proceeding to process the request |
407 | | // (e.g., PHP is run), but without the body. It then closes the connection. |
408 | 0 | if (htp_header_has_token(bstr_ptr(te->value), bstr_len(te->value), (unsigned char*) "chunked") != HTP_OK) { |
409 | | // Invalid T-E header value. |
410 | 0 | tx->request_transfer_coding = HTP_CODING_INVALID; |
411 | 0 | tx->flags |= HTP_REQUEST_INVALID_T_E; |
412 | 0 | tx->flags |= HTP_REQUEST_INVALID; |
413 | 0 | } else { |
414 | | // Chunked encoding is a HTTP/1.1 feature, so check that an earlier protocol |
415 | | // version is not used. The flag will also be set if the protocol could not be parsed. |
416 | | // |
417 | | // TODO IIS 7.0, for example, would ignore the T-E header when it |
418 | | // it is used with a protocol below HTTP 1.1. This should be a |
419 | | // personality trait. |
420 | 0 | if (tx->request_protocol_number < HTP_PROTOCOL_1_1) { |
421 | 0 | tx->flags |= HTP_REQUEST_INVALID_T_E; |
422 | 0 | tx->flags |= HTP_REQUEST_SMUGGLING; |
423 | 0 | } |
424 | | |
425 | | // If the T-E header is present we are going to use it. |
426 | 0 | tx->request_transfer_coding = HTP_CODING_CHUNKED; |
427 | | |
428 | | // We are still going to check for the presence of C-L. |
429 | 0 | if (cl != NULL) { |
430 | | // According to the HTTP/1.1 RFC (section 4.4): |
431 | | // |
432 | | // "The Content-Length header field MUST NOT be sent |
433 | | // if these two lengths are different (i.e., if a Transfer-Encoding |
434 | | // header field is present). If a message is received with both a |
435 | | // Transfer-Encoding header field and a Content-Length header field, |
436 | | // the latter MUST be ignored." |
437 | | // |
438 | 0 | tx->flags |= HTP_REQUEST_SMUGGLING; |
439 | 0 | } |
440 | 0 | } |
441 | 59.8k | } else if (cl != NULL) { |
442 | | // Check for a folded C-L header. |
443 | 0 | if (cl->flags & HTP_FIELD_FOLDED) { |
444 | 0 | tx->flags |= HTP_REQUEST_SMUGGLING; |
445 | 0 | } |
446 | | |
447 | | // Check for multiple C-L headers. |
448 | 0 | if (cl->flags & HTP_FIELD_REPEATED) { |
449 | 0 | tx->flags |= HTP_REQUEST_SMUGGLING; |
450 | | // TODO Personality trait to determine which C-L header to parse. |
451 | | // At the moment we're parsing the combination of all instances, |
452 | | // which is bound to fail (because it will contain commas). |
453 | 0 | } |
454 | | |
455 | | // Get the body length. |
456 | 0 | tx->request_content_length = htp_parse_content_length(cl->value, tx->connp); |
457 | 0 | if (tx->request_content_length < 0) { |
458 | 0 | tx->request_transfer_coding = HTP_CODING_INVALID; |
459 | 0 | tx->flags |= HTP_REQUEST_INVALID_C_L; |
460 | 0 | tx->flags |= HTP_REQUEST_INVALID; |
461 | 0 | } else { |
462 | | // We have a request body of known length. |
463 | 0 | tx->request_transfer_coding = HTP_CODING_IDENTITY; |
464 | 0 | } |
465 | 59.8k | } else { |
466 | | // No body. |
467 | 59.8k | tx->request_transfer_coding = HTP_CODING_NO_BODY; |
468 | 59.8k | } |
469 | | |
470 | | // If we could not determine the correct body handling, |
471 | | // consider the request invalid. |
472 | 59.8k | if (tx->request_transfer_coding == HTP_CODING_UNKNOWN) { |
473 | 0 | tx->request_transfer_coding = HTP_CODING_INVALID; |
474 | 0 | tx->flags |= HTP_REQUEST_INVALID; |
475 | 0 | } |
476 | | |
477 | | // Check for PUT requests, which we need to treat as file uploads. |
478 | 59.8k | if (tx->request_method_number == HTP_M_PUT) { |
479 | 655 | if (htp_tx_req_has_body(tx)) { |
480 | | // Prepare to treat PUT request body as a file. |
481 | | |
482 | 0 | tx->connp->put_file = calloc(1, sizeof (htp_file_t)); |
483 | 0 | if (tx->connp->put_file == NULL) return HTP_ERROR; |
484 | | |
485 | 0 | tx->connp->put_file->fd = -1; |
486 | 0 | tx->connp->put_file->source = HTP_FILE_PUT; |
487 | 655 | } else { |
488 | | // TODO Warn about PUT request without a body. |
489 | 655 | } |
490 | 655 | } |
491 | | |
492 | | // Determine hostname. |
493 | | |
494 | | // Use the hostname from the URI, when available. |
495 | 59.8k | if (tx->parsed_uri->hostname != NULL) { |
496 | 18.9k | tx->request_hostname = bstr_dup(tx->parsed_uri->hostname); |
497 | 18.9k | if (tx->request_hostname == NULL) return HTP_ERROR; |
498 | 18.9k | } |
499 | | |
500 | 59.8k | tx->request_port_number = tx->parsed_uri->port_number; |
501 | | |
502 | | // Examine the Host header. |
503 | | |
504 | 59.8k | htp_header_t *h = htp_table_get_c(tx->request_headers, "host"); |
505 | 59.8k | if (h == NULL) { |
506 | | // No host information in the headers. |
507 | | |
508 | | // HTTP/1.1 requires host information in the headers. |
509 | 54.5k | if (tx->request_protocol_number >= HTP_PROTOCOL_1_1) { |
510 | 358 | tx->flags |= HTP_HOST_MISSING; |
511 | 358 | } |
512 | 54.5k | } else { |
513 | | // Host information available in the headers. |
514 | | |
515 | 5.32k | bstr *hostname; |
516 | 5.32k | int port; |
517 | | |
518 | 5.32k | rc = htp_parse_header_hostport(h->value, &hostname, NULL, &port, &(tx->flags)); |
519 | 5.32k | if (rc != HTP_OK) return rc; |
520 | | |
521 | 5.32k | if (hostname != NULL) { |
522 | | // The host information in the headers is valid. |
523 | | |
524 | | // Is there host information in the URI? |
525 | 4.65k | if (tx->request_hostname == NULL) { |
526 | | // There is no host information in the URI. Place the |
527 | | // hostname from the headers into the parsed_uri structure. |
528 | 2.31k | tx->request_hostname = hostname; |
529 | 2.31k | tx->request_port_number = port; |
530 | 2.34k | } else { |
531 | | // The host information appears in the URI and in the headers. The |
532 | | // HTTP RFC states that we should ignore the header copy. |
533 | | |
534 | | // Check for different hostnames. |
535 | 2.34k | if (bstr_cmp_nocase(hostname, tx->request_hostname) != 0) { |
536 | 1.24k | tx->flags |= HTP_HOST_AMBIGUOUS; |
537 | 1.24k | } |
538 | | |
539 | | // Check for different ports. |
540 | 2.34k | if (((tx->request_port_number != -1)&&(port != -1))&&(tx->request_port_number != port)) { |
541 | 407 | tx->flags |= HTP_HOST_AMBIGUOUS; |
542 | 407 | } |
543 | | |
544 | 2.34k | bstr_free(hostname); |
545 | 2.34k | } |
546 | 4.65k | } else { |
547 | | // Invalid host information in the headers. |
548 | | |
549 | 667 | if (tx->request_hostname != NULL) { |
550 | | // Raise the flag, even though the host information in the headers is invalid. |
551 | 49 | tx->flags |= HTP_HOST_AMBIGUOUS; |
552 | 49 | } |
553 | 667 | } |
554 | 5.32k | } |
555 | | |
556 | | // Determine Content-Type. |
557 | 59.8k | htp_header_t *ct = htp_table_get_c(tx->request_headers, "content-type"); |
558 | 59.8k | if (ct != NULL) { |
559 | 433 | rc = htp_parse_ct_header(ct->value, &tx->request_content_type); |
560 | 433 | if (rc != HTP_OK) return rc; |
561 | 433 | } |
562 | | |
563 | | // Parse cookies. |
564 | 59.8k | if (tx->connp->cfg->parse_request_cookies) { |
565 | 59.8k | rc = htp_parse_cookies_v0(tx->connp); |
566 | 59.8k | if (rc != HTP_OK) return rc; |
567 | 59.8k | } |
568 | | |
569 | | // Parse authentication information. |
570 | 59.8k | if (tx->connp->cfg->parse_request_auth) { |
571 | 59.8k | rc = htp_parse_authorization(tx->connp); |
572 | 59.8k | if (rc == HTP_DECLINED) { |
573 | | // Don't fail the stream if an authorization header is invalid, just set a flag. |
574 | 0 | tx->flags |= HTP_AUTH_INVALID; |
575 | 59.8k | } else { |
576 | 59.8k | if (rc != HTP_OK) return rc; |
577 | 59.8k | } |
578 | 59.8k | } |
579 | | |
580 | | // Finalize sending raw header data. |
581 | 59.8k | rc = htp_connp_req_receiver_finalize_clear(tx->connp); |
582 | 59.8k | if (rc != HTP_OK) return rc; |
583 | | |
584 | | // Run hook REQUEST_HEADERS. |
585 | 59.8k | rc = htp_hook_run_all(tx->connp->cfg->hook_request_headers, tx); |
586 | 59.8k | if (rc != HTP_OK) return rc; |
587 | | |
588 | | // We still proceed if the request is invalid. |
589 | | |
590 | 59.8k | return HTP_OK; |
591 | 59.8k | } |
592 | | |
593 | 0 | htp_status_t htp_tx_req_process_body_data(htp_tx_t *tx, const void *data, size_t len) { |
594 | 0 | if ((tx == NULL) || (data == NULL)) return HTP_ERROR; |
595 | 0 | if (len == 0) return HTP_OK; |
596 | | |
597 | 0 | return htp_tx_req_process_body_data_ex(tx, data, len); |
598 | 0 | } |
599 | | |
600 | 103k | htp_status_t htp_tx_req_process_body_data_ex(htp_tx_t *tx, const void *data, size_t len) { |
601 | 103k | if (tx == NULL) return HTP_ERROR; |
602 | | |
603 | | // NULL data is allowed in this private function; it's |
604 | | // used to indicate the end of request body. |
605 | | |
606 | | // Send data to the callbacks. |
607 | | |
608 | 103k | htp_tx_data_t d; |
609 | 103k | d.tx = tx; |
610 | 103k | d.data = (unsigned char *) data; |
611 | 103k | d.len = len; |
612 | 103k | d.is_last = (data == NULL && len == 0); |
613 | | |
614 | 103k | switch(tx->request_content_encoding) { |
615 | 103k | case HTP_COMPRESSION_UNKNOWN: |
616 | 103k | case HTP_COMPRESSION_NONE: |
617 | | // When there's no decompression, request_entity_len. |
618 | | // is identical to request_message_len. |
619 | 103k | tx->request_entity_len += d.len; |
620 | 103k | htp_status_t rc = htp_req_run_hook_body_data(tx->connp, &d); |
621 | 103k | if (rc != HTP_OK) { |
622 | 0 | htp_log(tx->connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0, "Request body data callback returned error (%d)", rc); |
623 | 0 | return HTP_ERROR; |
624 | 0 | } |
625 | 103k | break; |
626 | | |
627 | 103k | case HTP_COMPRESSION_GZIP: |
628 | 0 | case HTP_COMPRESSION_DEFLATE: |
629 | 0 | case HTP_COMPRESSION_LZMA: |
630 | | // In severe memory stress these could be NULL |
631 | 0 | if (tx->connp->req_decompressor == NULL) |
632 | 0 | return HTP_ERROR; |
633 | | |
634 | | // Send data buffer to the decompressor. |
635 | 0 | htp_gzip_decompressor_decompress(tx->connp->req_decompressor, &d); |
636 | |
|
637 | 0 | if (data == NULL) { |
638 | | // Shut down the decompressor, if we used one. |
639 | 0 | htp_tx_req_destroy_decompressors(tx->connp); |
640 | 0 | } |
641 | 0 | break; |
642 | | |
643 | 0 | default: |
644 | | // Internal error. |
645 | 0 | htp_log(tx->connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0, |
646 | 0 | "[Internal Error] Invalid tx->request_content_encoding value: %d", |
647 | 0 | tx->request_content_encoding); |
648 | 0 | return HTP_ERROR; |
649 | 103k | } |
650 | | |
651 | 103k | return HTP_OK; |
652 | 103k | } |
653 | | |
654 | 0 | htp_status_t htp_tx_req_set_headers_clear(htp_tx_t *tx) { |
655 | 0 | if ((tx == NULL) || (tx->request_headers == NULL)) return HTP_ERROR; |
656 | | |
657 | 0 | htp_header_t *h = NULL; |
658 | 0 | for (size_t i = 0, n = htp_table_size(tx->request_headers); i < n; i++) { |
659 | 0 | h = htp_table_get_index(tx->request_headers, i, NULL); |
660 | 0 | bstr_free(h->name); |
661 | 0 | bstr_free(h->value); |
662 | 0 | free(h); |
663 | 0 | } |
664 | |
|
665 | 0 | htp_table_destroy(tx->request_headers); |
666 | |
|
667 | 0 | tx->request_headers = htp_table_create(32); |
668 | 0 | if (tx->request_headers == NULL) return HTP_ERROR; |
669 | | |
670 | 0 | return HTP_OK; |
671 | 0 | } |
672 | | |
673 | 0 | htp_status_t htp_tx_req_set_line(htp_tx_t *tx, const char *line, size_t line_len, enum htp_alloc_strategy_t alloc) { |
674 | 0 | if ((tx == NULL) || (line == NULL) || (line_len == 0)) return HTP_ERROR; |
675 | | |
676 | 0 | tx->request_line = copy_or_wrap_mem(line, line_len, alloc); |
677 | 0 | if (tx->request_line == NULL) return HTP_ERROR; |
678 | | |
679 | 0 | if (tx->connp->cfg->parse_request_line(tx->connp) != HTP_OK) return HTP_ERROR; |
680 | | |
681 | 0 | return HTP_OK; |
682 | 0 | } |
683 | | |
684 | 0 | void htp_tx_req_set_parsed_uri(htp_tx_t *tx, htp_uri_t *parsed_uri) { |
685 | 0 | if ((tx == NULL) || (parsed_uri == NULL)) return; |
686 | | |
687 | 0 | if (tx->parsed_uri != NULL) { |
688 | 0 | htp_uri_free(tx->parsed_uri); |
689 | 0 | } |
690 | |
|
691 | 0 | tx->parsed_uri = parsed_uri; |
692 | 0 | } |
693 | | |
694 | 0 | htp_status_t htp_tx_res_set_status_line(htp_tx_t *tx, const char *line, size_t line_len, enum htp_alloc_strategy_t alloc) { |
695 | 0 | if ((tx == NULL) || (line == NULL) || (line_len == 0)) return HTP_ERROR; |
696 | | |
697 | 0 | tx->response_line = copy_or_wrap_mem(line, line_len, alloc); |
698 | 0 | if (tx->response_line == NULL) return HTP_ERROR; |
699 | | |
700 | 0 | if (tx->connp->cfg->parse_response_line(tx->connp) != HTP_OK) return HTP_ERROR; |
701 | | |
702 | 0 | return HTP_OK; |
703 | 0 | } |
704 | | |
705 | 0 | void htp_tx_res_set_protocol_number(htp_tx_t *tx, int protocol_number) { |
706 | 0 | if (tx == NULL) return; |
707 | 0 | tx->response_protocol_number = protocol_number; |
708 | 0 | } |
709 | | |
710 | 0 | void htp_tx_res_set_status_code(htp_tx_t *tx, int status_code) { |
711 | 0 | if (tx == NULL) return; |
712 | 0 | tx->response_status_number = status_code; |
713 | 0 | } |
714 | | |
715 | 0 | htp_status_t htp_tx_res_set_status_message(htp_tx_t *tx, const char *msg, size_t msg_len, enum htp_alloc_strategy_t alloc) { |
716 | 0 | if ((tx == NULL) || (msg == NULL)) return HTP_ERROR; |
717 | | |
718 | 0 | if (tx->response_message != NULL) { |
719 | 0 | bstr_free(tx->response_message); |
720 | 0 | } |
721 | |
|
722 | 0 | tx->response_message = copy_or_wrap_mem(msg, msg_len, alloc); |
723 | 0 | if (tx->response_message == NULL) return HTP_ERROR; |
724 | | |
725 | 0 | return HTP_OK; |
726 | 0 | } |
727 | | |
728 | 47.3k | htp_status_t htp_tx_state_response_line(htp_tx_t *tx) { |
729 | 47.3k | if (tx == NULL) return HTP_ERROR; |
730 | | |
731 | | #if 0 |
732 | | // Commented-out until we determine which fields can be |
733 | | // unavailable in real-life. |
734 | | |
735 | | // Unless we're dealing with HTTP/0.9, check that |
736 | | // the minimum amount of data has been provided. |
737 | | if (tx->is_protocol_0_9 != 0) { |
738 | | if ((tx->response_protocol == NULL) || (tx->response_status_number == -1) || (tx->response_message == NULL)) { |
739 | | return HTP_ERROR; |
740 | | } |
741 | | } |
742 | | #endif |
743 | | |
744 | | // Is the response line valid? |
745 | 47.3k | if (tx->response_protocol_number == HTP_PROTOCOL_INVALID) { |
746 | 46.4k | htp_log(tx->connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, |
747 | 46.4k | "Invalid response line: invalid protocol"); |
748 | 46.4k | tx->flags |= HTP_STATUS_LINE_INVALID; |
749 | 46.4k | } |
750 | 47.3k | if ((tx->response_status_number == HTP_STATUS_INVALID) |
751 | 47.3k | || (tx->response_status_number < HTP_VALID_STATUS_MIN) |
752 | 47.3k | || (tx->response_status_number > HTP_VALID_STATUS_MAX)) { |
753 | 3.80k | htp_log(tx->connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, |
754 | 3.80k | "Invalid response line: invalid response status %d.", |
755 | 3.80k | tx->response_status_number); |
756 | 3.80k | tx->response_status_number = HTP_STATUS_INVALID; |
757 | 3.80k | tx->flags |= HTP_STATUS_LINE_INVALID; |
758 | 3.80k | } |
759 | | |
760 | | // Run hook HTP_RESPONSE_LINE |
761 | 47.3k | htp_status_t rc = htp_hook_run_all(tx->connp->cfg->hook_response_line, tx); |
762 | 47.3k | if (rc != HTP_OK) return rc; |
763 | | |
764 | 47.3k | return HTP_OK; |
765 | 47.3k | } |
766 | | |
767 | | htp_status_t htp_tx_res_set_header(htp_tx_t *tx, const char *name, size_t name_len, |
768 | 0 | const char *value, size_t value_len, enum htp_alloc_strategy_t alloc) { |
769 | 0 | if ((tx == NULL) || (name == NULL) || (value == NULL)) return HTP_ERROR; |
770 | | |
771 | | |
772 | 0 | htp_header_t *h = calloc(1, sizeof (htp_header_t)); |
773 | 0 | if (h == NULL) return HTP_ERROR; |
774 | | |
775 | 0 | h->name = copy_or_wrap_mem(name, name_len, alloc); |
776 | 0 | if (h->name == NULL) { |
777 | 0 | free(h); |
778 | 0 | return HTP_ERROR; |
779 | 0 | } |
780 | | |
781 | 0 | h->value = copy_or_wrap_mem(value, value_len, alloc); |
782 | 0 | if (h->value == NULL) { |
783 | 0 | bstr_free(h->name); |
784 | 0 | free(h); |
785 | 0 | return HTP_ERROR; |
786 | 0 | } |
787 | | |
788 | 0 | if (htp_table_add(tx->response_headers, h->name, h) != HTP_OK) { |
789 | 0 | bstr_free(h->name); |
790 | 0 | bstr_free(h->value); |
791 | 0 | free(h); |
792 | 0 | return HTP_ERROR; |
793 | 0 | } |
794 | | |
795 | 0 | return HTP_OK; |
796 | 0 | } |
797 | | |
798 | 0 | htp_status_t htp_tx_res_set_headers_clear(htp_tx_t *tx) { |
799 | 0 | if ((tx == NULL) || (tx->response_headers == NULL)) return HTP_ERROR; |
800 | | |
801 | 0 | htp_header_t *h = NULL; |
802 | 0 | for (size_t i = 0, n = htp_table_size(tx->response_headers); i < n; i++) { |
803 | 0 | h = htp_table_get_index(tx->response_headers, i, NULL); |
804 | 0 | bstr_free(h->name); |
805 | 0 | bstr_free(h->value); |
806 | 0 | free(h); |
807 | 0 | } |
808 | |
|
809 | 0 | htp_table_destroy(tx->response_headers); |
810 | |
|
811 | 0 | tx->response_headers = htp_table_create(32); |
812 | 0 | if (tx->response_headers == NULL) return HTP_ERROR; |
813 | | |
814 | 0 | return HTP_OK; |
815 | 0 | } |
816 | | |
817 | | /** \internal |
818 | | * |
819 | | * Clean up decompressor(s). |
820 | | * |
821 | | * @param[in] tx |
822 | | */ |
823 | 24.7k | static void htp_tx_res_destroy_decompressors(htp_connp_t *connp) { |
824 | 24.7k | htp_decompressor_t *comp = connp->out_decompressor; |
825 | 43.9k | while (comp) { |
826 | 19.2k | htp_decompressor_t *next = comp->next; |
827 | 19.2k | htp_gzip_decompressor_destroy(comp); |
828 | 19.2k | comp = next; |
829 | 19.2k | } |
830 | 24.7k | connp->out_decompressor = NULL; |
831 | 24.7k | } |
832 | | |
833 | 9.92k | static void htp_tx_req_destroy_decompressors(htp_connp_t *connp) { |
834 | 9.92k | htp_decompressor_t *comp = connp->req_decompressor; |
835 | 9.92k | while (comp) { |
836 | 0 | htp_decompressor_t *next = comp->next; |
837 | 0 | htp_gzip_decompressor_destroy(comp); |
838 | 0 | comp = next; |
839 | 0 | } |
840 | 9.92k | connp->req_decompressor = NULL; |
841 | 9.92k | } |
842 | | |
843 | 9.92k | void htp_connp_destroy_decompressors(htp_connp_t *connp) { |
844 | 9.92k | htp_tx_res_destroy_decompressors(connp); |
845 | 9.92k | htp_tx_req_destroy_decompressors(connp); |
846 | 9.92k | } |
847 | | |
848 | 1.19M | static htp_status_t htp_timer_track(int32_t *time_spent, struct timeval * after, struct timeval *before) { |
849 | 1.19M | if (after->tv_sec < before->tv_sec) { |
850 | 0 | return HTP_ERROR; |
851 | 1.19M | } else if (after->tv_sec == before->tv_sec) { |
852 | 1.19M | if (after->tv_usec < before->tv_usec) { |
853 | 0 | return HTP_ERROR; |
854 | 0 | } |
855 | 1.19M | *time_spent += after->tv_usec - before->tv_usec; |
856 | 1.19M | } else { |
857 | 37 | *time_spent += (after->tv_sec - before->tv_sec) * 1000000 + after->tv_usec - before->tv_usec; |
858 | 37 | } |
859 | 1.19M | return HTP_OK; |
860 | 1.19M | } |
861 | | |
862 | 0 | static htp_status_t htp_tx_req_process_body_data_decompressor_callback(htp_tx_data_t *d) { |
863 | 0 | if (d == NULL) return HTP_ERROR; |
864 | | |
865 | | #if HTP_DEBUG |
866 | | fprint_raw_data(stderr, __func__, d->data, d->len); |
867 | | #endif |
868 | | |
869 | | // Keep track of actual request body length. |
870 | 0 | d->tx->request_entity_len += d->len; |
871 | | |
872 | | // Invoke all callbacks. |
873 | 0 | htp_status_t rc = htp_req_run_hook_body_data(d->tx->connp, d); |
874 | 0 | if (rc != HTP_OK) return HTP_ERROR; |
875 | 0 | d->tx->connp->req_decompressor->nb_callbacks++; |
876 | 0 | if (d->tx->connp->req_decompressor->nb_callbacks % HTP_COMPRESSION_TIME_FREQ_TEST == 0) { |
877 | 0 | struct timeval after; |
878 | 0 | gettimeofday(&after, NULL); |
879 | | // sanity check for race condition if system time changed |
880 | 0 | if ( htp_timer_track(&d->tx->connp->req_decompressor->time_spent, &after, &d->tx->connp->req_decompressor->time_before) == HTP_OK) { |
881 | | // updates last tracked time |
882 | 0 | d->tx->connp->req_decompressor->time_before = after; |
883 | 0 | if (d->tx->connp->req_decompressor->time_spent > d->tx->connp->cfg->compression_time_limit ) { |
884 | 0 | htp_log(d->tx->connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0, |
885 | 0 | "Compression bomb: spent %"PRId32" us decompressing", |
886 | 0 | d->tx->connp->req_decompressor->time_spent); |
887 | 0 | d->tx->connp->req_decompressor->passthrough = 1; |
888 | 0 | } |
889 | 0 | } |
890 | |
|
891 | 0 | } |
892 | 0 | if (d->tx->request_entity_len > d->tx->connp->cfg->compression_bomb_limit && |
893 | 0 | d->tx->request_entity_len > HTP_COMPRESSION_BOMB_RATIO * d->tx->request_message_len) { |
894 | 0 | htp_log(d->tx->connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0, |
895 | 0 | "Compression bomb: decompressed %"PRId64" bytes out of %"PRId64, |
896 | 0 | d->tx->request_entity_len, d->tx->request_message_len); |
897 | 0 | return HTP_ERROR; |
898 | 0 | } |
899 | | |
900 | 0 | return HTP_OK; |
901 | 0 | } |
902 | | |
903 | 2.25M | static htp_status_t htp_tx_res_process_body_data_decompressor_callback(htp_tx_data_t *d) { |
904 | 2.25M | if (d == NULL) return HTP_ERROR; |
905 | | |
906 | | #if HTP_DEBUG |
907 | | fprint_raw_data(stderr, __func__, d->data, d->len); |
908 | | #endif |
909 | | |
910 | | // Keep track of actual response body length. |
911 | 2.25M | d->tx->response_entity_len += d->len; |
912 | | |
913 | | // Invoke all callbacks. |
914 | 2.25M | htp_status_t rc = htp_res_run_hook_body_data(d->tx->connp, d); |
915 | 2.25M | if (rc != HTP_OK) return HTP_ERROR; |
916 | 2.25M | d->tx->connp->out_decompressor->nb_callbacks++; |
917 | 2.25M | if (d->tx->connp->out_decompressor->nb_callbacks % HTP_COMPRESSION_TIME_FREQ_TEST == 0) { |
918 | 3.67k | struct timeval after; |
919 | 3.67k | gettimeofday(&after, NULL); |
920 | | // sanity check for race condition if system time changed |
921 | 3.67k | if ( htp_timer_track(&d->tx->connp->out_decompressor->time_spent, &after, &d->tx->connp->out_decompressor->time_before) == HTP_OK) { |
922 | | // updates last tracked time |
923 | 3.67k | d->tx->connp->out_decompressor->time_before = after; |
924 | 3.67k | if (d->tx->connp->out_decompressor->time_spent > d->tx->connp->cfg->compression_time_limit ) { |
925 | 2.75k | htp_log(d->tx->connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0, |
926 | 2.75k | "Compression bomb: spent %"PRId32" us decompressing", |
927 | 2.75k | d->tx->connp->out_decompressor->time_spent); |
928 | 2.75k | d->tx->connp->out_decompressor->passthrough = 1; |
929 | 2.75k | } |
930 | 3.67k | } |
931 | | |
932 | 3.67k | } |
933 | 2.25M | if (d->tx->response_entity_len > d->tx->connp->cfg->compression_bomb_limit && |
934 | 2.25M | d->tx->response_entity_len > HTP_COMPRESSION_BOMB_RATIO * d->tx->response_message_len) { |
935 | 1.01M | htp_log(d->tx->connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0, |
936 | 1.01M | "Compression bomb: decompressed %"PRId64" bytes out of %"PRId64, |
937 | 1.01M | d->tx->response_entity_len, d->tx->response_message_len); |
938 | 1.01M | return HTP_ERROR; |
939 | 1.01M | } |
940 | | |
941 | 1.23M | return HTP_OK; |
942 | 2.25M | } |
943 | | |
944 | 0 | htp_status_t htp_tx_res_process_body_data(htp_tx_t *tx, const void *data, size_t len) { |
945 | 0 | if ((tx == NULL) || (data == NULL)) return HTP_ERROR; |
946 | 0 | if (len == 0) return HTP_OK; |
947 | 0 | return htp_tx_res_process_body_data_ex(tx, data, len); |
948 | 0 | } |
949 | | |
950 | 1.41M | htp_status_t htp_tx_res_process_body_data_ex(htp_tx_t *tx, const void *data, size_t len) { |
951 | 1.41M | if (tx == NULL) return HTP_ERROR; |
952 | | |
953 | | // NULL data is allowed in this private function; it's |
954 | | // used to indicate the end of response body. |
955 | | |
956 | | #ifdef HTP_DEBUG |
957 | | fprint_raw_data(stderr, __func__, data, len); |
958 | | #endif |
959 | | |
960 | 1.41M | htp_tx_data_t d; |
961 | | |
962 | 1.41M | d.tx = tx; |
963 | 1.41M | d.data = (unsigned char *) data; |
964 | 1.41M | d.len = len; |
965 | 1.41M | d.is_last = 0; |
966 | | |
967 | | // Keep track of body size before decompression. |
968 | 1.41M | tx->response_message_len += d.len; |
969 | | |
970 | 1.41M | switch (tx->response_content_encoding_processing) { |
971 | 6.22k | case HTP_COMPRESSION_GZIP: |
972 | 6.22k | case HTP_COMPRESSION_DEFLATE: |
973 | 1.18M | case HTP_COMPRESSION_LZMA: |
974 | | // In severe memory stress these could be NULL |
975 | 1.18M | if (tx->connp->out_decompressor == NULL) { |
976 | 261 | if (data == NULL) { |
977 | | // we were already stopped on a gap finishing CL |
978 | 257 | return HTP_OK; |
979 | 257 | } |
980 | 4 | return HTP_ERROR; |
981 | 261 | } |
982 | | |
983 | 1.18M | struct timeval after; |
984 | 1.18M | gettimeofday(&tx->connp->out_decompressor->time_before, NULL); |
985 | | // Send data buffer to the decompressor. |
986 | 1.18M | tx->connp->out_decompressor->nb_callbacks=0; |
987 | 1.18M | htp_gzip_decompressor_decompress(tx->connp->out_decompressor, &d); |
988 | 1.18M | gettimeofday(&after, NULL); |
989 | | // sanity check for race condition if system time changed |
990 | 1.18M | if ( htp_timer_track(&tx->connp->out_decompressor->time_spent, &after, &tx->connp->out_decompressor->time_before) == HTP_OK) { |
991 | 1.18M | if ( tx->connp->out_decompressor->time_spent > tx->connp->cfg->compression_time_limit ) { |
992 | 502k | htp_log(tx->connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0, |
993 | 502k | "Compression bomb: spent %"PRId32" us decompressing", |
994 | 502k | tx->connp->out_decompressor->time_spent); |
995 | 502k | tx->connp->out_decompressor->passthrough = 1; |
996 | 502k | } |
997 | 1.18M | } |
998 | | |
999 | 1.18M | if (data == NULL) { |
1000 | | // Shut down the decompressor, if we used one. |
1001 | 3.08k | htp_tx_res_destroy_decompressors(tx->connp); |
1002 | 3.08k | } |
1003 | 1.18M | break; |
1004 | | |
1005 | 222k | case HTP_COMPRESSION_NONE: |
1006 | | // When there's no decompression, response_entity_len. |
1007 | | // is identical to response_message_len. |
1008 | 222k | tx->response_entity_len += d.len; |
1009 | | |
1010 | 222k | htp_status_t rc = htp_res_run_hook_body_data(tx->connp, &d); |
1011 | 222k | if (rc != HTP_OK) return HTP_ERROR; |
1012 | 222k | break; |
1013 | | |
1014 | 222k | default: |
1015 | | // Internal error. |
1016 | 1.23k | htp_log(tx->connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0, |
1017 | 1.23k | "[Internal Error] Invalid tx->response_content_encoding_processing value: %d", |
1018 | 1.23k | tx->response_content_encoding_processing); |
1019 | 1.23k | return HTP_ERROR; |
1020 | 0 | break; |
1021 | 1.41M | } |
1022 | | |
1023 | 1.40M | return HTP_OK; |
1024 | 1.41M | } |
1025 | | |
1026 | 91.9k | htp_status_t htp_tx_state_request_complete_partial(htp_tx_t *tx) { |
1027 | 91.9k | if (tx == NULL) return HTP_ERROR; |
1028 | | |
1029 | | // Finalize request body. |
1030 | 91.9k | if (htp_tx_req_has_body(tx)) { |
1031 | 0 | htp_status_t rc = htp_tx_req_process_body_data_ex(tx, NULL, 0); |
1032 | 0 | if (rc != HTP_OK) return rc; |
1033 | 0 | } |
1034 | | |
1035 | 91.9k | tx->request_progress = HTP_REQUEST_COMPLETE; |
1036 | | |
1037 | | // Run hook REQUEST_COMPLETE. |
1038 | 91.9k | htp_status_t rc = htp_hook_run_all(tx->connp->cfg->hook_request_complete, tx); |
1039 | 91.9k | if (rc != HTP_OK) return rc; |
1040 | 91.9k | rc = htp_connp_req_receiver_finalize_clear(tx->connp); |
1041 | 91.9k | if (rc != HTP_OK) return rc; |
1042 | | |
1043 | | // Clean-up. |
1044 | 91.9k | if (tx->connp->put_file != NULL) { |
1045 | 0 | bstr_free(tx->connp->put_file->filename); |
1046 | 0 | free(tx->connp->put_file); |
1047 | 0 | tx->connp->put_file = NULL; |
1048 | 0 | } |
1049 | | |
1050 | 91.9k | return HTP_OK; |
1051 | 91.9k | } |
1052 | | |
1053 | 91.9k | htp_status_t htp_tx_state_request_complete(htp_tx_t *tx) { |
1054 | 91.9k | if (tx == NULL) return HTP_ERROR; |
1055 | | |
1056 | 91.9k | if (tx->request_progress != HTP_REQUEST_COMPLETE) { |
1057 | 91.9k | htp_status_t rc = htp_tx_state_request_complete_partial(tx); |
1058 | 91.9k | if (rc != HTP_OK) return rc; |
1059 | 91.9k | } |
1060 | | |
1061 | | // Make a copy of the connection parser pointer, so that |
1062 | | // we don't have to reference it via tx, which may be |
1063 | | // destroyed later. |
1064 | 91.9k | htp_connp_t *connp = tx->connp; |
1065 | | |
1066 | | // Determine what happens next, and remove this transaction from the parser. |
1067 | 91.9k | if (tx->is_protocol_0_9) { |
1068 | 3.17k | connp->in_state = htp_connp_REQ_IGNORE_DATA_AFTER_HTTP_0_9; |
1069 | 88.8k | } else { |
1070 | 88.8k | connp->in_state = htp_connp_REQ_IDLE; |
1071 | 88.8k | } |
1072 | | |
1073 | | // Check if the entire transaction is complete. This call may |
1074 | | // destroy the transaction, if auto-destroy is enabled. |
1075 | 91.9k | htp_tx_finalize(tx); |
1076 | | |
1077 | | // At this point, tx may no longer be valid. |
1078 | | |
1079 | 91.9k | connp->in_tx = NULL; |
1080 | | |
1081 | 91.9k | return HTP_OK; |
1082 | 91.9k | } |
1083 | | |
1084 | 64.9k | htp_status_t htp_tx_state_request_start(htp_tx_t *tx) { |
1085 | 64.9k | if (tx == NULL) return HTP_ERROR; |
1086 | | |
1087 | | // Run hook REQUEST_START. |
1088 | 64.9k | htp_status_t rc = htp_hook_run_all(tx->connp->cfg->hook_request_start, tx); |
1089 | 64.9k | if (rc != HTP_OK) return rc; |
1090 | | |
1091 | | // Change state into request line parsing. |
1092 | 64.9k | tx->connp->in_state = htp_connp_REQ_LINE; |
1093 | 64.9k | tx->connp->in_tx->request_progress = HTP_REQUEST_LINE; |
1094 | | |
1095 | 64.9k | return HTP_OK; |
1096 | 64.9k | } |
1097 | | |
1098 | 61.1k | htp_status_t htp_tx_state_request_headers(htp_tx_t *tx) { |
1099 | 61.1k | if (tx == NULL) return HTP_ERROR; |
1100 | | |
1101 | | // If we're in HTP_REQ_HEADERS that means that this is the |
1102 | | // first time we're processing headers in a request. Otherwise, |
1103 | | // we're dealing with trailing headers. |
1104 | 61.1k | if (tx->request_progress > HTP_REQUEST_HEADERS) { |
1105 | | // Request trailers. |
1106 | | |
1107 | | // Run hook HTP_REQUEST_TRAILER. |
1108 | 1.26k | htp_status_t rc = htp_hook_run_all(tx->connp->cfg->hook_request_trailer, tx); |
1109 | 1.26k | if (rc != HTP_OK) return rc; |
1110 | | |
1111 | | // Finalize sending raw header data. |
1112 | 1.26k | rc = htp_connp_req_receiver_finalize_clear(tx->connp); |
1113 | 1.26k | if (rc != HTP_OK) return rc; |
1114 | | |
1115 | | // Completed parsing this request; finalize it now. |
1116 | 1.26k | tx->connp->in_state = htp_connp_REQ_FINALIZE; |
1117 | 59.8k | } else if (tx->request_progress >= HTP_REQUEST_LINE) { |
1118 | | // Request headers. |
1119 | | |
1120 | | // Did this request arrive in multiple data chunks? |
1121 | 59.8k | if (tx->connp->in_chunk_count != tx->connp->in_chunk_request_index) { |
1122 | 3.09k | tx->flags |= HTP_MULTI_PACKET_HEAD; |
1123 | 3.09k | } |
1124 | | |
1125 | 59.8k | htp_status_t rc = htp_tx_process_request_headers(tx); |
1126 | 59.8k | if (rc != HTP_OK) return rc; |
1127 | | |
1128 | 59.8k | tx->connp->in_state = htp_connp_REQ_CONNECT_CHECK; |
1129 | 59.8k | } else { |
1130 | 0 | htp_log(tx->connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "[Internal Error] Invalid tx progress: %d", tx->request_progress); |
1131 | |
|
1132 | 0 | return HTP_ERROR; |
1133 | 0 | } |
1134 | | |
1135 | 61.1k | return HTP_OK; |
1136 | 61.1k | } |
1137 | | |
1138 | 64.4k | htp_status_t htp_tx_state_request_line(htp_tx_t *tx) { |
1139 | 64.4k | if (tx == NULL) return HTP_ERROR; |
1140 | | |
1141 | | // Determine how to process the request URI. |
1142 | | |
1143 | 64.4k | if (tx->request_method_number == HTP_M_CONNECT) { |
1144 | | // When CONNECT is used, the request URI contains an authority string. |
1145 | 3.34k | if (htp_parse_uri_hostport(tx->connp, tx->request_uri, tx->parsed_uri_raw) != HTP_OK) { |
1146 | 4 | return HTP_ERROR; |
1147 | 4 | } |
1148 | 61.1k | } else { |
1149 | | // Parse the request URI into htp_tx_t::parsed_uri_raw. |
1150 | 61.1k | if (htp_parse_uri(tx->request_uri, &(tx->parsed_uri_raw)) != HTP_OK) { |
1151 | 0 | return HTP_ERROR; |
1152 | 0 | } |
1153 | 61.1k | } |
1154 | | |
1155 | | // Build htp_tx_t::parsed_uri, but only if it was not explicitly set already. |
1156 | 64.4k | if (tx->parsed_uri == NULL) { |
1157 | 64.4k | tx->parsed_uri = htp_uri_alloc(); |
1158 | 64.4k | if (tx->parsed_uri == NULL) return HTP_ERROR; |
1159 | | |
1160 | | // Keep the original URI components, but create a copy which we can normalize and use internally. |
1161 | 64.4k | if (htp_normalize_parsed_uri(tx, tx->parsed_uri_raw, tx->parsed_uri) != HTP_OK) { |
1162 | 0 | return HTP_ERROR; |
1163 | 0 | } |
1164 | 64.4k | } |
1165 | | |
1166 | | // Check parsed_uri hostname. |
1167 | 64.4k | if (tx->parsed_uri->hostname != NULL) { |
1168 | 19.7k | if (htp_validate_hostname(tx->parsed_uri->hostname) == 0) { |
1169 | 17.5k | tx->flags |= HTP_HOSTU_INVALID; |
1170 | 17.5k | } |
1171 | 19.7k | } |
1172 | | |
1173 | | // Run hook REQUEST_URI_NORMALIZE. |
1174 | 64.4k | htp_status_t rc = htp_hook_run_all(tx->connp->cfg->hook_request_uri_normalize, tx); |
1175 | 64.4k | if (rc != HTP_OK) return rc; |
1176 | | |
1177 | | |
1178 | | // Run hook REQUEST_LINE. |
1179 | 64.4k | rc = htp_hook_run_all(tx->connp->cfg->hook_request_line, tx); |
1180 | 64.4k | if (rc != HTP_OK) return rc; |
1181 | | |
1182 | | // Move on to the next phase. |
1183 | 64.4k | tx->connp->in_state = htp_connp_REQ_PROTOCOL; |
1184 | | |
1185 | 64.4k | return HTP_OK; |
1186 | 64.4k | } |
1187 | | |
1188 | 0 | htp_status_t htp_tx_state_response_complete(htp_tx_t *tx) { |
1189 | 0 | if (tx == NULL) return HTP_ERROR; |
1190 | 0 | return htp_tx_state_response_complete_ex(tx, 1 /* hybrid mode */); |
1191 | 0 | } |
1192 | | |
1193 | 126k | htp_status_t htp_tx_finalize(htp_tx_t *tx) { |
1194 | 126k | if (tx == NULL) return HTP_ERROR; |
1195 | | |
1196 | 126k | if (!htp_tx_is_complete(tx)) return HTP_OK; |
1197 | | |
1198 | | // Run hook TRANSACTION_COMPLETE. |
1199 | 34.1k | htp_status_t rc = htp_hook_run_all(tx->connp->cfg->hook_transaction_complete, tx); |
1200 | 34.1k | if (rc != HTP_OK) return rc; |
1201 | | |
1202 | | // In streaming processing, we destroy the transaction because it will not be needed any more. |
1203 | 34.1k | if (tx->connp->cfg->tx_auto_destroy) { |
1204 | 0 | htp_tx_destroy(tx); |
1205 | 0 | } |
1206 | | |
1207 | 34.1k | return HTP_OK; |
1208 | 34.1k | } |
1209 | | |
1210 | 37.1k | htp_status_t htp_tx_state_response_complete_ex(htp_tx_t *tx, int hybrid_mode) { |
1211 | 37.1k | if (tx == NULL) return HTP_ERROR; |
1212 | | |
1213 | 37.1k | if (tx->response_progress != HTP_RESPONSE_COMPLETE) { |
1214 | 34.6k | tx->response_progress = HTP_RESPONSE_COMPLETE; |
1215 | | |
1216 | | // Run the last RESPONSE_BODY_DATA HOOK, but only if there was a response body present. |
1217 | 34.6k | if (tx->response_transfer_coding != HTP_CODING_NO_BODY) { |
1218 | 13.2k | htp_tx_res_process_body_data_ex(tx, NULL, 0); |
1219 | 13.2k | } |
1220 | | |
1221 | | // Run hook RESPONSE_COMPLETE. |
1222 | 34.6k | htp_status_t rc = htp_hook_run_all(tx->connp->cfg->hook_response_complete, tx); |
1223 | 34.6k | if (rc != HTP_OK) return rc; |
1224 | | |
1225 | | // Clear the data receivers hook if any |
1226 | 34.6k | rc = htp_connp_res_receiver_finalize_clear(tx->connp); |
1227 | 34.6k | if (rc != HTP_OK) return rc; |
1228 | 34.6k | } |
1229 | | |
1230 | 37.1k | if (!hybrid_mode) { |
1231 | | // Check if the inbound parser is waiting on us. If it is, that means that |
1232 | | // there might be request data that the inbound parser hasn't consumed yet. |
1233 | | // If we don't stop parsing we might encounter a response without a request, |
1234 | | // which is why we want to return straight away before processing any data. |
1235 | | // |
1236 | | // This situation will occur any time the parser needs to see the server |
1237 | | // respond to a particular situation before it can decide how to proceed. For |
1238 | | // example, when a CONNECT is sent, different paths are used when it is accepted |
1239 | | // and when it is not accepted. |
1240 | | // |
1241 | | // It is not enough to check only in_status here. Because of pipelining, it's possible |
1242 | | // that many inbound transactions have been processed, and that the parser is |
1243 | | // waiting on a response that we have not seen yet. |
1244 | 37.1k | if ((tx->connp->in_status == HTP_STREAM_DATA_OTHER) && (tx->connp->in_tx == tx->connp->out_tx)) { |
1245 | 2.14k | return HTP_DATA_OTHER; |
1246 | 2.14k | } |
1247 | | |
1248 | | // Do we have a signal to yield to inbound processing at |
1249 | | // the end of the next transaction? |
1250 | 34.9k | if (tx->connp->out_data_other_at_tx_end) { |
1251 | | // We do. Let's yield then. |
1252 | 341 | tx->connp->out_data_other_at_tx_end = 0; |
1253 | 341 | return HTP_DATA_OTHER; |
1254 | 341 | } |
1255 | 34.9k | } |
1256 | | |
1257 | | // Make a copy of the connection parser pointer, so that |
1258 | | // we don't have to reference it via tx, which may be destroyed later. |
1259 | 34.6k | htp_connp_t *connp = tx->connp; |
1260 | | |
1261 | | // Finalize the transaction. This may call may destroy the transaction, if auto-destroy is enabled. |
1262 | 34.6k | htp_status_t rc = htp_tx_finalize(tx); |
1263 | 34.6k | if (rc != HTP_OK) return rc; |
1264 | | |
1265 | | // Disconnect transaction from the parser. |
1266 | 34.6k | connp->out_tx = NULL; |
1267 | | |
1268 | 34.6k | connp->out_state = htp_connp_RES_IDLE; |
1269 | | |
1270 | 34.6k | return HTP_OK; |
1271 | 34.6k | } |
1272 | | |
1273 | | /** |
1274 | | * @internal |
1275 | | * @brief split input into tokens separated by "seps" |
1276 | | * @param seps nul-terminated string: each character is a separator |
1277 | | */ |
1278 | | static int get_token(const unsigned char *in, size_t in_len, const char *seps, |
1279 | | unsigned char **ret_tok_ptr, size_t *ret_tok_len) |
1280 | 11.9k | { |
1281 | | #if HTP_DEBUG |
1282 | | fprintf(stderr, "INPUT %"PRIuMAX, (uintmax_t)in_len); |
1283 | | fprint_raw_data(stderr, __func__, in, in_len); |
1284 | | #endif |
1285 | | |
1286 | 11.9k | size_t i = 0; |
1287 | | |
1288 | | /* skip leading 'separators' */ |
1289 | 15.1k | while (i < in_len) |
1290 | 15.0k | { |
1291 | 15.0k | int match = 0; |
1292 | 39.4k | for (const char *s = seps; *s != '\0'; s++) { |
1293 | 27.5k | if (in[i] == *s) { |
1294 | 3.18k | match++; |
1295 | 3.18k | break; |
1296 | 3.18k | } |
1297 | 27.5k | } |
1298 | 15.0k | if (!match) |
1299 | 11.8k | break; |
1300 | | |
1301 | 3.18k | i++; |
1302 | 3.18k | } |
1303 | 11.9k | if (i >= in_len) |
1304 | 79 | return 0; |
1305 | | |
1306 | 11.8k | in += i; |
1307 | 11.8k | in_len -= i; |
1308 | | |
1309 | | #if HTP_DEBUG |
1310 | | fprintf(stderr, "INPUT (POST SEP STRIP) %"PRIuMAX, (uintmax_t)in_len); |
1311 | | fprint_raw_data(stderr, __func__, in, in_len); |
1312 | | #endif |
1313 | | |
1314 | 1.44M | for (i = 0; i < in_len; i++) |
1315 | 1.43M | { |
1316 | 4.31M | for (const char *s = seps; *s != '\0'; s++) { |
1317 | 2.87M | if (in[i] == *s) { |
1318 | 4.76k | *ret_tok_ptr = (unsigned char *)in; |
1319 | 4.76k | *ret_tok_len = i; |
1320 | 4.76k | return 1; |
1321 | 4.76k | } |
1322 | 2.87M | } |
1323 | 1.43M | } |
1324 | | |
1325 | 7.07k | *ret_tok_ptr = (unsigned char *)in; |
1326 | 7.07k | *ret_tok_len = in_len; |
1327 | 7.07k | return 1; |
1328 | 11.8k | } |
1329 | | |
1330 | 25.4k | htp_status_t htp_tx_state_response_headers(htp_tx_t *tx) { |
1331 | 25.4k | if (tx == NULL) return HTP_ERROR; |
1332 | | |
1333 | | // Check for compression. |
1334 | | |
1335 | | // Determine content encoding. |
1336 | | |
1337 | 25.4k | int ce_multi_comp = 0; |
1338 | 25.4k | tx->response_content_encoding = HTP_COMPRESSION_NONE; |
1339 | 25.4k | htp_header_t *ce = htp_table_get_c(tx->response_headers, "content-encoding"); |
1340 | 25.4k | if (ce != NULL) { |
1341 | | /* fast paths: regular gzip and friends */ |
1342 | 17.1k | if ((bstr_cmp_c_nocasenorzero(ce->value, "gzip") == 0) || |
1343 | 17.1k | (bstr_cmp_c_nocasenorzero(ce->value, "x-gzip") == 0)) { |
1344 | 986 | tx->response_content_encoding = HTP_COMPRESSION_GZIP; |
1345 | 16.1k | } else if ((bstr_cmp_c_nocasenorzero(ce->value, "deflate") == 0) || |
1346 | 16.1k | (bstr_cmp_c_nocasenorzero(ce->value, "x-deflate") == 0)) { |
1347 | 0 | tx->response_content_encoding = HTP_COMPRESSION_DEFLATE; |
1348 | 16.1k | } else if (bstr_cmp_c_nocasenorzero(ce->value, "lzma") == 0) { |
1349 | 9.27k | tx->response_content_encoding = HTP_COMPRESSION_LZMA; |
1350 | 9.27k | } else if (bstr_cmp_c_nocasenorzero(ce->value, "inflate") == 0) { |
1351 | | // ignore |
1352 | 6.92k | } else { |
1353 | | /* exceptional cases: enter slow path */ |
1354 | 6.92k | ce_multi_comp = 1; |
1355 | 6.92k | } |
1356 | 17.1k | } |
1357 | | |
1358 | | // Configure decompression, if enabled in the configuration. |
1359 | 25.4k | if (tx->connp->cfg->response_decompression_enabled) { |
1360 | 25.4k | tx->response_content_encoding_processing = tx->response_content_encoding; |
1361 | 25.4k | } else { |
1362 | 0 | tx->response_content_encoding_processing = HTP_COMPRESSION_NONE; |
1363 | 0 | ce_multi_comp = 0; |
1364 | 0 | } |
1365 | | |
1366 | | // Finalize sending raw header data. |
1367 | 25.4k | htp_status_t rc = htp_connp_res_receiver_finalize_clear(tx->connp); |
1368 | 25.4k | if (rc != HTP_OK) return rc; |
1369 | | |
1370 | | // Run hook RESPONSE_HEADERS. |
1371 | 25.4k | rc = htp_hook_run_all(tx->connp->cfg->hook_response_headers, tx); |
1372 | 25.4k | if (rc != HTP_OK) return rc; |
1373 | | |
1374 | | // Initialize the decompression engine as necessary. We can deal with three |
1375 | | // scenarios: |
1376 | | // |
1377 | | // 1. Decompression is enabled, compression indicated in headers, and we decompress. |
1378 | | // |
1379 | | // 2. As above, but the user disables decompression by setting response_content_encoding |
1380 | | // to COMPRESSION_NONE. |
1381 | | // |
1382 | | // 3. Decompression is disabled and we do not attempt to enable it, but the user |
1383 | | // forces decompression by setting response_content_encoding to one of the |
1384 | | // supported algorithms. |
1385 | 25.4k | if ((tx->response_content_encoding_processing == HTP_COMPRESSION_GZIP) || |
1386 | 25.4k | (tx->response_content_encoding_processing == HTP_COMPRESSION_DEFLATE) || |
1387 | 25.4k | (tx->response_content_encoding_processing == HTP_COMPRESSION_LZMA) || |
1388 | 25.4k | ce_multi_comp) |
1389 | 17.1k | { |
1390 | 17.1k | if (tx->connp->out_decompressor != NULL) { |
1391 | 11.7k | htp_tx_res_destroy_decompressors(tx->connp); |
1392 | 11.7k | } |
1393 | | |
1394 | | /* normal case */ |
1395 | 17.1k | if (!ce_multi_comp) { |
1396 | 10.2k | tx->connp->out_decompressor = htp_gzip_decompressor_create(tx->connp, tx->response_content_encoding_processing); |
1397 | 10.2k | if (tx->connp->out_decompressor == NULL) return HTP_ERROR; |
1398 | | |
1399 | 10.2k | tx->connp->out_decompressor->callback = htp_tx_res_process_body_data_decompressor_callback; |
1400 | | |
1401 | | /* multiple ce value case */ |
1402 | 10.2k | } else { |
1403 | 6.92k | int layers = 0; |
1404 | 6.92k | htp_decompressor_t *comp = NULL; |
1405 | 6.92k | int nblzma = 0; |
1406 | | |
1407 | 6.92k | uint8_t *tok = NULL; |
1408 | 6.92k | size_t tok_len = 0; |
1409 | | |
1410 | 6.92k | uint8_t *input = bstr_ptr(ce->value); |
1411 | 6.92k | size_t input_len = bstr_len(ce->value); |
1412 | | |
1413 | | #if HTP_DEBUG |
1414 | | fprintf(stderr, "INPUT %"PRIuMAX, (uintmax_t)input_len); |
1415 | | fprint_raw_data(stderr, __func__, input, input_len); |
1416 | | #endif |
1417 | | |
1418 | 12.3k | while (input_len > 0 && |
1419 | 12.3k | get_token(input, input_len, ", ", &tok, &tok_len)) |
1420 | 11.8k | { |
1421 | | #if HTP_DEBUG |
1422 | | fprintf(stderr, "TOKEN %"PRIuMAX, (uintmax_t)tok_len); |
1423 | | fprint_raw_data(stderr, __func__, tok, tok_len); |
1424 | | #endif |
1425 | 11.8k | enum htp_content_encoding_t cetype = HTP_COMPRESSION_NONE; |
1426 | | |
1427 | | /* check depth limit (0 means no limit) */ |
1428 | 11.8k | if ((tx->connp->cfg->response_decompression_layer_limit != 0) && |
1429 | 11.8k | ((++layers) > tx->connp->cfg->response_decompression_layer_limit)) |
1430 | 516 | { |
1431 | 516 | htp_log(tx->connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, |
1432 | 516 | "Too many response content encoding layers"); |
1433 | 516 | break; |
1434 | 516 | } |
1435 | | |
1436 | 11.3k | nblzma++; |
1437 | 11.3k | if (bstr_util_mem_index_of_c_nocase(tok, tok_len, "gzip") != -1) { |
1438 | 5.85k | if (!(bstr_util_cmp_mem(tok, tok_len, "gzip", 4) == 0 || |
1439 | 5.85k | bstr_util_cmp_mem(tok, tok_len, "x-gzip", 6) == 0)) { |
1440 | 5.20k | htp_log(tx->connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, |
1441 | 5.20k | "C-E gzip has abnormal value"); |
1442 | 5.20k | } |
1443 | 5.85k | cetype = HTP_COMPRESSION_GZIP; |
1444 | 5.85k | } else if (bstr_util_mem_index_of_c_nocase(tok, tok_len, "deflate") != -1) { |
1445 | 0 | if (!(bstr_util_cmp_mem(tok, tok_len, "deflate", 7) == 0 || |
1446 | 0 | bstr_util_cmp_mem(tok, tok_len, "x-deflate", 9) == 0)) { |
1447 | 0 | htp_log(tx->connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, |
1448 | 0 | "C-E deflate has abnormal value"); |
1449 | 0 | } |
1450 | 0 | cetype = HTP_COMPRESSION_DEFLATE; |
1451 | 5.47k | } else if (bstr_util_cmp_mem(tok, tok_len, "lzma", 4) == 0) { |
1452 | 3.23k | cetype = HTP_COMPRESSION_LZMA; |
1453 | 3.23k | if (nblzma > tx->connp->cfg->response_lzma_layer_limit) { |
1454 | 135 | htp_log(tx->connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0, |
1455 | 135 | "Compression bomb: multiple encoding with lzma"); |
1456 | 135 | break; |
1457 | 135 | } |
1458 | 3.23k | } else if (bstr_util_cmp_mem(tok, tok_len, "inflate", 7) == 0 || bstr_util_cmp_mem(tok, tok_len, "none", 4) == 0) { |
1459 | 6 | cetype = HTP_COMPRESSION_NONE; |
1460 | 2.22k | } else { |
1461 | | // continue |
1462 | 2.22k | htp_log(tx->connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, |
1463 | 2.22k | "C-E unknown setting"); |
1464 | 2.22k | } |
1465 | | |
1466 | 11.1k | if (cetype != HTP_COMPRESSION_NONE) { |
1467 | 8.95k | if (comp == NULL) { |
1468 | 5.28k | tx->response_content_encoding_processing = cetype; |
1469 | 5.28k | tx->connp->out_decompressor = htp_gzip_decompressor_create(tx->connp, tx->response_content_encoding_processing); |
1470 | 5.28k | if (tx->connp->out_decompressor == NULL) { |
1471 | 0 | return HTP_ERROR; |
1472 | 0 | } |
1473 | 5.28k | tx->connp->out_decompressor->callback = htp_tx_res_process_body_data_decompressor_callback; |
1474 | 5.28k | comp = tx->connp->out_decompressor; |
1475 | 5.28k | } else { |
1476 | 3.67k | comp->next = htp_gzip_decompressor_create(tx->connp, cetype); |
1477 | 3.67k | if (comp->next == NULL) { |
1478 | 0 | return HTP_ERROR; |
1479 | 0 | } |
1480 | 3.67k | comp->next->callback = htp_tx_res_process_body_data_decompressor_callback; |
1481 | 3.67k | comp = comp->next; |
1482 | 3.67k | } |
1483 | 8.95k | } |
1484 | | |
1485 | 11.1k | if ((tok_len + 1) >= input_len) |
1486 | 5.77k | break; |
1487 | 5.41k | input += (tok_len + 1); |
1488 | 5.41k | input_len -= (tok_len + 1); |
1489 | 5.41k | } |
1490 | 6.92k | } |
1491 | 17.1k | } else if (tx->response_content_encoding_processing != HTP_COMPRESSION_NONE) { |
1492 | 0 | return HTP_ERROR; |
1493 | 0 | } |
1494 | | |
1495 | 25.4k | return HTP_OK; |
1496 | 25.4k | } |
1497 | | |
1498 | 35.1k | htp_status_t htp_tx_state_response_start(htp_tx_t *tx) { |
1499 | 35.1k | if (tx == NULL) return HTP_ERROR; |
1500 | | |
1501 | 35.1k | tx->connp->out_tx = tx; |
1502 | | |
1503 | | // Run hook RESPONSE_START. |
1504 | 35.1k | htp_status_t rc = htp_hook_run_all(tx->connp->cfg->hook_response_start, tx); |
1505 | 35.1k | if (rc != HTP_OK) return rc; |
1506 | | |
1507 | | // Change state into response line parsing, except if we're following |
1508 | | // a HTTP/0.9 request (no status line or response headers). |
1509 | 35.1k | if (tx->is_protocol_0_9) { |
1510 | 45 | tx->response_transfer_coding = HTP_CODING_IDENTITY; |
1511 | 45 | tx->response_content_encoding_processing = HTP_COMPRESSION_NONE; |
1512 | 45 | tx->response_progress = HTP_RESPONSE_BODY; |
1513 | 45 | tx->connp->out_state = htp_connp_RES_BODY_IDENTITY_STREAM_CLOSE; |
1514 | 45 | tx->connp->out_body_data_left = -1; |
1515 | 35.0k | } else { |
1516 | 35.0k | tx->connp->out_state = htp_connp_RES_LINE; |
1517 | 35.0k | tx->response_progress = HTP_RESPONSE_LINE; |
1518 | 35.0k | } |
1519 | | |
1520 | | /* If at this point we have no method and no uri and our status |
1521 | | * is still htp_connp_REQ_LINE, we likely have timed out request |
1522 | | * or a overly long request */ |
1523 | 35.1k | if (tx->request_method == HTP_M_UNKNOWN && tx->request_uri == NULL && tx->connp->in_state == htp_connp_REQ_LINE) { |
1524 | 1.13k | htp_log(tx->connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Request line incomplete"); |
1525 | 1.13k | } |
1526 | | |
1527 | 35.1k | return HTP_OK; |
1528 | 35.1k | } |
1529 | | |
1530 | | /** |
1531 | | * Register callback for the transaction-specific REQUEST_BODY_DATA hook. |
1532 | | * |
1533 | | * @param[in] tx |
1534 | | * @param[in] callback_fn |
1535 | | */ |
1536 | 0 | void htp_tx_register_request_body_data(htp_tx_t *tx, int (*callback_fn)(htp_tx_data_t *)) { |
1537 | 0 | if ((tx == NULL) || (callback_fn == NULL)) return; |
1538 | 0 | htp_hook_register(&tx->hook_request_body_data, (htp_callback_fn_t) callback_fn); |
1539 | 0 | } |
1540 | | |
1541 | | /** |
1542 | | * Register callback for the transaction-specific RESPONSE_BODY_DATA hook. |
1543 | | * |
1544 | | * @param[in] tx |
1545 | | * @param[in] callback_fn |
1546 | | */ |
1547 | 0 | void htp_tx_register_response_body_data(htp_tx_t *tx, int (*callback_fn)(htp_tx_data_t *)) { |
1548 | 0 | if ((tx == NULL) || (callback_fn == NULL)) return; |
1549 | 0 | htp_hook_register(&tx->hook_response_body_data, (htp_callback_fn_t) callback_fn); |
1550 | 0 | } |
1551 | | |
1552 | 126k | int htp_tx_is_complete(htp_tx_t *tx) { |
1553 | 126k | if (tx == NULL) return -1; |
1554 | | |
1555 | | // A transaction is considered complete only when both the request and |
1556 | | // response are complete. (Sometimes a complete response can be seen |
1557 | | // even while the request is ongoing.) |
1558 | 126k | if ((tx->request_progress != HTP_REQUEST_COMPLETE) || (tx->response_progress != HTP_RESPONSE_COMPLETE)) { |
1559 | 92.4k | return 0; |
1560 | 92.4k | } else { |
1561 | 34.1k | return 1; |
1562 | 34.1k | } |
1563 | 126k | } |