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