1 /* packet-multipart.c
2 * Routines for multipart media encapsulation dissection
3 * Copyright 2004, Anders Broman.
4 * Copyright 2004, Olivier Biot.
5 *
6 * Refer to the AUTHORS file or the AUTHORS section in the man page
7 * for contacting the author(s) of this file.
8 *
9 * Wireshark - Network traffic analyzer
10 * By Gerald Combs <gerald@wireshark.org>
11 * Copyright 1998 Gerald Combs
12 *
13 *
14 * SPDX-License-Identifier: GPL-2.0-or-later
15 *
16 * References for "media-type multipart/mixed :
17 * https://www.iana.org/assignments/media-types/index.html
18 * https://tools.ietf.org/html/rfc2045
19 * https://tools.ietf.org/html/rfc2046
20 * https://tools.ietf.org/html/rfc2047
21 * https://tools.ietf.org/html/rfc2048
22 * https://tools.ietf.org/html/rfc2049
23 *
24 * Part of the code is modeled from the SIP and HTTP dissectors
25 *
26 * General format of a MIME multipart document:
27 * [ preamble line-end ]
28 * dash-boundary transport-padding line-end
29 * body-part
30 * *encapsulation
31 * close-delimiter transport-padding
32 * [ line-end epilogue ]
33 *
34 * Where:
35 * dash-boundary := "--" boundary
36 * encapsulation := delimiter transport-padding line-end body-part
37 * delimiter := line-end body-part
38 * close-delimiter := delimiter "--"
39 * body-part := MIME-part-headers [ line-end *OCTET ]
40 * transport-padding := *LWSP-char
41 *
42 * Note that line-end is often a LF instead of a CRLF.
43 */
44
45 #include "config.h"
46
47 #include <epan/packet.h>
48 #include <epan/expert.h>
49 #include <epan/media_params.h>
50 #include <epan/prefs.h>
51 #include <wsutil/str_util.h>
52 #include "packet-imf.h"
53
54 #include "packet-dcerpc.h"
55 #include "packet-gssapi.h"
56 #include "packet-http.h"
57
58 void proto_register_multipart(void);
59 void proto_reg_handoff_multipart(void);
60
61 /* Dissector table for media requiring special attention in multipart
62 * encapsulation. */
63 static dissector_table_t multipart_media_subdissector_table;
64
65 /* Initialize the protocol and registered fields */
66 static int proto_multipart = -1;
67
68 /* Generated from convert_proto_tree_add_text.pl */
69 static int hf_multipart_trailer = -1;
70 static int hf_multipart_boundary = -1;
71 static int hf_multipart_first_boundary = -1;
72 static int hf_multipart_last_boundary = -1;
73 static int hf_multipart_preamble = -1;
74
75 /* Initialize the subtree pointers */
76 static gint ett_multipart = -1;
77 static gint ett_multipart_main = -1;
78 static gint ett_multipart_body = -1;
79
80 /* Generated from convert_proto_tree_add_text.pl */
81 static expert_field ei_multipart_no_required_parameter = EI_INIT;
82 static expert_field ei_multipart_decryption_not_possible = EI_INIT;
83
84 /* Not sure that compact_name exists for multipart, but choose to keep
85 * the structure from SIP dissector, all the content- is also from SIP */
86
87
88 typedef struct {
89 const char *name;
90 const char *compact_name;
91 } multipart_header_t;
92
93 static const multipart_header_t multipart_headers[] = {
94 { "Unknown-header", NULL }, /* Pad so that the real headers start at index 1 */
95 { "Content-Description", NULL },
96 { "Content-Disposition", NULL },
97 { "Content-Encoding", "e" },
98 { "Content-Id", NULL },
99 { "Content-Language", NULL },
100 { "Content-Length", "l" },
101 { "Content-Transfer-Encoding", NULL },
102 { "Content-Type", "c" },
103 { "OriginalContent", NULL }
104 };
105
106 #define POS_CONTENT_DESCRIPTION 1
107 #define POS_CONTENT_DISPOSITION 2
108 #define POS_CONTENT_ENCODING 3
109 #define POS_CONTENT_ID 4
110 #define POS_CONTENT_LANGUAGE 5
111 #define POS_CONTENT_LENGTH 6
112 #define POS_CONTENT_TRANSFER_ENCODING 7
113 #define POS_CONTENT_TYPE 8
114 #define POS_ORIGINALCONTENT 9
115
116 /* Initialize the header fields */
117 static gint hf_multipart_type = -1;
118 static gint hf_multipart_part = -1;
119 static gint hf_multipart_sec_token_len = -1;
120
121 static gint hf_header_array[] = {
122 -1, /* "Unknown-header" - Pad so that the real headers start at index 1 */
123 -1, /* "Content-Description" */
124 -1, /* "Content-Disposition" */
125 -1, /* "Content-Encoding" */
126 -1, /* "Content-Id" */
127 -1, /* "Content-Language" */
128 -1, /* "Content-Length" */
129 -1, /* "Content-Transfer-Encoding" */
130 -1, /* "Content-Type" */
131 -1, /* "OriginalContent" */
132 };
133
134 /* Define media_type/Content type table */
135 static dissector_table_t media_type_dissector_table;
136
137 /* Data and media dissector handles */
138 static dissector_handle_t media_handle;
139 static dissector_handle_t gssapi_handle;
140
141 /* Determines if bodies with no media type dissector should be displayed
142 * as raw text, may cause problems with images sound etc
143 * TODO improve to check for different content types ?
144 */
145 static gboolean display_unknown_body_as_text = FALSE;
146 static gboolean remove_base64_encoding = FALSE;
147 #ifdef HAVE_ZLIB
148 static gboolean uncompress_data = TRUE;
149 #endif
150
151 typedef struct {
152 const char *type; /* Type of multipart */
153 char *boundary; /* Boundary string (enclosing quotes removed if any) */
154 guint boundary_length; /* Length of the boundary string */
155 char *protocol; /* Protocol string if encrypted multipart (enclosing quotes removed if any) */
156 guint protocol_length; /* Length of the protocol string */
157 char *orig_content_type; /* Content-Type of original message */
158 char *orig_parameters; /* Parameters for Content-Type of original message */
159 } multipart_info_t;
160
161
162
163 static gint
164 find_first_boundary(tvbuff_t *tvb, gint start, const guint8 *boundary,
165 gint boundary_len, gint *boundary_line_len, gboolean *last_boundary);
166 static gint
167 find_next_boundary(tvbuff_t *tvb, gint start, const guint8 *boundary,
168 gint boundary_len, gint *boundary_line_len, gboolean *last_boundary);
169 static gint
170 process_preamble(proto_tree *tree, tvbuff_t *tvb, multipart_info_t *m_info,
171 gboolean *last_boundary);
172 static gint
173 process_body_part(proto_tree *tree, tvbuff_t *tvb,
174 http_message_info_t *input_message_info, multipart_info_t *m_info,
175 packet_info *pinfo, gint start, gint idx,
176 gboolean *last_boundary);
177 static gint
178 is_known_multipart_header(const char *header_str, guint len);
179
180
181 /* Return a tvb that contains the binary representation of a base64
182 string */
183
184 static tvbuff_t *
base64_decode(packet_info * pinfo,tvbuff_t * b64_tvb,char * name)185 base64_decode(packet_info *pinfo, tvbuff_t *b64_tvb, char *name)
186 {
187 char *data;
188 tvbuff_t *tvb;
189 data = tvb_get_string_enc(pinfo->pool, b64_tvb, 0, tvb_reported_length(b64_tvb), ENC_ASCII);
190
191 tvb = base64_to_tvb(b64_tvb, data);
192 add_new_data_source(pinfo, tvb, name);
193
194 return tvb;
195 }
196
197 /*
198 * Unfold and clean up a MIME-like header, and process LWS as follows:
199 * o Preserves LWS in quoted text
200 * o Remove LWS before and after a separator
201 * o Remove trailing LWS
202 * o Replace other LWS with a single space
203 * Set value to the start of the value
204 * Return the cleaned-up RFC2822 header (buffer must be freed).
205 */
206 static char *
unfold_and_compact_mime_header(wmem_allocator_t * pool,const char * lines,gint * first_colon_offset)207 unfold_and_compact_mime_header(wmem_allocator_t *pool, const char *lines, gint *first_colon_offset)
208 {
209 const char *p = lines;
210 char c;
211 char *ret, *q;
212 char sep_seen = 0; /* Did we see a separator ":;," */
213 char lws = FALSE; /* Did we see LWS (incl. folding) */
214 gint colon = -1;
215
216 if (! lines) return NULL;
217
218 c = *p;
219 ret = (char *)wmem_alloc(pool, strlen(lines) + 1);
220 q = ret;
221
222 while (c) {
223 if (c == ':') {
224 lws = FALSE; /* Prevent leading LWS from showing up */
225 if (colon == -1) {/* First colon */
226 colon = (gint) (q - ret);
227 }
228 *(q++) = sep_seen = c;
229 p++;
230 } else if (c == ';' || c == ',' || c == '=') {
231 lws = FALSE; /* Prevent leading LWS from showing up */
232 *(q++) = sep_seen = c;
233 p++;
234 } else if (c == ' ' || c == '\t') {
235 lws = TRUE;
236 p++;
237 } else if (c == '\n') {
238 lws = FALSE; /* Skip trailing LWS */
239 if ((c = *(p+1))) {
240 if (c == ' ' || c == '\t') { /* Header unfolding */
241 lws = TRUE;
242 p += 2;
243 } else {
244 *q = c = 0; /* Stop */
245 }
246 }
247 } else if (c == '\r') {
248 lws = FALSE;
249 if ((c = *(p+1))) {
250 if (c == '\n') {
251 if ((c = *(p+2))) {
252 if (c == ' ' || c == '\t') { /* Header unfolding */
253 lws = TRUE;
254 p += 3;
255 } else {
256 *q = c = 0; /* Stop */
257 }
258 }
259 } else if (c == ' ' || c == '\t') { /* Header unfolding */
260 lws = TRUE;
261 p += 2;
262 } else {
263 *q = c = 0; /* Stop */
264 }
265 }
266 } else if (c == '"') { /* Start of quoted-string */
267 lws = FALSE;
268 *(q++) = c;
269 while (c) {
270 c = *(q++) = *(++p);
271 if (c == '\\') {
272 /* First part of a quoted-pair; copy the other part,
273 without checking if it's a quote */
274 c = *(q++) = *(++p);
275 } else {
276 if (c == '"') {
277 p++; /* Skip closing quote */
278 break;
279 }
280 }
281 }
282 /* if already zero terminated now, rewind one char to avoid an "off by one" */
283 if(c == 0) {
284 q--;
285 }
286 } else { /* Regular character */
287 if (sep_seen) {
288 sep_seen = 0;
289 } else {
290 if (lws) {
291 *(q++) = ' ';
292 }
293 }
294 lws = FALSE;
295 *(q++) = c;
296 p++; /* OK */
297 }
298
299 if (c) {
300 c = *p;
301 }
302 }
303 *q = 0;
304
305 *first_colon_offset = colon;
306 return (ret);
307 }
308
309 /* Retrieve the media information from pinfo->private_data,
310 * and compute the boundary string and its length.
311 * Return a pointer to a filled-in multipart_info_t, or NULL on failure.
312 *
313 * Boundary delimiters must not appear within the encapsulated material,
314 * and must be no longer than 70 characters, not counting the two
315 * leading hyphens. (quote from rfc2046)
316 */
317 static multipart_info_t *
get_multipart_info(packet_info * pinfo,http_message_info_t * message_info)318 get_multipart_info(packet_info *pinfo, http_message_info_t *message_info)
319 {
320 char *start_boundary, *start_protocol = NULL;
321 multipart_info_t *m_info = NULL;
322 const char *type = pinfo->match_string;
323 char *parameters;
324 gint dummy;
325
326 /*
327 * We need both a content type AND parameters
328 * for multipart dissection.
329 */
330 if (type == NULL) {
331 return NULL;
332 }
333 if (message_info == NULL) {
334 return NULL;
335 }
336 if (message_info->media_str == NULL) {
337 return NULL;
338 }
339
340 /* Clean up the parameters */
341 parameters = unfold_and_compact_mime_header(pinfo->pool, message_info->media_str, &dummy);
342
343 start_boundary = ws_find_media_type_parameter(pinfo->pool, parameters, "boundary");
344 if (!start_boundary) {
345 return NULL;
346 }
347
348 if (strncmp(type, "multipart/encrypted", sizeof("multipart/encrypted") - 1) == 0) {
349 start_protocol = ws_find_media_type_parameter(pinfo->pool, parameters, "protocol");
350 if (!start_protocol) {
351 return NULL;
352 }
353 }
354
355 /*
356 * There is a value for the boundary string
357 */
358 m_info = wmem_new(pinfo->pool, multipart_info_t);
359 m_info->type = type;
360 m_info->boundary = start_boundary;
361 m_info->boundary_length = (guint)strlen(start_boundary);
362 if(start_protocol) {
363 m_info->protocol = start_protocol;
364 m_info->protocol_length = (guint)strlen(start_protocol);
365 } else {
366 m_info->protocol = NULL;
367 m_info->protocol_length = -1;
368 }
369 m_info->orig_content_type = NULL;
370 m_info->orig_parameters = NULL;
371
372 return m_info;
373 }
374
375 /*
376 * The first boundary does not implicitly contain the leading
377 * line-end sequence.
378 *
379 * Return the offset to the 1st byte of the boundary delimiter line.
380 * Set boundary_line_len to the length of the entire boundary delimiter.
381 * Set last_boundary to TRUE if we've seen the last-boundary delimiter.
382 */
383 static gint
find_first_boundary(tvbuff_t * tvb,gint start,const guint8 * boundary,gint boundary_len,gint * boundary_line_len,gboolean * last_boundary)384 find_first_boundary(tvbuff_t *tvb, gint start, const guint8 *boundary,
385 gint boundary_len, gint *boundary_line_len, gboolean *last_boundary)
386 {
387 gint offset = start, next_offset, line_len, boundary_start;
388
389 while (tvb_offset_exists(tvb, offset + 2 + boundary_len)) {
390 boundary_start = offset;
391 if (((tvb_strneql(tvb, offset, (const guint8 *)"--", 2) == 0)
392 && (tvb_strneql(tvb, offset + 2, boundary, boundary_len) == 0)))
393 {
394 /* Boundary string; now check if last */
395 if ((tvb_reported_length_remaining(tvb, offset + 2 + boundary_len + 2) >= 0)
396 && (tvb_strneql(tvb, offset + 2 + boundary_len,
397 (const guint8 *)"--", 2) == 0)) {
398 *last_boundary = TRUE;
399 } else {
400 *last_boundary = FALSE;
401 }
402 /* Look for line end of the boundary line */
403 line_len = tvb_find_line_end(tvb, offset, -1, &offset, FALSE);
404 if (line_len == -1) {
405 *boundary_line_len = -1;
406 } else {
407 *boundary_line_len = offset - boundary_start;
408 }
409 return boundary_start;
410 }
411 line_len = tvb_find_line_end(tvb, offset, -1, &next_offset, FALSE);
412 if (line_len == -1) {
413 return -1;
414 }
415 offset = next_offset;
416 }
417
418 return -1;
419 }
420
421 /*
422 * Unless the first boundary, subsequent boundaries include a line-end sequence
423 * before the dashed boundary string.
424 *
425 * Return the offset to the 1st byte of the boundary delimiter line.
426 * Set boundary_line_len to the length of the entire boundary delimiter.
427 * Set last_boundary to TRUE if we've seen the last-boundary delimiter.
428 */
429 static gint
find_next_boundary(tvbuff_t * tvb,gint start,const guint8 * boundary,gint boundary_len,gint * boundary_line_len,gboolean * last_boundary)430 find_next_boundary(tvbuff_t *tvb, gint start, const guint8 *boundary,
431 gint boundary_len, gint *boundary_line_len, gboolean *last_boundary)
432 {
433 gint offset = start, next_offset, line_len, boundary_start;
434
435 while (tvb_offset_exists(tvb, offset + 2 + boundary_len)) {
436 line_len = tvb_find_line_end(tvb, offset, -1, &next_offset, FALSE);
437 if (line_len == -1) {
438 return -1;
439 }
440 boundary_start = offset + line_len;
441 if (((tvb_strneql(tvb, next_offset, (const guint8 *)"--", 2) == 0)
442 && (tvb_strneql(tvb, next_offset + 2, boundary, boundary_len) == 0)))
443 {
444 /* Boundary string; now check if last */
445 if ((tvb_reported_length_remaining(tvb, next_offset + 2 + boundary_len + 2) >= 0)
446 && (tvb_strneql(tvb, next_offset + 2 + boundary_len,
447 (const guint8 *)"--", 2) == 0)) {
448 *last_boundary = TRUE;
449 } else {
450 *last_boundary = FALSE;
451 }
452 /* Look for line end of the boundary line */
453 line_len = tvb_find_line_end(tvb, next_offset, -1, &offset, FALSE);
454 if (line_len == -1) {
455 *boundary_line_len = -1;
456 } else {
457 *boundary_line_len = offset - boundary_start;
458 }
459 return boundary_start;
460 /* check if last before CRLF; some ignore the standard, so there is no CRLF before the boundary */
461 } else if ((tvb_strneql(tvb, boundary_start - 2, (const guint8 *)"--", 2) == 0)
462 && (tvb_strneql(tvb, boundary_start - (2 + boundary_len), boundary, boundary_len) == 0)
463 && (tvb_strneql(tvb, boundary_start - (2 + boundary_len + 2),
464 (const guint8 *)"--", 2) == 0)) {
465 boundary_start -= 2 + boundary_len + 2;
466 *boundary_line_len = next_offset - boundary_start;
467 *last_boundary = TRUE;
468 return boundary_start;
469 }
470 offset = next_offset;
471 }
472
473 return -1;
474 }
475
476 /*
477 * Process the multipart preamble:
478 * [ preamble line-end ] dashed-boundary transport-padding line-end
479 *
480 * Return the offset to the start of the first body-part.
481 */
482 static gint
process_preamble(proto_tree * tree,tvbuff_t * tvb,multipart_info_t * m_info,gboolean * last_boundary)483 process_preamble(proto_tree *tree, tvbuff_t *tvb, multipart_info_t *m_info,
484 gboolean *last_boundary)
485 {
486 gint boundary_start, boundary_line_len;
487
488 const guint8 *boundary = (guint8 *)m_info->boundary;
489 gint boundary_len = m_info->boundary_length;
490
491 boundary_start = find_first_boundary(tvb, 0, boundary, boundary_len,
492 &boundary_line_len, last_boundary);
493 if (boundary_start == 0) {
494 proto_tree_add_item(tree, hf_multipart_first_boundary, tvb, boundary_start, boundary_line_len, ENC_NA|ENC_ASCII);
495 return boundary_start + boundary_line_len;
496 } else if (boundary_start > 0) {
497 if (boundary_line_len > 0) {
498 gint body_part_start = boundary_start + boundary_line_len;
499 proto_tree_add_item(tree, hf_multipart_preamble, tvb, 0, boundary_start, ENC_NA);
500 proto_tree_add_item(tree, hf_multipart_first_boundary, tvb, boundary_start, boundary_line_len, ENC_NA|ENC_ASCII);
501 return body_part_start;
502 }
503 }
504 return -1;
505 }
506
507 static void
dissect_kerberos_encrypted_message(tvbuff_t * tvb,packet_info * pinfo,proto_tree * tree,gssapi_encrypt_info_t * encrypt)508 dissect_kerberos_encrypted_message(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, gssapi_encrypt_info_t* encrypt)
509 {
510 tvbuff_t *kerberos_tvb;
511 gint offset = 0, len;
512 guint8 *data;
513
514 proto_tree_add_item(tree, hf_multipart_sec_token_len, tvb, offset, 4, ENC_LITTLE_ENDIAN);
515 offset += 4;
516 len = tvb_reported_length_remaining(tvb, offset);
517
518 DISSECTOR_ASSERT(tvb_bytes_exist(tvb, offset, len));
519
520 data = (guint8 *)tvb_memdup(pinfo->pool, tvb, offset, len);
521 kerberos_tvb = tvb_new_child_real_data(tvb, data, len, len);
522
523 add_new_data_source(pinfo, kerberos_tvb, "Kerberos Data");
524 call_dissector_with_data(gssapi_handle, kerberos_tvb, pinfo, tree, encrypt);
525 }
526
527 /*
528 * Process a multipart body-part:
529 * MIME-part-headers [ line-end *OCTET ]
530 * line-end dashed-boundary transport-padding line-end
531 *
532 * If applicable, call a media subdissector.
533 *
534 * Return the offset to the start of the next body-part.
535 */
536 static gint
process_body_part(proto_tree * tree,tvbuff_t * tvb,http_message_info_t * input_message_info,multipart_info_t * m_info,packet_info * pinfo,gint start,gint idx,gboolean * last_boundary)537 process_body_part(proto_tree *tree, tvbuff_t *tvb,
538 http_message_info_t *input_message_info, multipart_info_t *m_info,
539 packet_info *pinfo, gint start, gint idx,
540 gboolean *last_boundary)
541 {
542 proto_tree *subtree;
543 proto_item *ti;
544 gint offset = start, next_offset = 0;
545 http_message_info_t message_info = { input_message_info->type, NULL, NULL, NULL };
546 gint body_start, boundary_start, boundary_line_len;
547
548 gchar *content_type_str = NULL;
549 gchar *content_trans_encoding_str = NULL;
550 #ifdef HAVE_ZLIB
551 gchar *content_encoding_str = NULL;
552 #endif
553 char *filename = NULL;
554 char *mimetypename = NULL;
555 gboolean last_field = FALSE;
556 gboolean is_raw_data = FALSE;
557
558 const guint8 *boundary = (guint8 *)m_info->boundary;
559 gint boundary_len = m_info->boundary_length;
560
561 ti = proto_tree_add_item(tree, hf_multipart_part, tvb, start, 0, ENC_ASCII|ENC_NA);
562 subtree = proto_item_add_subtree(ti, ett_multipart_body);
563
564 /* find the next boundary to find the end of this body part */
565 boundary_start = find_next_boundary(tvb, offset, boundary, boundary_len,
566 &boundary_line_len, last_boundary);
567
568 if (boundary_start <= 0) {
569 return -1;
570 }
571
572 /*
573 * Process the MIME-part-headers
574 */
575
576 while (!last_field)
577 {
578 gint colon_offset;
579 char *hdr_str;
580 char *header_str;
581
582 /* Look for the end of the header (denoted by cr)
583 * 3:d argument to imf_find_field_end() maxlen; must be last offset in the tvb.
584 */
585 next_offset = imf_find_field_end(tvb, offset, tvb_reported_length_remaining(tvb, offset)+offset, &last_field);
586 /* the following should never happen */
587 /* If cr not found, won't have advanced - get out to avoid infinite loop! */
588 /*
589 if (next_offset == offset) {
590 break;
591 }
592 */
593 if (last_field && (next_offset+2) <= boundary_start) {
594 /* Add the extra CRLF of the last field */
595 next_offset += 2;
596 } else if((next_offset-2) == boundary_start) {
597 /* if CRLF is the start of next boundary it belongs to the boundary and not the field,
598 so it's the last field without CRLF */
599 last_field = TRUE;
600 next_offset -= 2;
601 } else if (next_offset > boundary_start) {
602 /* if there is no CRLF between last field and next boundary - trim it! */
603 next_offset = boundary_start;
604 }
605
606 hdr_str = tvb_get_string_enc(pinfo->pool, tvb, offset, next_offset - offset, ENC_ASCII);
607
608 colon_offset = 0;
609 header_str = unfold_and_compact_mime_header(pinfo->pool, hdr_str, &colon_offset);
610 if (colon_offset <= 0) {
611 /* if there is no colon it's no header, so break and add complete line to the body */
612 next_offset = offset;
613 break;
614 } else {
615 gint hf_index;
616
617 hf_index = is_known_multipart_header(header_str, colon_offset);
618
619 if (hf_index == -1) {
620 if(isprint_string(header_str)) {
621 proto_tree_add_format_text(subtree, tvb, offset, next_offset - offset);
622 } else {
623 /* if the header name is unknown and not printable, break and add complete line to the body */
624 next_offset = offset;
625 break;
626 }
627 } else {
628 char *value_str = wmem_strdup(pinfo->pool, header_str + colon_offset + 1);
629
630 proto_tree_add_string_format(subtree,
631 hf_header_array[hf_index], tvb,
632 offset, next_offset - offset,
633 (const char *)value_str, "%s",
634 tvb_format_text(pinfo->pool, tvb, offset, next_offset - offset));
635
636 switch (hf_index) {
637 case POS_ORIGINALCONTENT:
638 {
639 char *semicolonp;
640 /* The Content-Type starts at colon_offset + 1 or after the type parameter */
641 char* type_str = ws_find_media_type_parameter(pinfo->pool, value_str, "type");
642 if(type_str != NULL) {
643 value_str = type_str;
644 }
645
646 semicolonp = strchr(value_str, ';');
647
648 if (semicolonp != NULL) {
649 *semicolonp = '\0';
650 m_info->orig_parameters = wmem_strdup(pinfo->pool,
651 semicolonp + 1);
652 }
653
654 m_info->orig_content_type = wmem_ascii_strdown(pinfo->pool, value_str, -1);
655 }
656 break;
657 case POS_CONTENT_TYPE:
658 {
659 /* The Content-Type starts at colon_offset + 1 */
660 char *semicolonp = strchr(value_str, ';');
661
662 if (semicolonp != NULL) {
663 *semicolonp = '\0';
664 message_info.media_str = wmem_strdup(pinfo->pool, semicolonp + 1);
665 } else {
666 message_info.media_str = NULL;
667 }
668
669 content_type_str = wmem_ascii_strdown(pinfo->pool, value_str, -1);
670
671 /* Show content-type in root 'part' label */
672 proto_item_append_text(ti, " (%s)", content_type_str);
673
674 /* find the "name" parameter in case we don't find a content disposition "filename" */
675 mimetypename = ws_find_media_type_parameter(pinfo->pool, message_info.media_str, "name");
676
677 if(strncmp(content_type_str, "application/octet-stream",
678 sizeof("application/octet-stream")-1) == 0) {
679 is_raw_data = TRUE;
680 }
681
682 /* there are only 2 body parts possible and each part has specific content types */
683 if(m_info->protocol && idx == 0
684 && (is_raw_data || g_ascii_strncasecmp(content_type_str, m_info->protocol,
685 strlen(m_info->protocol)) != 0))
686 {
687 return -1;
688 }
689 }
690 break;
691 case POS_CONTENT_ENCODING:
692 {
693 /* The Content-Encoding starts at colon_offset + 1 */
694 char *crp = strchr(value_str, '\r');
695
696 if (crp != NULL) {
697 *crp = '\0';
698 }
699 #ifdef HAVE_ZLIB
700 content_encoding_str = wmem_ascii_strdown(pinfo->pool, value_str, -1);
701 #endif
702 }
703 break;
704 case POS_CONTENT_TRANSFER_ENCODING:
705 {
706 /* The Content-Transferring starts at colon_offset + 1 */
707 char *crp = strchr(value_str, '\r');
708
709 if (crp != NULL) {
710 *crp = '\0';
711 }
712
713 content_trans_encoding_str = wmem_ascii_strdown(pinfo->pool, value_str, -1);
714 }
715 break;
716 case POS_CONTENT_DISPOSITION:
717 {
718 /* find the "filename" parameter */
719 filename = ws_find_media_type_parameter(pinfo->pool, value_str, "filename");
720 }
721 break;
722 case POS_CONTENT_ID:
723 message_info.content_id = wmem_strdup(pinfo->pool, value_str);
724 break;
725 default:
726 break;
727 }
728 }
729 }
730 offset = next_offset;
731 }
732
733 body_start = next_offset;
734
735 /*
736 * Process the body
737 */
738
739 {
740 gint body_len = boundary_start - body_start;
741 tvbuff_t *tmp_tvb = tvb_new_subset_length(tvb, body_start, body_len);
742 /*
743 * If multipart subtype is encrypted the protcol string was set.
744 *
745 * See MS-WSMV section 2.2.9.1.2.1 "HTTP Headers":
746 *
747 * https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wsmv/b79927c2-96be-4801-aa68-180db95593f9
748 *
749 * There are only 2 body parts possible, and each part has specific
750 * content types.
751 */
752 if(m_info->protocol && idx == 1 && is_raw_data)
753 {
754 gssapi_encrypt_info_t encrypt;
755
756 memset(&encrypt, 0, sizeof(encrypt));
757 encrypt.decrypt_gssapi_tvb=DECRYPT_GSSAPI_NORMAL;
758
759 dissect_kerberos_encrypted_message(tmp_tvb, pinfo, subtree, &encrypt);
760
761 if(encrypt.gssapi_decrypted_tvb){
762 tmp_tvb = encrypt.gssapi_decrypted_tvb;
763 is_raw_data = FALSE;
764 content_type_str = m_info->orig_content_type;
765 message_info.media_str = m_info->orig_parameters;
766 } else if(encrypt.gssapi_encrypted_tvb) {
767 tmp_tvb = encrypt.gssapi_encrypted_tvb;
768 proto_tree_add_expert(tree, pinfo, &ei_multipart_decryption_not_possible, tmp_tvb, 0, -1);
769 }
770 }
771
772 if (!is_raw_data &&
773 content_type_str) {
774
775 /*
776 * subdissection
777 */
778 gboolean dissected;
779
780 /*
781 * Try and remove any content transfer encoding so that each sub-dissector
782 * doesn't have to do it itself
783 *
784 */
785
786 if(content_trans_encoding_str && remove_base64_encoding) {
787
788 if(!g_ascii_strncasecmp(content_trans_encoding_str, "base64", 6))
789 tmp_tvb = base64_decode(pinfo, tmp_tvb, filename ? filename : (mimetypename ? mimetypename : content_type_str));
790
791 }
792
793 #ifdef HAVE_ZLIB
794 if(content_encoding_str && uncompress_data) {
795
796 if(g_ascii_strncasecmp(content_encoding_str,"gzip",4) == 0 ||
797 g_ascii_strncasecmp(content_encoding_str,"deflate",7) == 0 ||
798 g_ascii_strncasecmp(content_encoding_str,"x-gzip",6) == 0 ||
799 g_ascii_strncasecmp(content_encoding_str,"x-deflate",9) == 0){
800 /* The body is gzip:ed */
801 tvbuff_t *uncompress_tvb = tvb_uncompress(tmp_tvb, 0, body_len);
802 if (uncompress_tvb) {
803 tmp_tvb = uncompress_tvb;
804 add_new_data_source(pinfo, tmp_tvb, "gunzipped data");
805 }
806 }
807 }
808 #endif
809
810 /*
811 * First try the dedicated multipart dissector table
812 */
813 dissected = dissector_try_string(multipart_media_subdissector_table,
814 content_type_str, tmp_tvb, pinfo, subtree, &message_info);
815 if (! dissected) {
816 /*
817 * Fall back to the default media dissector table
818 */
819 dissected = dissector_try_string(media_type_dissector_table,
820 content_type_str, tmp_tvb, pinfo, subtree, &message_info);
821 }
822 if (! dissected) {
823 const char *save_match_string = pinfo->match_string;
824 pinfo->match_string = content_type_str;
825 call_dissector_with_data(media_handle, tmp_tvb, pinfo, subtree, &message_info);
826 pinfo->match_string = save_match_string;
827 }
828 message_info.media_str = NULL; /* Shares same memory as content_type_str */
829 } else {
830 call_data_dissector(tmp_tvb, pinfo, subtree);
831 }
832 proto_item_set_len(ti, boundary_start - start);
833 if (*last_boundary == TRUE) {
834 proto_tree_add_item(tree, hf_multipart_last_boundary, tvb, boundary_start, boundary_line_len, ENC_NA|ENC_ASCII);
835 } else {
836 proto_tree_add_item(tree, hf_multipart_boundary, tvb, boundary_start, boundary_line_len, ENC_NA|ENC_ASCII);
837 }
838
839 return boundary_start + boundary_line_len;
840 }
841 }
842
843 /*
844 * Call this method to actually dissect the multipart body.
845 * NOTE - Only do so if a boundary string has been found!
846 */
dissect_multipart(tvbuff_t * tvb,packet_info * pinfo,proto_tree * tree,void * data)847 static int dissect_multipart(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data)
848 {
849 proto_tree *subtree;
850 proto_item *ti;
851 proto_item *type_ti;
852 http_message_info_t *message_info = (http_message_info_t *)data;
853 multipart_info_t *m_info = get_multipart_info(pinfo, message_info);
854 gint header_start = 0;
855 gint body_index = 0;
856 gboolean last_boundary = FALSE;
857
858 if (m_info == NULL) {
859 /*
860 * We can't get the required multipart information
861 */
862 proto_tree_add_expert(tree, pinfo, &ei_multipart_no_required_parameter, tvb, 0, -1);
863 call_data_dissector(tvb, pinfo, tree);
864 return tvb_reported_length(tvb);
865 }
866
867 /* Add stuff to the protocol tree */
868 ti = proto_tree_add_item(tree, proto_multipart,
869 tvb, 0, -1, ENC_NA);
870 subtree = proto_item_add_subtree(ti, ett_multipart);
871 proto_item_append_text(ti, ", Type: %s, Boundary: \"%s\"",
872 m_info->type, m_info->boundary);
873
874 /* Show multi-part type as a generated field */
875 type_ti = proto_tree_add_string(subtree, hf_multipart_type,
876 tvb, 0, 0, pinfo->match_string);
877 proto_item_set_generated(type_ti);
878
879 /*
880 * Make no entries in Protocol column and Info column on summary display,
881 * but stop sub-dissectors from clearing entered text in summary display.
882 */
883 col_set_fence(pinfo->cinfo, COL_INFO);
884
885 /*
886 * Process the multipart preamble
887 */
888 header_start = process_preamble(subtree, tvb, m_info, &last_boundary);
889 if (header_start == -1) {
890 call_data_dissector(tvb, pinfo, subtree);
891 return tvb_reported_length(tvb);
892 }
893 /*
894 * Process the encapsulated bodies
895 */
896 while (last_boundary == FALSE) {
897 header_start = process_body_part(subtree, tvb, message_info, m_info,
898 pinfo, header_start, body_index++, &last_boundary);
899 if (header_start == -1) {
900 return tvb_reported_length(tvb);
901 }
902 }
903 /*
904 * Process the multipart trailer
905 */
906 if (tvb_reported_length_remaining(tvb, header_start) > 0) {
907 proto_tree_add_item(subtree, hf_multipart_trailer, tvb, header_start, -1, ENC_NA);
908 }
909
910 return tvb_reported_length(tvb);
911 }
912
913 /* Returns index of method in multipart_headers */
914 static gint
is_known_multipart_header(const char * header_str,guint len)915 is_known_multipart_header(const char *header_str, guint len)
916 {
917 guint i;
918
919 for (i = 1; i < array_length(multipart_headers); i++) {
920 if (len == strlen(multipart_headers[i].name) &&
921 g_ascii_strncasecmp(header_str, multipart_headers[i].name, len) == 0)
922 return i;
923 if (multipart_headers[i].compact_name != NULL &&
924 len == strlen(multipart_headers[i].compact_name) &&
925 g_ascii_strncasecmp(header_str, multipart_headers[i].compact_name, len) == 0)
926 return i;
927 }
928
929 return -1;
930 }
931
932 /*
933 * Register the protocol with Wireshark.
934 *
935 * This format is required because a script is used to build the C function
936 * that calls all the protocol registration.
937 */
938
939 void
proto_register_multipart(void)940 proto_register_multipart(void)
941 {
942
943 /* Setup list of header fields See Section 1.6.1 for details */
944 static hf_register_info hf[] = {
945 { &hf_multipart_type,
946 { "Type",
947 "mime_multipart.type",
948 FT_STRING, BASE_NONE, NULL, 0x00,
949 "MIME multipart encapsulation type", HFILL
950 }
951 },
952 { &hf_multipart_part,
953 { "Encapsulated multipart part",
954 "mime_multipart.part",
955 FT_STRING, BASE_NONE, NULL, 0x00,
956 NULL, HFILL
957 }
958 },
959 { &hf_multipart_sec_token_len,
960 { "Length of security token",
961 "mime_multipart.header.sectoken-length",
962 FT_UINT32, BASE_DEC, NULL, 0x00,
963 "Length of the Kerberos BLOB which follows this token", HFILL
964 }
965 },
966 { &hf_header_array[POS_CONTENT_DESCRIPTION],
967 { "Content-Description",
968 "mime_multipart.header.content-description",
969 FT_STRING, BASE_NONE, NULL, 0x00,
970 "Content-Description Header", HFILL
971 }
972 },
973 { &hf_header_array[POS_CONTENT_DISPOSITION],
974 { "Content-Disposition",
975 "mime_multipart.header.content-disposition",
976 FT_STRING, BASE_NONE, NULL, 0x00,
977 "RFC 2183: Content-Disposition Header", HFILL
978 }
979 },
980 { &hf_header_array[POS_CONTENT_ENCODING],
981 { "Content-Encoding",
982 "mime_multipart.header.content-encoding",
983 FT_STRING, BASE_NONE, NULL, 0x00,
984 "Content-Encoding Header", HFILL
985 }
986 },
987 { &hf_header_array[POS_CONTENT_ID],
988 { "Content-Id",
989 "mime_multipart.header.content-id",
990 FT_STRING, BASE_NONE, NULL, 0x00,
991 "RFC 2045: Content-Id Header", HFILL
992 }
993 },
994 { &hf_header_array[POS_CONTENT_LANGUAGE],
995 { "Content-Language",
996 "mime_multipart.header.content-language",
997 FT_STRING, BASE_NONE, NULL, 0x00,
998 "Content-Language Header", HFILL
999 }
1000 },
1001 { &hf_header_array[POS_CONTENT_LENGTH],
1002 { "Content-Length",
1003 "mime_multipart.header.content-length",
1004 FT_STRING, BASE_NONE, NULL, 0x0,
1005 "Content-Length Header", HFILL
1006 }
1007 },
1008 { &hf_header_array[POS_CONTENT_TRANSFER_ENCODING],
1009 { "Content-Transfer-Encoding",
1010 "mime_multipart.header.content-transfer-encoding",
1011 FT_STRING, BASE_NONE, NULL, 0x00,
1012 "RFC 2045: Content-Transfer-Encoding Header", HFILL
1013 }
1014 },
1015 { &hf_header_array[POS_CONTENT_TYPE],
1016 { "Content-Type",
1017 "mime_multipart.header.content-type",
1018 FT_STRING, BASE_NONE,NULL,0x0,
1019 "Content-Type Header", HFILL
1020 }
1021 },
1022 { &hf_header_array[POS_ORIGINALCONTENT],
1023 { "OriginalContent",
1024 "mime_multipart.header.originalcontent",
1025 FT_STRING, BASE_NONE,NULL,0x0,
1026 "Original Content-Type Header", HFILL
1027 }
1028 },
1029
1030 /* Generated from convert_proto_tree_add_text.pl */
1031 { &hf_multipart_first_boundary, { "First boundary", "mime_multipart.first_boundary", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL }},
1032 { &hf_multipart_preamble, { "Preamble", "mime_multipart.preamble", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL }},
1033 { &hf_multipart_last_boundary, { "Last boundary", "mime_multipart.last_boundary", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL }},
1034 { &hf_multipart_boundary, { "Boundary", "mime_multipart.boundary", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL }},
1035 { &hf_multipart_trailer, { "Trailer", "mime_multipart.trailer", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL }},
1036
1037 };
1038
1039 /*
1040 * Preferences
1041 */
1042 module_t *multipart_module;
1043 expert_module_t* expert_multipart;
1044
1045
1046 /*
1047 * Setup protocol subtree array
1048 */
1049 static gint *ett[] = {
1050 &ett_multipart,
1051 &ett_multipart_main,
1052 &ett_multipart_body,
1053 };
1054
1055 static ei_register_info ei[] = {
1056 { &ei_multipart_no_required_parameter, { "mime_multipart.no_required_parameter", PI_PROTOCOL, PI_ERROR, "The multipart dissector could not find a required parameter.", EXPFILL }},
1057 { &ei_multipart_decryption_not_possible, { "mime_multipart.decryption_not_possible", PI_UNDECODED, PI_WARN, "The multipart dissector could not decrypt the message.", EXPFILL }},
1058 };
1059
1060 /*
1061 * Register the protocol name and description
1062 */
1063 proto_multipart = proto_register_protocol(
1064 "MIME Multipart Media Encapsulation",
1065 "MIME multipart",
1066 "mime_multipart");
1067
1068 /*
1069 * Required function calls to register
1070 * the header fields and subtrees used.
1071 */
1072 proto_register_field_array(proto_multipart, hf, array_length(hf));
1073 proto_register_subtree_array(ett, array_length(ett));
1074 expert_multipart = expert_register_protocol(proto_multipart);
1075 expert_register_field_array(expert_multipart, ei, array_length(ei));
1076
1077 multipart_module = prefs_register_protocol(proto_multipart, NULL);
1078
1079 prefs_register_bool_preference(multipart_module,
1080 "display_unknown_body_as_text",
1081 "Display bodies without media type as text",
1082 "Display multipart bodies with no media type dissector"
1083 " as raw text (may cause problems with binary data).",
1084 &display_unknown_body_as_text);
1085
1086 prefs_register_bool_preference(multipart_module,
1087 "remove_base64_encoding",
1088 "Remove base64 encoding from bodies",
1089 "Remove any base64 content-transfer encoding from bodies. "
1090 "This supports export of the body and its further dissection.",
1091 &remove_base64_encoding);
1092
1093 #ifdef HAVE_ZLIB
1094 prefs_register_bool_preference(multipart_module,
1095 "uncompress_data",
1096 "Uncompress parts which are compressed",
1097 "Uncompress parts which are compressed. GZIP for example. "
1098 "This supports export of the body and its further dissection.",
1099 &uncompress_data);
1100 #endif
1101
1102 /*
1103 * Dissectors requiring different behavior in cases where the media
1104 * is contained in a multipart entity should register their multipart
1105 * dissector in the dissector table below, which is similar to the
1106 * "media_type" dissector table defined in the HTTP dissector code.
1107 */
1108 multipart_media_subdissector_table = register_dissector_table(
1109 "multipart_media_type",
1110 "Internet media type (for multipart processing)",
1111 proto_multipart, FT_STRING, BASE_NONE);
1112 }
1113
1114
1115 /* If this dissector uses sub-dissector registration add a registration routine.
1116 This format is required because a script is used to find these routines and
1117 create the code that calls these routines.
1118 */
1119 void
proto_reg_handoff_multipart(void)1120 proto_reg_handoff_multipart(void)
1121 {
1122 dissector_handle_t multipart_handle;
1123
1124 /*
1125 * When we cannot display the data, call the data dissector.
1126 * When there is no dissector for the given media, call the media dissector.
1127 */
1128 media_handle = find_dissector_add_dependency("media", proto_multipart);
1129 gssapi_handle = find_dissector_add_dependency("gssapi", proto_multipart);
1130
1131 /*
1132 * Get the content type and Internet media type table
1133 */
1134 media_type_dissector_table = find_dissector_table("media_type");
1135
1136 /*
1137 * Handle for multipart dissection
1138 */
1139 multipart_handle = create_dissector_handle(
1140 dissect_multipart, proto_multipart);
1141
1142 dissector_add_string("media_type",
1143 "multipart/mixed", multipart_handle);
1144 dissector_add_string("media_type",
1145 "multipart/related", multipart_handle);
1146 dissector_add_string("media_type",
1147 "multipart/alternative", multipart_handle);
1148 dissector_add_string("media_type",
1149 "multipart/form-data", multipart_handle);
1150 dissector_add_string("media_type",
1151 "multipart/report", multipart_handle);
1152 dissector_add_string("media_type",
1153 "multipart/signed", multipart_handle);
1154 dissector_add_string("media_type",
1155 "multipart/encrypted", multipart_handle);
1156
1157 /*
1158 * Supply an entry to use for unknown multipart subtype.
1159 * See RFC 2046, section 5.1.3
1160 */
1161 dissector_add_string("media_type",
1162 "multipart/", multipart_handle);
1163 }
1164
1165 /*
1166 * Editor modelines - https://www.wireshark.org/tools/modelines.html
1167 *
1168 * Local variables:
1169 * c-basic-offset: 4
1170 * tab-width: 8
1171 * indent-tabs-mode: nil
1172 * End:
1173 *
1174 * vi: set shiftwidth=4 tabstop=8 expandtab:
1175 * :indentSize=4:tabSize=8:noTabs=true:
1176 */
1177