1 /*
2 * ModSecurity for Apache 2.x, http://www.modsecurity.org/
3 * Copyright (c) 2004-2013 Trustwave Holdings, Inc. (http://www.trustwave.com/)
4 *
5 * You may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * If any of the files related to licensing are missing or if you have any
11 * other questions related to licensing please contact Trustwave Holdings, Inc.
12 * directly using the email address security@modsecurity.org.
13 */
14
15 #include "modsecurity.h"
16 #include <ctype.h>
17 #include <sys/stat.h>
18
19 #include "msc_multipart.h"
20 #include "msc_util.h"
21 #include "msc_parsers.h"
22
validate_quotes(modsec_rec * msr,char * data)23 void validate_quotes(modsec_rec *msr, char *data) {
24 int i, len;
25
26 if(msr == NULL)
27 return;
28
29 if(msr->mpd == NULL)
30 return;
31
32 if(data == NULL)
33 return;
34
35 len = strlen(data);
36
37 for(i = 0; i < len; i++) {
38
39 if(data[i] == '\'') {
40 if (msr->txcfg->debuglog_level >= 9) {
41 msr_log(msr, 9, "Multipart: Invalid quoting detected: %s length %d bytes",
42 log_escape_nq(msr->mp, data), len);
43 }
44 msr->mpd->flag_invalid_quoting = 1;
45 }
46 }
47 }
48
49 #if 0
50 static char *multipart_construct_filename(modsec_rec *msr) {
51 char c, *p, *q = msr->mpd->mpp->filename;
52 char *filename;
53
54 /* find the last backward slash and consider the
55 * filename to be only what's right from it
56 */
57 p = strrchr(q, '\\');
58 if (p != NULL) q = p + 1;
59
60 /* do the same for the forward slash */
61 p = strrchr(q, '/');
62 if (p != NULL) q = p + 1;
63
64 /* allow letters, digits and dots, replace
65 * everything else with underscores
66 */
67 p = filename = apr_pstrdup(msr->mp, q);
68 while((c = *p) != 0) {
69 if (!( isalnum(c) || (c == '.') )) *p = '_';
70 p++;
71 }
72
73 return filename;
74 }
75 #endif
76
77 /**
78 *
79 */
multipart_parse_content_disposition(modsec_rec * msr,char * c_d_value)80 static int multipart_parse_content_disposition(modsec_rec *msr, char *c_d_value) {
81 char *p = NULL, *t = NULL;
82
83 /* accept only what we understand */
84 if (strncmp(c_d_value, "form-data", 9) != 0) {
85 return -1;
86 }
87
88 /* see if there are any other parts to parse */
89
90 p = c_d_value + 9;
91 while((*p == '\t') || (*p == ' ')) p++;
92 if (*p == '\0') return 1; /* this is OK */
93
94 if (*p != ';') return -2;
95 p++;
96
97 /* parse the appended parts */
98
99 while(*p != '\0') {
100 char *name = NULL, *value = NULL, *start = NULL;
101
102 /* go over the whitespace */
103 while((*p == '\t') || (*p == ' ')) p++;
104 if (*p == '\0') return -3;
105
106 start = p;
107 while((*p != '\0') && (*p != '=') && (*p != '\t') && (*p != ' ')) p++;
108 if (*p == '\0') return -4;
109
110 name = apr_pstrmemdup(msr->mp, start, (p - start));
111
112 while((*p == '\t') || (*p == ' ')) p++;
113 if (*p == '\0') return -5;
114
115 if (*p != '=') return -13;
116 p++;
117
118 while((*p == '\t') || (*p == ' ')) p++;
119 if (*p == '\0') return -6;
120
121 /* Accept both quotes as some backends will accept them, but
122 * technically "'" is invalid and so flag_invalid_quoting is
123 * set so the user can deal with it in the rules if they so wish.
124 */
125
126 if ((*p == '"') || (*p == '\'')) {
127 /* quoted */
128 char quote = *p;
129
130 if (quote == '\'') {
131 msr->mpd->flag_invalid_quoting = 1;
132 }
133
134 p++;
135 if (*p == '\0') return -7;
136
137 start = p;
138 value = apr_pstrdup(msr->mp, p);
139 t = value;
140
141 while(*p != '\0') {
142 if (*p == '\\') {
143 if (*(p + 1) == '\0') {
144 /* improper escaping */
145 return -8;
146 }
147 /* only quote and \ can be escaped */
148 if ((*(p + 1) == quote) || (*(p + 1) == '\\')) {
149 p++;
150 }
151 else {
152 /* improper escaping */
153
154 /* We allow for now because IE sends
155 * improperly escaped content and there's
156 * nothing we can do about it.
157 *
158 * return -9;
159 */
160 }
161 }
162 else if (*p == quote) {
163 *t = '\0';
164 break;
165 }
166
167 *(t++) = *(p++);
168 }
169 if (*p == '\0') return -10;
170
171 p++; /* go over the quote at the end */
172
173 } else {
174 /* not quoted */
175
176 start = p;
177 while((*p != '\0') && (is_token_char(*p))) p++;
178 value = apr_pstrmemdup(msr->mp, start, (p - start));
179 }
180
181 /* evaluate part */
182
183 if (strcmp(name, "name") == 0) {
184
185 validate_quotes(msr, value);
186
187 msr->multipart_name = apr_pstrdup(msr->mp, value);
188
189 if (msr->mpd->mpp->name != NULL) {
190 msr_log(msr, 4, "Multipart: Warning: Duplicate Content-Disposition name: %s",
191 log_escape_nq(msr->mp, value));
192 return -14;
193 }
194 msr->mpd->mpp->name = value;
195
196 if (msr->txcfg->debuglog_level >= 9) {
197 msr_log(msr, 9, "Multipart: Content-Disposition name: %s",
198 log_escape_nq(msr->mp, value));
199 }
200 }
201 else
202 if (strcmp(name, "filename") == 0) {
203
204 validate_quotes(msr, value);
205
206 msr->multipart_filename = apr_pstrdup(msr->mp, value);
207
208 if (msr->mpd->mpp->filename != NULL) {
209 msr_log(msr, 4, "Multipart: Warning: Duplicate Content-Disposition filename: %s",
210 log_escape_nq(msr->mp, value));
211 return -15;
212 }
213 msr->mpd->mpp->filename = value;
214
215 if (msr->txcfg->debuglog_level >= 9) {
216 msr_log(msr, 9, "Multipart: Content-Disposition filename: %s",
217 log_escape_nq(msr->mp, value));
218 }
219 }
220 else return -11;
221
222 if (*p != '\0') {
223 while((*p == '\t') || (*p == ' ')) p++;
224 /* the next character must be a zero or a semi-colon */
225 if (*p == '\0') return 1; /* this is OK */
226 if (*p != ';') {
227 p--;
228 if(*p == '\'' || *p == '\"') {
229 if (msr->txcfg->debuglog_level >= 9) {
230 msr_log(msr, 9, "Multipart: Invalid quoting detected: %s length %zu bytes",
231 log_escape_nq(msr->mp, p), strlen(p));
232 }
233 msr->mpd->flag_invalid_quoting = 1;
234 }
235 p++;
236 return -12;
237 }
238 p++; /* move over the semi-colon */
239 }
240
241 /* loop will stop when (*p == '\0') */
242 }
243
244 return 1;
245 }
246
247 /**
248 *
249 */
multipart_process_part_header(modsec_rec * msr,char ** error_msg)250 static int multipart_process_part_header(modsec_rec *msr, char **error_msg) {
251 int i, len, rc;
252
253 if (error_msg == NULL) return -1;
254 *error_msg = NULL;
255
256 /* Check for nul bytes. */
257 len = MULTIPART_BUF_SIZE - msr->mpd->bufleft;
258 for(i = 0; i < len; i++) {
259 if (msr->mpd->buf[i] == '\0') {
260 *error_msg = apr_psprintf(msr->mp, "Multipart: Nul byte in part headers.");
261 return -1;
262 }
263 }
264
265 /* The buffer is data so increase the data length counter. */
266 msr->msc_reqbody_no_files_length += (MULTIPART_BUF_SIZE - msr->mpd->bufleft);
267
268 if (len > 1) {
269 if (msr->mpd->buf[len - 2] == '\r') {
270 msr->mpd->flag_crlf_line = 1;
271 } else {
272 msr->mpd->flag_lf_line = 1;
273 }
274 } else {
275 msr->mpd->flag_lf_line = 1;
276 }
277
278 /* Is this an empty line? */
279 if ( ((msr->mpd->buf[0] == '\r')
280 &&(msr->mpd->buf[1] == '\n')
281 &&(msr->mpd->buf[2] == '\0') )
282 || ((msr->mpd->buf[0] == '\n')
283 &&(msr->mpd->buf[1] == '\0') ) )
284 { /* Empty line. */
285 char *header_value = NULL;
286
287 header_value = (char *)apr_table_get(msr->mpd->mpp->headers, "Content-Disposition");
288 if (header_value == NULL) {
289 *error_msg = apr_psprintf(msr->mp, "Multipart: Part missing Content-Disposition header.");
290 return -1;
291 }
292
293 rc = multipart_parse_content_disposition(msr, header_value);
294 if (rc < 0) {
295 *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid Content-Disposition header (%d): %s.",
296 rc, log_escape_nq(msr->mp, header_value));
297 return -1;
298 }
299
300 if (msr->mpd->mpp->name == NULL) {
301 *error_msg = apr_psprintf(msr->mp, "Multipart: Content-Disposition header missing name field.");
302 return -1;
303 }
304
305 if (msr->mpd->mpp->filename != NULL) {
306 /* Some parsers use crude methods to extract the name and filename
307 * values from the C-D header. We need to check for the case where they
308 * didn't understand C-D but we did.
309 */
310 if (strstr(header_value, "filename=") == NULL) {
311 *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid Content-Disposition header (filename).");
312 return -1;
313 }
314
315 msr->mpd->mpp->type = MULTIPART_FILE;
316 } else {
317 msr->mpd->mpp->type = MULTIPART_FORMDATA;
318 }
319
320 msr->mpd->mpp_state = 1;
321 msr->mpd->mpp->last_header_name = NULL;
322 } else {
323 /* Header line. */
324
325 if (isspace(msr->mpd->buf[0])) {
326 char *header_value, *new_value, *data;
327
328 /* header folding, add data to the header we are building */
329 msr->mpd->flag_header_folding = 1;
330
331 /* RFC-2557 states header folding is SP / HTAB, but PHP and
332 * perhaps others will take any whitespace. So, we accept,
333 * but with a flag set.
334 */
335 if ((msr->mpd->buf[0] != '\t') && (msr->mpd->buf[0] != ' ')) {
336 msr->mpd->flag_invalid_header_folding = 1;
337 }
338
339 if (msr->mpd->mpp->last_header_name == NULL) {
340 /* we are not building a header at this moment */
341 *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid part header (folding error).");
342 return -1;
343 }
344
345 /* locate the beginning of data */
346 data = msr->mpd->buf;
347 while(isspace(*data)) {
348 /* Flag invalid header folding if an invalid RFC-2557 character is used anywhere
349 * in the folding prefix.
350 */
351 if ((*data != '\t') && (*data != ' ')) {
352 msr->mpd->flag_invalid_header_folding = 1;
353 }
354 data++;
355 }
356
357 new_value = apr_pstrdup(msr->mp, data);
358 remove_lf_crlf_inplace(new_value);
359
360 /* update the header value in the table */
361 header_value = (char *)apr_table_get(msr->mpd->mpp->headers, msr->mpd->mpp->last_header_name);
362 new_value = apr_pstrcat(msr->mp, header_value, " ", new_value, NULL);
363 apr_table_set(msr->mpd->mpp->headers, msr->mpd->mpp->last_header_name, new_value);
364
365 if (msr->txcfg->debuglog_level >= 9) {
366 msr_log(msr, 9, "Multipart: Continued folder header \"%s\" with \"%s\"",
367 log_escape(msr->mp, msr->mpd->mpp->last_header_name),
368 log_escape(msr->mp, data));
369 }
370
371 if (strlen(new_value) > MULTIPART_BUF_SIZE) {
372 *error_msg = apr_psprintf(msr->mp, "Multipart: Part header too long.");
373 return -1;
374 }
375 } else {
376 char *header_name, *header_value, *data;
377
378 /* new header */
379
380 data = msr->mpd->buf;
381 while((*data != ':') && (*data != '\0')) data++;
382 if (*data == '\0') {
383 *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid part header (colon missing): %s.",
384 log_escape_nq(msr->mp, msr->mpd->buf));
385 return -1;
386 }
387
388 /* extract header name */
389 header_name = apr_pstrmemdup(msr->mp, msr->mpd->buf, (data - msr->mpd->buf));
390 if (data == msr->mpd->buf) {
391 *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid part header (header name missing).");
392
393 return -1;
394 }
395
396 /* extract the value value */
397 data++;
398 while((*data == '\t') || (*data == ' ')) data++;
399 header_value = apr_pstrdup(msr->mp, data);
400 remove_lf_crlf_inplace(header_value);
401
402 /* error if the name already exists */
403 if (apr_table_get(msr->mpd->mpp->headers, header_name) != NULL) {
404 *error_msg = apr_psprintf(msr->mp, "Multipart: Duplicate part header: %s.",
405 log_escape_nq(msr->mp, header_name));
406 return -1;
407 }
408
409 apr_table_setn(msr->mpd->mpp->headers, header_name, header_value);
410 msr->mpd->mpp->last_header_name = header_name;
411
412 if (msr->txcfg->debuglog_level >= 9) {
413 msr_log(msr, 9, "Multipart: Added part header \"%s\" \"%s\"",
414 log_escape(msr->mp, header_name),
415 log_escape(msr->mp, header_value));
416 }
417 }
418 }
419
420 return 1;
421 }
422
423 /**
424 *
425 */
multipart_process_part_data(modsec_rec * msr,char ** error_msg)426 static int multipart_process_part_data(modsec_rec *msr, char **error_msg) {
427 char *p = msr->mpd->buf + (MULTIPART_BUF_SIZE - msr->mpd->bufleft);
428 char localreserve[2] = { '\0', '\0' }; /* initialized to quiet warning */
429 int bytes_reserved = 0;
430
431 if (error_msg == NULL) return -1;
432 *error_msg = NULL;
433
434 /* Preserve some bytes for later. */
435 if ( ((MULTIPART_BUF_SIZE - msr->mpd->bufleft) >= 1)
436 && (*(p - 1) == '\n') )
437 {
438 if ( ((MULTIPART_BUF_SIZE - msr->mpd->bufleft) >= 2)
439 && (*(p - 2) == '\r') )
440 {
441 /* Two bytes. */
442 bytes_reserved = 2;
443 localreserve[0] = *(p - 2);
444 localreserve[1] = *(p - 1);
445 msr->mpd->bufleft += 2;
446 *(p - 2) = 0;
447 } else {
448 /* Only one byte. */
449 bytes_reserved = 1;
450 localreserve[0] = *(p - 1);
451 localreserve[1] = 0;
452 msr->mpd->bufleft += 1;
453 *(p - 1) = 0;
454 }
455 }
456
457 /* add data to the part we are building */
458 if (msr->mpd->mpp->type == MULTIPART_FILE) {
459 int extract = msr->upload_extract_files;
460
461 /* remember where we started */
462 if (msr->mpd->mpp->length == 0) {
463 msr->mpd->mpp->offset = msr->mpd->buf_offset;
464 }
465
466 /* check if the file limit has been reached */
467 if (extract && (msr->mpd->nfiles >= msr->txcfg->upload_file_limit)) {
468 if (msr->mpd->flag_file_limit_exceeded == 0) {
469 *error_msg = apr_psprintf(msr->mp,
470 "Multipart: Upload file limit exceeded "
471 "SecUploadFileLimit %d.",
472 msr->txcfg->upload_file_limit);
473 msr_log(msr, 3, "%s", *error_msg);
474
475 msr->mpd->flag_file_limit_exceeded = 1;
476 }
477
478 extract = 0;
479 }
480
481 /* only store individual files on disk if we are going
482 * to keep them or if we need to have them approved later
483 */
484 if (extract) {
485 /* first create a temporary file if we don't have it already */
486 if (msr->mpd->mpp->tmp_file_fd == 0) {
487 /* construct temporary file name */
488 msr->mpd->mpp->tmp_file_name = apr_psprintf(msr->mp, "%s/%s-%s-file-XXXXXX",
489 msr->txcfg->tmp_dir, current_filetime(msr->mp), msr->txid);
490 msr->mpd->mpp->tmp_file_fd = msc_mkstemp_ex(msr->mpd->mpp->tmp_file_name, msr->txcfg->upload_filemode);
491
492 /* do we have an opened file? */
493 if (msr->mpd->mpp->tmp_file_fd < 0) {
494 *error_msg = apr_psprintf(msr->mp, "Multipart: Failed to create file: %s",
495 log_escape_nq(msr->mp, msr->mpd->mpp->tmp_file_name));
496 return -1;
497 }
498
499 /* keep track of the files count */
500 msr->mpd->nfiles++;
501
502 if (msr->txcfg->debuglog_level >= 4) {
503 msr_log(msr, 4,
504 "Multipart: Created temporary file %d (mode %04o): %s",
505 msr->mpd->nfiles,
506 (unsigned int)msr->txcfg->upload_filemode,
507 log_escape_nq(msr->mp, msr->mpd->mpp->tmp_file_name));
508 }
509 }
510
511 /* write the reserve first */
512 if (msr->mpd->reserve[0] != 0) {
513 if (write(msr->mpd->mpp->tmp_file_fd, &msr->mpd->reserve[1], msr->mpd->reserve[0]) != msr->mpd->reserve[0]) {
514 *error_msg = apr_psprintf(msr->mp, "Multipart: writing to \"%s\" failed",
515 log_escape(msr->mp, msr->mpd->mpp->tmp_file_name));
516 return -1;
517 }
518
519 msr->mpd->mpp->tmp_file_size += msr->mpd->reserve[0];
520 msr->mpd->mpp->length += msr->mpd->reserve[0];
521 }
522
523 /* write data to the file */
524 if (write(msr->mpd->mpp->tmp_file_fd, msr->mpd->buf, MULTIPART_BUF_SIZE - msr->mpd->bufleft)
525 != (MULTIPART_BUF_SIZE - msr->mpd->bufleft))
526 {
527 *error_msg = apr_psprintf(msr->mp, "Multipart: writing to \"%s\" failed",
528 log_escape(msr->mp, msr->mpd->mpp->tmp_file_name));
529 return -1;
530 }
531
532 msr->mpd->mpp->tmp_file_size += (MULTIPART_BUF_SIZE - msr->mpd->bufleft);
533 msr->mpd->mpp->length += (MULTIPART_BUF_SIZE - msr->mpd->bufleft);
534 } else {
535 /* just keep track of the file size */
536 msr->mpd->mpp->tmp_file_size += (MULTIPART_BUF_SIZE - msr->mpd->bufleft) + msr->mpd->reserve[0];
537 msr->mpd->mpp->length += (MULTIPART_BUF_SIZE - msr->mpd->bufleft) + msr->mpd->reserve[0];
538 }
539 }
540 else if (msr->mpd->mpp->type == MULTIPART_FORMDATA) {
541 value_part_t *value_part = apr_pcalloc(msr->mp, sizeof(value_part_t));
542
543 /* The buffer contains data so increase the data length counter. */
544 msr->msc_reqbody_no_files_length += (MULTIPART_BUF_SIZE - msr->mpd->bufleft) + msr->mpd->reserve[0];
545
546 /* add this part to the list of parts */
547
548 /* remember where we started */
549 if (msr->mpd->mpp->length == 0) {
550 msr->mpd->mpp->offset = msr->mpd->buf_offset;
551 }
552
553 if (msr->mpd->reserve[0] != 0) {
554 value_part->data = apr_palloc(msr->mp, (MULTIPART_BUF_SIZE - msr->mpd->bufleft) + msr->mpd->reserve[0]);
555 memcpy(value_part->data, &(msr->mpd->reserve[1]), msr->mpd->reserve[0]);
556 memcpy(value_part->data + msr->mpd->reserve[0], msr->mpd->buf, (MULTIPART_BUF_SIZE - msr->mpd->bufleft));
557
558 value_part->length = (MULTIPART_BUF_SIZE - msr->mpd->bufleft) + msr->mpd->reserve[0];
559 msr->mpd->mpp->length += value_part->length;
560 } else {
561 value_part->length = (MULTIPART_BUF_SIZE - msr->mpd->bufleft);
562 value_part->data = apr_pstrmemdup(msr->mp, msr->mpd->buf, value_part->length);
563 msr->mpd->mpp->length += value_part->length;
564 }
565
566 *(value_part_t **)apr_array_push(msr->mpd->mpp->value_parts) = value_part;
567
568 if (msr->txcfg->debuglog_level >= 9) {
569 msr_log(msr, 9, "Multipart: Added data to variable: %s",
570 log_escape_nq_ex(msr->mp, value_part->data, value_part->length));
571 }
572 }
573 else {
574 *error_msg = apr_psprintf(msr->mp, "Multipart: unknown part type %d", msr->mpd->mpp->type);
575 return -1;
576 }
577
578 /* store the reserved bytes to the multipart
579 * context so that they don't get lost
580 */
581 if (bytes_reserved) {
582 msr->mpd->reserve[0] = bytes_reserved;
583 msr->mpd->reserve[1] = localreserve[0];
584 msr->mpd->reserve[2] = localreserve[1];
585 msr->mpd->buf_offset += bytes_reserved;
586 }
587 else {
588 msr->mpd->buf_offset -= msr->mpd->reserve[0];
589 msr->mpd->reserve[0] = 0;
590 }
591
592 return 1;
593 }
594
595 /**
596 *
597 */
multipart_combine_value_parts(modsec_rec * msr,apr_array_header_t * value_parts)598 static char *multipart_combine_value_parts(modsec_rec *msr, apr_array_header_t *value_parts) {
599 value_part_t **parts = NULL;
600 char *rval = apr_palloc(msr->mp, msr->mpd->mpp->length + 1);
601 unsigned long int offset;
602 int i;
603
604 if (rval == NULL) return NULL;
605
606 offset = 0;
607 parts = (value_part_t **)value_parts->elts;
608 for(i = 0; i < value_parts->nelts; i++) {
609 if (offset + parts[i]->length <= msr->mpd->mpp->length) {
610 memcpy(rval + offset, parts[i]->data, parts[i]->length);
611 offset += parts[i]->length;
612 }
613 }
614 rval[offset] = '\0';
615
616 return rval;
617 }
618
619 /**
620 *
621 */
multipart_process_boundary(modsec_rec * msr,int last_part,char ** error_log)622 static int multipart_process_boundary(modsec_rec *msr, int last_part, char **error_log) {
623 /* if there was a part being built finish it */
624 if (msr->mpd->mpp != NULL) {
625 /* close the temp file */
626 if ((msr->mpd->mpp->type == MULTIPART_FILE)
627 &&(msr->mpd->mpp->tmp_file_name != NULL)
628 &&(msr->mpd->mpp->tmp_file_fd != 0))
629 {
630 close(msr->mpd->mpp->tmp_file_fd);
631 msr->mpd->mpp->tmp_file_fd = -1;
632 }
633
634 if (msr->mpd->mpp->type != MULTIPART_FILE) {
635 /* now construct a single string out of the parts */
636 msr->mpd->mpp->value = multipart_combine_value_parts(msr, msr->mpd->mpp->value_parts);
637 if (msr->mpd->mpp->value == NULL) return -1;
638 }
639
640 if (msr->mpd->mpp->name) {
641 /* add the part to the list of parts */
642 *(multipart_part **)apr_array_push(msr->mpd->parts) = msr->mpd->mpp;
643 if (msr->mpd->mpp->type == MULTIPART_FILE) {
644 if (msr->txcfg->debuglog_level >= 9) {
645 msr_log(msr, 9, "Multipart: Added file part %pp to the list: name \"%s\" "
646 "file name \"%s\" (offset %u, length %u)",
647 msr->mpd->mpp, log_escape(msr->mp, msr->mpd->mpp->name),
648 log_escape(msr->mp, msr->mpd->mpp->filename),
649 msr->mpd->mpp->offset, msr->mpd->mpp->length);
650 }
651 }
652 else {
653 if (msr->txcfg->debuglog_level >= 9) {
654 msr_log(msr, 9, "Multipart: Added part %pp to the list: name \"%s\" "
655 "(offset %u, length %u)", msr->mpd->mpp, log_escape(msr->mp, msr->mpd->mpp->name),
656 msr->mpd->mpp->offset, msr->mpd->mpp->length);
657 }
658 }
659 }
660 else {
661 msr->mpd->flag_invalid_part = 1;
662 msr_log(msr, 3, "Multipart: Skipping invalid part %pp (part name missing): "
663 "(offset %u, length %u)", msr->mpd->mpp,
664 msr->mpd->mpp->offset, msr->mpd->mpp->length);
665 }
666
667 msr->mpd->mpp = NULL;
668 }
669
670 if (last_part == 0) {
671 /* start building a new part */
672 msr->mpd->mpp = (multipart_part *)apr_pcalloc(msr->mp, sizeof(multipart_part));
673 if (msr->mpd->mpp == NULL) return -1;
674 msr->mpd->mpp->type = MULTIPART_FORMDATA;
675 msr->mpd->mpp_state = 0;
676
677 msr->mpd->mpp->headers = apr_table_make(msr->mp, 10);
678 if (msr->mpd->mpp->headers == NULL) return -1;
679 msr->mpd->mpp->last_header_name = NULL;
680
681 msr->mpd->reserve[0] = 0;
682 msr->mpd->reserve[1] = 0;
683 msr->mpd->reserve[2] = 0;
684 msr->mpd->reserve[3] = 0;
685
686 msr->mpd->mpp->value_parts = apr_array_make(msr->mp, 10, sizeof(value_part_t *));
687 }
688
689 return 1;
690 }
691
multipart_boundary_characters_valid(char * boundary)692 static int multipart_boundary_characters_valid(char *boundary) {
693 unsigned char *p = (unsigned char *)boundary;
694 unsigned char c;
695
696 if (p == NULL) return -1;
697
698 while ((c = *p) != '\0') {
699 // Check against allowed list defined in RFC2046 page 21
700 if (!(
701 ('0' <= c && c <= '9')
702 || ('A' <= c && c <= 'Z')
703 || ('a' <= c && c <= 'z')
704 || (c == ' ' && *(p + 1) != '\0') // space allowed, but not as last character
705 || c == '\''
706 || c == '('
707 || c == ')'
708 || c == '+'
709 || c == '_'
710 || c == ','
711 || c == '-'
712 || c == '.'
713 || c == '/'
714 || c == ':'
715 || c == '='
716 || c == '?'
717 )) {
718 return 0;
719 }
720
721 p++;
722 }
723
724 return 1;
725 }
726
multipart_count_boundary_params(apr_pool_t * mp,const char * header_value)727 static int multipart_count_boundary_params(apr_pool_t *mp, const char *header_value) {
728 char *duplicate = NULL;
729 char *s = NULL;
730 int count = 0;
731
732 if (header_value == NULL) return -1;
733 duplicate = apr_pstrdup(mp, header_value);
734 if (duplicate == NULL) return -1;
735
736 /* Performing a case-insensitive search. */
737 strtolower_inplace((unsigned char *)duplicate);
738
739 s = duplicate;
740 while((s = strstr(s, "boundary")) != NULL) {
741 s += 8;
742
743 if (strchr(s, '=') != NULL) {
744 count++;
745 }
746 }
747
748 return count;
749 }
750
751 /**
752 *
753 */
multipart_init(modsec_rec * msr,char ** error_msg)754 int multipart_init(modsec_rec *msr, char **error_msg) {
755 if (error_msg == NULL) return -1;
756 *error_msg = NULL;
757
758 msr->mpd = (multipart_data *)apr_pcalloc(msr->mp, sizeof(multipart_data));
759 if (msr->mpd == NULL) return -1;
760
761 msr->mpd->parts = apr_array_make(msr->mp, 10, sizeof(multipart_part *));
762 msr->mpd->bufleft = MULTIPART_BUF_SIZE;
763 msr->mpd->bufptr = msr->mpd->buf;
764 msr->mpd->buf_contains_line = 1;
765 msr->mpd->mpp = NULL;
766
767 if (msr->request_content_type == NULL) {
768 msr->mpd->flag_error = 1;
769 *error_msg = apr_psprintf(msr->mp, "Multipart: Content-Type header not available.");
770 return -1;
771 }
772
773 if (strlen(msr->request_content_type) > 1024) {
774 msr->mpd->flag_error = 1;
775 *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid boundary in C-T (length).");
776 return -1;
777 }
778
779 if (strncasecmp(msr->request_content_type, "multipart/form-data", 19) != 0) {
780 msr->mpd->flag_error = 1;
781 *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid MIME type.");
782 return -1;
783 }
784
785 /* Count how many times the word "boundary" appears in the C-T header. */
786 if (multipart_count_boundary_params(msr->mp, msr->request_content_type) > 1) {
787 msr->mpd->flag_error = 1;
788 *error_msg = apr_psprintf(msr->mp, "Multipart: Multiple boundary parameters in C-T.");
789 return -1;
790 }
791
792 msr->mpd->boundary = strstr(msr->request_content_type, "boundary");
793 if (msr->mpd->boundary != NULL) {
794 char *p = NULL;
795 char *b = NULL;
796 int seen_semicolon = 0;
797 int len = 0;
798
799 /* Check for extra characters before the boundary. */
800 for (p = (char *)(msr->request_content_type + 19); p < msr->mpd->boundary; p++) {
801 if (!isspace(*p)) {
802 if ((seen_semicolon == 0) && (*p == ';')) {
803 seen_semicolon = 1; /* It is OK to have one semicolon. */
804 } else {
805 msr->mpd->flag_error = 1;
806 *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid boundary in C-T (malformed).");
807 return -1;
808 }
809 }
810 }
811
812 /* Have we seen the semicolon in the header? */
813 if (seen_semicolon == 0) {
814 msr->mpd->flag_missing_semicolon = 1;
815 }
816
817 b = strchr(msr->mpd->boundary + 8, '=');
818 if (b == NULL) {
819 msr->mpd->flag_error = 1;
820 *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid boundary in C-T (malformed).");
821 return -1;
822 }
823
824 /* Check parameter name ends well. */
825 if (b != (msr->mpd->boundary + 8)) {
826 /* Check all characters between the end of the boundary
827 * and the = character.
828 */
829 for (p = msr->mpd->boundary + 8; p < b; p++) {
830 if (isspace(*p)) {
831 /* Flag for whitespace after parameter name. */
832 msr->mpd->flag_boundary_whitespace = 1;
833 } else {
834 msr->mpd->flag_error = 1;
835 *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid boundary in C-T (parameter name).");
836 return -1;
837 }
838 }
839 }
840
841 b++; /* Go over the = character. */
842 len = strlen(b);
843
844 /* Flag for whitespace before parameter value. */
845 if (isspace(*b)) {
846 msr->mpd->flag_boundary_whitespace = 1;
847 }
848
849 /* Is the boundary quoted? */
850 if ((len >= 2) && (*b == '"') && (*(b + len - 1) == '"')) {
851 /* Quoted. */
852 msr->mpd->boundary = apr_pstrndup(msr->mp, b + 1, len - 2);
853 if (msr->mpd->boundary == NULL) return -1;
854 msr->mpd->flag_boundary_quoted = 1;
855 } else {
856 /* Not quoted. */
857
858 /* Test for partial quoting. */
859 if ( (*b == '"')
860 || ((len >= 2) && (*(b + len - 1) == '"')) )
861 {
862 msr->mpd->flag_error = 1;
863 *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid boundary in C-T (quote).");
864 return -1;
865 }
866
867 msr->mpd->boundary = apr_pstrdup(msr->mp, b);
868 if (msr->mpd->boundary == NULL) return -1;
869 msr->mpd->flag_boundary_quoted = 0;
870 }
871
872 /* Case-insensitive test for the string "boundary" in the boundary. */
873 if (multipart_count_boundary_params(msr->mp, msr->mpd->boundary) != 0) {
874 msr->mpd->flag_error = 1;
875 *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid boundary in C-T (content).");
876 return -1;
877 }
878
879 /* Validate the characters used in the boundary. */
880 if (multipart_boundary_characters_valid(msr->mpd->boundary) != 1) {
881 msr->mpd->flag_error = 1;
882 *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid boundary in C-T (characters).");
883 return -1;
884 }
885
886 if (msr->txcfg->debuglog_level >= 9) {
887 msr_log(msr, 9, "Multipart: Boundary%s: %s",
888 (msr->mpd->flag_boundary_quoted ? " (quoted)" : ""),
889 log_escape_nq(msr->mp, msr->mpd->boundary));
890 }
891
892 if (strlen(msr->mpd->boundary) == 0) {
893 msr->mpd->flag_error = 1;
894 *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid boundary in C-T (empty).");
895 return -1;
896 }
897 }
898 else { /* Could not find boundary in the C-T header. */
899 msr->mpd->flag_error = 1;
900
901 /* Test for case-insensitive boundary. Allowed by the RFC but highly unusual. */
902 if (multipart_count_boundary_params(msr->mp, msr->request_content_type) > 0) {
903 *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid boundary in C-T (case sensitivity).");
904 return -1;
905 }
906
907 *error_msg = apr_psprintf(msr->mp, "Multipart: Boundary not found in C-T.");
908 return -1;
909 }
910
911 return 1;
912 }
913
914 /**
915 * Finalise multipart processing. This method is invoked at the end, when it
916 * is clear that there is no more data to be processed.
917 */
multipart_complete(modsec_rec * msr,char ** error_msg)918 int multipart_complete(modsec_rec *msr, char **error_msg) {
919 if (msr->mpd == NULL) return 1;
920
921 if (msr->txcfg->debuglog_level >= 4) {
922 if (msr->mpd->flag_data_before) {
923 msr_log(msr, 4, "Multipart: Warning: seen data before first boundary.");
924 }
925
926 if (msr->mpd->flag_data_after) {
927 msr_log(msr, 4, "Multipart: Warning: seen data after last boundary.");
928 }
929
930 if (msr->mpd->flag_boundary_quoted) {
931 msr_log(msr, 4, "Multipart: Warning: boundary was quoted.");
932 }
933
934 if (msr->mpd->flag_boundary_whitespace) {
935 msr_log(msr, 4, "Multipart: Warning: boundary whitespace in C-T header.");
936 }
937
938 if (msr->mpd->flag_header_folding) {
939 msr_log(msr, 4, "Multipart: Warning: header folding used.");
940 }
941
942 if (msr->mpd->flag_crlf_line && msr->mpd->flag_lf_line) {
943 msr_log(msr, 4, "Multipart: Warning: mixed line endings used (CRLF/LF).");
944 }
945 else if (msr->mpd->flag_lf_line) {
946 msr_log(msr, 4, "Multipart: Warning: incorrect line endings used (LF).");
947 }
948
949 if (msr->mpd->flag_missing_semicolon) {
950 msr_log(msr, 4, "Multipart: Warning: missing semicolon in C-T header.");
951 }
952
953 if (msr->mpd->flag_invalid_quoting) {
954 msr_log(msr, 4, "Multipart: Warning: invalid quoting used.");
955 }
956
957 if (msr->mpd->flag_invalid_part) {
958 msr_log(msr, 4, "Multipart: Warning: invalid part parsing.");
959 }
960
961 if (msr->mpd->flag_invalid_header_folding) {
962 msr_log(msr, 4, "Multipart: Warning: invalid header folding used.");
963 }
964 }
965
966 if ((msr->mpd->seen_data != 0) && (msr->mpd->is_complete == 0)) {
967 if (msr->mpd->boundary_count > 0) {
968 /* Check if we have the final boundary (that we haven't
969 * processed yet) in the buffer.
970 */
971 if (msr->mpd->buf_contains_line) {
972 if ( ((unsigned int)(MULTIPART_BUF_SIZE - msr->mpd->bufleft) == (4 + strlen(msr->mpd->boundary)))
973 && (*(msr->mpd->buf) == '-')
974 && (*(msr->mpd->buf + 1) == '-')
975 && (strncmp(msr->mpd->buf + 2, msr->mpd->boundary, strlen(msr->mpd->boundary)) == 0)
976 && (*(msr->mpd->buf + 2 + strlen(msr->mpd->boundary)) == '-')
977 && (*(msr->mpd->buf + 2 + strlen(msr->mpd->boundary) + 1) == '-') )
978 {
979 /* Looks like the final boundary - process it. */
980 if (multipart_process_boundary(msr, 1 /* final */, error_msg) < 0) {
981 msr->mpd->flag_error = 1;
982 return -1;
983 }
984
985 /* The payload is complete after all. */
986 msr->mpd->is_complete = 1;
987 }
988 }
989
990 if (msr->mpd->is_complete == 0) {
991 *error_msg = apr_psprintf(msr->mp, "Multipart: Final boundary missing.");
992 return -1;
993 }
994 } else {
995 *error_msg = apr_psprintf(msr->mp, "Multipart: No boundaries found in payload.");
996 return -1;
997 }
998 }
999
1000 return 1;
1001 }
1002
1003 /**
1004 *
1005 */
multipart_process_chunk(modsec_rec * msr,const char * buf,unsigned int size,char ** error_msg)1006 int multipart_process_chunk(modsec_rec *msr, const char *buf,
1007 unsigned int size, char **error_msg)
1008 {
1009 char *inptr = (char *)buf;
1010 unsigned int inleft = size;
1011
1012 if (error_msg == NULL) return -1;
1013 *error_msg = NULL;
1014
1015 if (size == 0) return 1;
1016
1017 msr->mpd->seen_data = 1;
1018
1019 if (msr->mpd->is_complete) {
1020 msr->mpd->flag_data_before = 1;
1021
1022 if (msr->txcfg->debuglog_level >= 4) {
1023 msr_log(msr, 4, "Multipart: Ignoring data after last boundary (received %u bytes)", size);
1024 }
1025
1026 return 1;
1027 }
1028
1029 if (msr->mpd->bufleft == 0) {
1030 msr->mpd->flag_error = 1;
1031 *error_msg = apr_psprintf(msr->mp,
1032 "Multipart: Internal error in process_chunk: no space left in the buffer");
1033 return -1;
1034 }
1035
1036 /* here we loop through the available data, one byte at a time */
1037 while(inleft > 0) {
1038 char c = *inptr;
1039 int process_buffer = 0;
1040
1041 if ((c == '\r') && (msr->mpd->bufleft == 1)) {
1042 /* we don't want to take \r as the last byte in the buffer */
1043 process_buffer = 1;
1044 } else {
1045 inptr++;
1046 inleft = inleft - 1;
1047
1048 *(msr->mpd->bufptr) = c;
1049 msr->mpd->bufptr++;
1050 msr->mpd->bufleft--;
1051 }
1052
1053 /* until we either reach the end of the line
1054 * or the end of our internal buffer
1055 */
1056 if ((c == '\n') || (msr->mpd->bufleft == 0) || (process_buffer)) {
1057 int processed_as_boundary = 0;
1058
1059 *(msr->mpd->bufptr) = 0;
1060
1061 /* Do we have something that looks like a boundary? */
1062 if ( msr->mpd->buf_contains_line
1063 && (strlen(msr->mpd->buf) > 3)
1064 && (*(msr->mpd->buf) == '-')
1065 && (*(msr->mpd->buf + 1) == '-') )
1066 {
1067 /* Does it match our boundary? */
1068 if ( (strlen(msr->mpd->buf) >= strlen(msr->mpd->boundary) + 2)
1069 && (strncmp(msr->mpd->buf + 2, msr->mpd->boundary, strlen(msr->mpd->boundary)) == 0) )
1070 {
1071 char *boundary_end = msr->mpd->buf + 2 + strlen(msr->mpd->boundary);
1072 int is_final = 0;
1073
1074 /* Is this the final boundary? */
1075 if ((*boundary_end == '-') && (*(boundary_end + 1)== '-')) {
1076 is_final = 1;
1077 boundary_end += 2;
1078
1079 if (msr->mpd->is_complete != 0) {
1080 msr->mpd->flag_error = 1;
1081 *error_msg = apr_psprintf(msr->mp,
1082 "Multipart: Invalid boundary (final duplicate).");
1083 return -1;
1084 }
1085 }
1086
1087 /* Allow for CRLF and LF line endings. */
1088 if ( ( (*boundary_end == '\r')
1089 && (*(boundary_end + 1) == '\n')
1090 && (*(boundary_end + 2) == '\0') )
1091 || ( (*boundary_end == '\n')
1092 && (*(boundary_end + 1) == '\0') ) )
1093 {
1094 if (*boundary_end == '\n') {
1095 msr->mpd->flag_lf_line = 1;
1096 } else {
1097 msr->mpd->flag_crlf_line = 1;
1098 }
1099
1100 if (multipart_process_boundary(msr, (is_final ? 1 : 0), error_msg) < 0) {
1101 msr->mpd->flag_error = 1;
1102 return -1;
1103 }
1104
1105 if (is_final) {
1106 msr->mpd->is_complete = 1;
1107 }
1108
1109 processed_as_boundary = 1;
1110 msr->mpd->boundary_count++;
1111 }
1112 else {
1113 /* error */
1114 msr->mpd->flag_error = 1;
1115 *error_msg = apr_psprintf(msr->mp,
1116 "Multipart: Invalid boundary: %s",
1117 log_escape_nq(msr->mp, msr->mpd->buf));
1118 return -1;
1119 }
1120 } else { /* It looks like a boundary but we couldn't match it. */
1121 char *p = NULL;
1122
1123 /* Check if an attempt to use quotes around the boundary was made. */
1124 if ( (msr->mpd->flag_boundary_quoted)
1125 && (strlen(msr->mpd->buf) >= strlen(msr->mpd->boundary) + 3)
1126 && (*(msr->mpd->buf + 2) == '"')
1127 && (strncmp(msr->mpd->buf + 3, msr->mpd->boundary, strlen(msr->mpd->boundary)) == 0)
1128 ) {
1129 msr->mpd->flag_error = 1;
1130 *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid boundary (quotes).");
1131 return -1;
1132 }
1133
1134 /* Check the beginning of the boundary for whitespace. */
1135 p = msr->mpd->buf + 2;
1136 while(isspace(*p)) {
1137 p++;
1138 }
1139
1140 if ( (p != msr->mpd->buf + 2)
1141 && (strncmp(p, msr->mpd->boundary, strlen(msr->mpd->boundary)) == 0)
1142 ) {
1143 /* Found whitespace in front of a boundary. */
1144 msr->mpd->flag_error = 1;
1145 *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid boundary (whitespace).");
1146 return -1;
1147 }
1148
1149 msr->mpd->flag_unmatched_boundary = 1;
1150 }
1151 } else { /* We do not think the buffer contains a boundary. */
1152 /* Look into the buffer to see if there's anything
1153 * there that resembles a boundary.
1154 */
1155 if (msr->mpd->buf_contains_line) {
1156 int i, len = (MULTIPART_BUF_SIZE - msr->mpd->bufleft);
1157 char *p = msr->mpd->buf;
1158
1159 for(i = 0; i < len; i++) {
1160 if ((p[i] == '-') && (i + 1 < len) && (p[i + 1] == '-'))
1161 {
1162 if (strncmp(p + i + 2, msr->mpd->boundary, strlen(msr->mpd->boundary)) == 0) {
1163 msr->mpd->flag_unmatched_boundary = 1;
1164 break;
1165 }
1166 }
1167 }
1168 }
1169 }
1170
1171 /* Process as data if it was not a boundary. */
1172 if (processed_as_boundary == 0) {
1173 if (msr->mpd->mpp == NULL) {
1174 msr->mpd->flag_data_before = 1;
1175
1176 if (msr->txcfg->debuglog_level >= 4) {
1177 msr_log(msr, 4, "Multipart: Ignoring data before first boundary.");
1178 }
1179 } else {
1180 if (msr->mpd->mpp_state == 0) {
1181 if ((msr->mpd->bufleft == 0) || (process_buffer)) {
1182 /* part header lines must be shorter than
1183 * MULTIPART_BUF_SIZE bytes
1184 */
1185 msr->mpd->flag_error = 1;
1186 *error_msg = apr_psprintf(msr->mp,
1187 "Multipart: Part header line over %d bytes long",
1188 MULTIPART_BUF_SIZE);
1189 return -1;
1190 }
1191
1192 if (multipart_process_part_header(msr, error_msg) < 0) {
1193 msr->mpd->flag_error = 1;
1194 return -1;
1195 }
1196 } else {
1197 if (multipart_process_part_data(msr, error_msg) < 0) {
1198 msr->mpd->flag_error = 1;
1199 return -1;
1200 }
1201 }
1202 }
1203 }
1204
1205 /* Update the offset of the data we are about
1206 * to process. This is to allow us to know the
1207 * offsets of individual files and variables.
1208 */
1209 msr->mpd->buf_offset += (MULTIPART_BUF_SIZE - msr->mpd->bufleft);
1210
1211 /* reset the pointer to the beginning of the buffer
1212 * and continue to accept input data
1213 */
1214 msr->mpd->bufptr = msr->mpd->buf;
1215 msr->mpd->bufleft = MULTIPART_BUF_SIZE;
1216 msr->mpd->buf_contains_line = (c == 0x0a) ? 1 : 0;
1217 }
1218
1219 if ((msr->mpd->is_complete) && (inleft != 0)) {
1220 msr->mpd->flag_data_after = 1;
1221
1222 if (msr->txcfg->debuglog_level >= 4) {
1223 msr_log(msr, 4, "Multipart: Ignoring data after last boundary (%u bytes left)", inleft);
1224 }
1225
1226 return 1;
1227 }
1228 }
1229
1230 return 1;
1231 }
1232
1233 /**
1234 *
1235 */
multipart_cleanup(modsec_rec * msr)1236 apr_status_t multipart_cleanup(modsec_rec *msr) {
1237 int keep_files = 0;
1238
1239 if (msr->mpd == NULL) return -1;
1240
1241 if (msr->txcfg->debuglog_level >= 4) {
1242 msr_log(msr, 4, "Multipart: Cleanup started (remove files %d).", msr->upload_remove_files);
1243 }
1244
1245 if (msr->upload_remove_files == 0) {
1246 if (msr->txcfg->upload_dir == NULL) {
1247 msr_log(msr, 1, "Input filter: SecUploadDir is undefined, unable to store "
1248 "multipart files.");
1249 } else {
1250 keep_files = 1;
1251 }
1252 }
1253
1254 /* Loop through the list of parts
1255 * and delete the temporary files, but only if
1256 * file storage was not requested, or if storage
1257 * of relevant files was requested and this isn't
1258 * such a request.
1259 */
1260 if (keep_files == 0) {
1261 multipart_part **parts;
1262 int i;
1263
1264 parts = (multipart_part **)msr->mpd->parts->elts;
1265 for(i = 0; i < msr->mpd->parts->nelts; i++) {
1266 if (parts[i]->type == MULTIPART_FILE) {
1267 if (parts[i]->tmp_file_name != NULL) {
1268 /* make sure it is closed first */
1269 if (parts[i]->tmp_file_fd > 0) {
1270 close(parts[i]->tmp_file_fd);
1271 parts[i]->tmp_file_fd = -1;
1272 }
1273
1274 if (unlink(parts[i]->tmp_file_name) < 0) {
1275 msr_log(msr, 1, "Multipart: Failed to delete file (part) \"%s\" because %d(%s)",
1276 log_escape(msr->mp, parts[i]->tmp_file_name), errno, strerror(errno));
1277 } else {
1278 if (msr->txcfg->debuglog_level >= 4) {
1279 msr_log(msr, 4, "Multipart: Deleted file (part) \"%s\"",
1280 log_escape(msr->mp, parts[i]->tmp_file_name));
1281 }
1282 }
1283 }
1284 }
1285 }
1286 } else {
1287 /* delete empty files, move the others to the upload dir */
1288 multipart_part **parts;
1289 int i;
1290
1291 parts = (multipart_part **)msr->mpd->parts->elts;
1292 for(i = 0; i < msr->mpd->parts->nelts; i++) {
1293 if ( (parts[i]->type == MULTIPART_FILE)
1294 && (parts[i]->tmp_file_size == 0))
1295 {
1296 /* Delete empty file. */
1297 if (parts[i]->tmp_file_name != NULL) {
1298 /* make sure it is closed first */
1299 if (parts[i]->tmp_file_fd > 0) {
1300 close(parts[i]->tmp_file_fd);
1301 parts[i]->tmp_file_fd = -1;
1302 }
1303
1304 if (unlink(parts[i]->tmp_file_name) < 0) {
1305 msr_log(msr, 1, "Multipart: Failed to delete empty file (part) \"%s\" because %d(%s)",
1306 log_escape(msr->mp, parts[i]->tmp_file_name), errno, strerror(errno));
1307 } else {
1308 if (msr->txcfg->debuglog_level >= 4) {
1309 msr_log(msr, 4, "Multipart: Deleted empty file (part) \"%s\"",
1310 log_escape(msr->mp, parts[i]->tmp_file_name));
1311 }
1312 }
1313 }
1314 } else {
1315 /* Move file to the upload dir. */
1316 if (parts[i]->tmp_file_name != NULL) {
1317 const char *new_filename = NULL;
1318 const char *new_basename = NULL;
1319
1320 if (strcmp(msr->txcfg->upload_dir, msr->txcfg->tmp_dir) == 0) {
1321 msr_log(msr, 4, "Not moving part to identical location");
1322 continue;
1323 }
1324
1325 /* make sure it is closed first */
1326 if (parts[i]->tmp_file_fd > 0) {
1327 close(parts[i]->tmp_file_fd);
1328 parts[i]->tmp_file_fd = -1;
1329 }
1330
1331 new_basename = file_basename(msr->mp, parts[i]->tmp_file_name);
1332 if (new_basename == NULL) return -1;
1333 new_filename = apr_psprintf(msr->mp, "%s/%s", msr->txcfg->upload_dir,
1334 new_basename);
1335 if (new_filename == NULL) return -1;
1336
1337 if (apr_file_rename(parts[i]->tmp_file_name, new_filename,
1338 msr->msc_reqbody_mp) != APR_SUCCESS)
1339 {
1340 msr_log(msr, 1, "Input filter: Failed to rename file from \"%s\" to \"%s\".",
1341 log_escape(msr->mp, parts[i]->tmp_file_name),
1342 log_escape(msr->mp, new_filename));
1343 return -1;
1344 } else {
1345 if (msr->txcfg->debuglog_level >= 4) {
1346 msr_log(msr, 4, "Input filter: Moved file from \"%s\" to \"%s\".",
1347 log_escape(msr->mp, parts[i]->tmp_file_name),
1348 log_escape(msr->mp, new_filename));
1349 }
1350 }
1351 }
1352 }
1353 }
1354 }
1355
1356 return 1;
1357 }
1358
1359 /**
1360 *
1361 */
multipart_get_arguments(modsec_rec * msr,char * origin,apr_table_t * arguments)1362 int multipart_get_arguments(modsec_rec *msr, char *origin, apr_table_t *arguments) {
1363 multipart_part **parts;
1364 int i;
1365
1366 parts = (multipart_part **)msr->mpd->parts->elts;
1367 for(i = 0; i < msr->mpd->parts->nelts; i++) {
1368 if (parts[i]->type == MULTIPART_FORMDATA) {
1369 msc_arg *arg = (msc_arg *)apr_pcalloc(msr->mp, sizeof(msc_arg));
1370 if (arg == NULL) return -1;
1371
1372 arg->name = parts[i]->name;
1373 arg->name_len = strlen(parts[i]->name);
1374 arg->value = parts[i]->value;
1375 arg->value_len = parts[i]->length;
1376 arg->value_origin_offset = parts[i]->offset;
1377 arg->value_origin_len = parts[i]->length;
1378 arg->origin = origin;
1379
1380 add_argument(msr, arguments, arg);
1381 }
1382 }
1383
1384 return 1;
1385 }
1386
1387 /**
1388 *
1389 */
multipart_reconstruct_urlencoded_body_sanitize(modsec_rec * msr)1390 char *multipart_reconstruct_urlencoded_body_sanitize(modsec_rec *msr) {
1391 multipart_part **parts;
1392 char *body;
1393 unsigned int body_len;
1394 int i;
1395
1396 if (msr->mpd == NULL) return NULL;
1397
1398 /* calculate the size of the buffer */
1399 body_len = 1;
1400 parts = (multipart_part **)msr->mpd->parts->elts;
1401 for(i = 0; i < msr->mpd->parts->nelts; i++) {
1402 if (parts[i]->type == MULTIPART_FORMDATA) {
1403 body_len += 4;
1404 body_len += strlen(parts[i]->name) * 3;
1405 body_len += strlen(parts[i]->value) * 3;
1406 }
1407 }
1408
1409 /* allocate the buffer */
1410 body = apr_palloc(msr->mp, body_len + 1);
1411 if ((body == NULL) || (body_len + 1 == 0)) return NULL;
1412 *body = 0;
1413
1414 parts = (multipart_part **)msr->mpd->parts->elts;
1415 for(i = 0; i < msr->mpd->parts->nelts; i++) {
1416 if (parts[i]->type == MULTIPART_FORMDATA) {
1417 if (*body != 0) {
1418 strncat(body, "&", body_len - strlen(body));
1419 }
1420 strnurlencat(body, parts[i]->name, body_len - strlen(body));
1421 strncat(body, "=", body_len - strlen(body));
1422
1423 /* Sanitise the variable. Since we are only doing this for
1424 * the logging we will actually write over the data we keep
1425 * in the memory.
1426 */
1427 if (msr->phase >= PHASE_LOGGING) {
1428 if (apr_table_get(msr->arguments_to_sanitize, parts[i]->name) != NULL) {
1429 memset(parts[i]->value, '*', strlen(parts[i]->value));
1430 }
1431 }
1432 strnurlencat(body, parts[i]->value, body_len - strlen(body));
1433 }
1434 }
1435
1436 return body;
1437 }
1438