1 /* packet-ippusb.c
2  * Routines for IPPUSB packet disassembly
3  * https://robots.org.uk/IPPOverUSB
4  *
5  * Jamie Hare <jamienh@umich.edu>
6  *
7  * PROTONAME: Internet Printing Protocol Over USB
8  * PROTOSHORTNAME: IPPUSB
9  * PROTOABBREV: ippusb
10  *
11  * Wireshark - Network traffic analyzer
12  * By Gerald Combs <gerald@wireshark.org>
13  * Copyright 1998 Gerald Combs
14  *
15  * SPDX-License-Identifier: GPL-2.0-or-later
16  */
17 
18 #include "config.h"
19 
20 #include <epan/packet.h>
21 #include <epan/strutil.h>
22 #include <epan/to_str.h>
23 #include <epan/conversation.h>
24 #include <epan/wmem_scopes.h>
25 #include <reassemble.h>
26 #include <packet-usb.h>
27 
28 /*
29  * IPPUSB transfer_type values
30  */
31 #define HTTP 0
32 
33 /* As also defined in IPP dissector */
34 #define PRINT_JOB              0x0002
35 #define SEND_DOCUMENT          0x0006
36 
37 #define TAG_END_OF_ATTRIBUTES 0x03
38 #define NEWLINE 0x0a
39 
40 #define CHUNK_LENGTH_MIN 5
41 
42 #define BITS_PER_BYTE 8
43 
44 static const guint8 CHUNKED_END[] = { 0x30, 0x0d, 0x0a, 0x0d, 0x0a };
45 static const guint8 RETURN_NEWLINE[] = { 0x0d, 0x0a };
46 static tvbuff_t *return_newline_tvb = NULL;
47 
48 void proto_register_ippusb(void);
49 void proto_reg_handoff_ippusb(void);
50 static gint is_http_header(guint first_linelen, const guchar *first_line);
51 
52 static gint proto_ippusb = -1;
53 static gint ett_ippusb = -1;
54 static gint ett_ippusb_as = -1;
55 static gint ett_ippusb_attr = -1;
56 static gint ett_ippusb_member = -1;
57 static gint ett_ippusb_fragment= -1;
58 static gint ett_ippusb_fragments = -1;
59 
60 /* For reassembly */
61 static gint32 ippusb_last_pdu = -1;
62 
63 static gint hf_ippusb_fragments = -1;
64 static gint hf_ippusb_fragment = -1;
65 static gint hf_ippusb_fragment_overlap = -1;
66 static gint hf_ippusb_fragment_overlap_conflict = -1;
67 static gint hf_ippusb_fragment_multiple_tails = -1;
68 static gint hf_ippusb_fragment_too_long_fragment = -1;
69 static gint hf_ippusb_fragment_error = -1;
70 static gint hf_ippusb_fragment_count = -1;
71 static gint hf_ippusb_reassembled_in = -1;
72 static gint hf_ippusb_reassembled_length = -1;
73 static gint hf_ippusb_reassembled_data = -1;
74 
75 /* Reassemble by default */
76 static gboolean global_ippusb_reassemble = TRUE;
77 
78 static const fragment_items ippusb_frag_items = {
79     &ett_ippusb_fragment,
80     &ett_ippusb_fragments,
81     &hf_ippusb_fragments,
82     &hf_ippusb_fragment,
83     &hf_ippusb_fragment_overlap,
84     &hf_ippusb_fragment_overlap_conflict,
85     &hf_ippusb_fragment_multiple_tails,
86     &hf_ippusb_fragment_too_long_fragment,
87     &hf_ippusb_fragment_error,
88     &hf_ippusb_fragment_count,
89     &hf_ippusb_reassembled_in,
90     &hf_ippusb_reassembled_length,
91     &hf_ippusb_reassembled_data,
92     "IPPUSB fragments"
93 };
94 
95 struct ippusb_multisegment_pdu {
96     guint nxtpdu;
97     guint32 first_frame;
98     guint32 running_size;
99     gboolean finished;
100     gboolean reassembled;
101     gboolean is_ipp;
102 
103     guint32 document;
104     #define MSP_HAS_DOCUMENT        0x00000001
105     #define MSP_DOCUMENT_TRUNCATED  0x00000002
106 
107     guint32 flags;
108     #define MSP_FLAGS_REASSEMBLE_ENTIRE_SEGMENT	0x00000001
109     #define MSP_FLAGS_GOT_ALL_SEGMENTS          0x00000002
110     #define MSP_FLAGS_MISSING_FIRST_SEGMENT     0x00000004
111 };
112 
113 static struct ippusb_multisegment_pdu *
114 pdu_store(packet_info *pinfo, wmem_tree_t *multisegment_pdus, guint32 first_frame, gboolean is_ipp, guint document)
115 {
116     struct ippusb_multisegment_pdu *msp;
117 
118     msp = wmem_new(wmem_file_scope(), struct ippusb_multisegment_pdu);
119     msp->first_frame = first_frame;
120     msp->finished = FALSE;
121     msp->reassembled = FALSE;
122     msp->is_ipp = is_ipp;
123     msp->document = document;
124     msp->flags = 0;
125     wmem_tree_insert32(multisegment_pdus, pinfo->num, (void *)msp);
126 
127     return msp;
128 }
129 
130 struct ippusb_analysis {
131     wmem_tree_t *multisegment_pdus;
132 };
133 
134 static struct ippusb_analysis *
ListUsers(context.Context, *ListUsersInput, ...func(*Options))135 init_ippusb_conversation_data(void)
136 {
137     struct ippusb_analysis *ippusbd;
138 
139     ippusbd = wmem_new0(wmem_file_scope(), struct ippusb_analysis);
140 
141     ippusbd->multisegment_pdus = wmem_tree_new(wmem_file_scope());
142 
143     return ippusbd;
144 }
145 
146 static struct ippusb_analysis *
147 get_ippusb_conversation_data(conversation_t *conv, packet_info *pinfo)
148 {
149     struct ippusb_analysis *ippusbd;
150 
151     if(conv == NULL ) {
152         conv = find_or_create_conversation(pinfo);
153     }
154 
155     ippusbd = (struct ippusb_analysis *)conversation_get_proto_data(conv, proto_ippusb);
156 
157     if (!ippusbd) {
158         ippusbd = init_ippusb_conversation_data();
159         conversation_add_proto_data(conv, proto_ippusb, ippusbd);
160     }
161 
162     return ippusbd;
163 }
164 
165 
166 static gpointer ippusb_temporary_key(const packet_info *pinfo _U_, const guint32 id _U_, const void *data)
167 {
168     return (gpointer)data;
169 }
170 
171 static gpointer ippusb_persistent_key(const packet_info *pinfo _U_, const guint32 id _U_, const void *data)
172 {
173     return (gpointer)data;
174 }
175 
176 static void ippusb_free_temporary_key(gpointer ptr _U_) { }
177 
178 static void ippusb_free_persistent_key(gpointer ptr _U_) { }
179 
180 static reassembly_table_functions ippusb_reassembly_table_functions =
181 {
182     g_direct_hash,
183     g_direct_equal,
184     ippusb_temporary_key,
185     ippusb_persistent_key,
186     ippusb_free_temporary_key,
187     ippusb_free_persistent_key
188 };
189 
190 static dissector_table_t ippusb_dissector_table;
191 static reassembly_table ippusb_reassembly_table;
192 
193 /* Main dissector function */
194 static int
195 dissect_ippusb(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data)
196 {
197     gint offset = 0;
198     gint ret = 0;
199     guint first_linelen;
200     const guchar *first_line;
201     gint next_offset;
202     guint8 last;
203     guint8 status_code;
204     const guchar *last_chunk = NULL;
205     struct ippusb_analysis *ippusbd = NULL;
206     conversation_t *conv = NULL;
207 
208     struct ippusb_multisegment_pdu *new_msp = NULL;
209     struct ippusb_multisegment_pdu *current_msp = NULL;
210     struct ippusb_multisegment_pdu *previous_msp = NULL;
211 
212     gint reported_length = tvb_reported_length(tvb);
213     gint captured_length = tvb_captured_length(tvb);
214 
215     if((conv = find_conversation_pinfo(pinfo, 0)) != NULL) {
216         /* Update how far the conversation reaches */
217         if (pinfo->num > conv->last_frame) {
218             conv->last_frame = pinfo->num;
219         }
220     }
221     else {
222         conv = conversation_new(pinfo->num, &pinfo->src, &pinfo->dst, ENDPOINT_TCP,
223                      pinfo->srcport, pinfo->destport, 0);
224     }
225 
226     ippusbd = get_ippusb_conversation_data(conv, pinfo);
227 
228     first_linelen = tvb_find_line_end(tvb, offset, tvb_ensure_captured_length_remaining(tvb, offset), &next_offset, TRUE);
229     first_line = tvb_get_ptr(tvb, offset, first_linelen);
230 
231     /* Get last byte of segment */
232     last = tvb_get_guint8(tvb, captured_length - 1);
233     status_code = tvb_get_bits8(tvb, 3 * BITS_PER_BYTE, BITS_PER_BYTE);
234 
235     /* If segment has length of last chunk from chunk transfer */
236     if(captured_length == CHUNK_LENGTH_MIN){
237         last_chunk = tvb_get_ptr(tvb, offset, captured_length);
238     }
239 
240     if (is_http_header(first_linelen, first_line) && last == TAG_END_OF_ATTRIBUTES && status_code != PRINT_JOB && status_code != SEND_DOCUMENT) {
241         /* An indiviual ippusb packet with http header */
242 
243         proto_tree_add_item(tree, proto_ippusb, tvb, offset, -1, 0);
244 
245         if (ippusb_last_pdu >= 0 && !pinfo->fd->visited) {
246             ippusb_last_pdu = -1;
247         }
248 
249         ret = dissector_try_uint_new(ippusb_dissector_table, HTTP, tvb, pinfo, tree, TRUE, data);
250     }
251     else if (global_ippusb_reassemble) {
252         /* If reassembly is wanted */
253 
254         if (!pinfo->fd->visited) {
255             /* First time this segment is ever seen */
256 
257             gboolean save_fragmented = pinfo->fragmented;
258             pinfo->fragmented = TRUE;
259 
260             proto_tree_add_item(tree, proto_ippusb, tvb, offset, -1, 0);
261 
262             if (is_http_header(first_linelen, first_line)) {
263                 /* The start of a new packet that will need to be reassembled */
264 
265                 new_msp = pdu_store(pinfo, ippusbd->multisegment_pdus, pinfo->num, TRUE, 0);
266                 new_msp->running_size = captured_length;
267 
268                 fragment_add_check(&ippusb_reassembly_table, tvb, offset, pinfo, new_msp->first_frame,
269                                             GUINT_TO_POINTER(new_msp->first_frame), 0, captured_length, TRUE);
270 
271                 ippusb_last_pdu = pinfo->num;
272             }
273             else {
274 
275                 previous_msp = (struct ippusb_multisegment_pdu *)wmem_tree_lookup32_le(ippusbd->multisegment_pdus, ippusb_last_pdu);
276 
277                 if (previous_msp) {
278                     previous_msp->nxtpdu = pinfo->num;
279                     new_msp = pdu_store(pinfo, ippusbd->multisegment_pdus, previous_msp->first_frame, previous_msp->is_ipp, previous_msp->document);
280                     new_msp->running_size = previous_msp->running_size + captured_length;
281 
282                     /* This packet has an HTTP header but is not an ipp packet */
283                     if ((first_linelen >= strlen("Content-Type: ") && strncmp(first_line, "Content-Type: ", strlen("Content-Type: ")) == 0) &&
284                         (first_linelen < strlen("Content-Type: application/ipp") || strncmp(first_line, "Content-Type: application/ipp", strlen("Content-Type: application/ipp")) != 0)) {
285                         new_msp->is_ipp = FALSE;
286                     }
287 
288                     /* This packet will have an attached document */
289                     if (status_code == PRINT_JOB || status_code == SEND_DOCUMENT) {
290                         new_msp->document |= MSP_HAS_DOCUMENT;
291                     }
292 
293                     if(!(last_chunk && strncmp(last_chunk, CHUNKED_END, CHUNK_LENGTH_MIN) == 0)){
294                         /* If this segment is not the last chunk in a chunked transfer */
295 
296                         if (captured_length < reported_length && (new_msp->document & MSP_HAS_DOCUMENT)) {
297                             /* The attached document segment is smaller than it says it should be and cannot be reaseembled properly */
298 
299                             tvbuff_t *new_tvb = tvb_new_subset_length(tvb, 0, captured_length);
300 
301                             fragment_add_check(&ippusb_reassembly_table, new_tvb, offset, pinfo, new_msp->first_frame,
302                                             GUINT_TO_POINTER(new_msp->first_frame), previous_msp->running_size, captured_length, TRUE);
303 
304                             new_msp->document |= MSP_DOCUMENT_TRUNCATED;
305                         }
306                         else {
307                             fragment_add_check(&ippusb_reassembly_table, tvb, offset, pinfo, new_msp->first_frame,
308                                             GUINT_TO_POINTER(new_msp->first_frame), previous_msp->running_size, captured_length, TRUE);
309                         }
310 
311                         if (last != NEWLINE) {
312                             fragment_add_check(&ippusb_reassembly_table, return_newline_tvb, offset, pinfo, new_msp->first_frame,
313                                             GUINT_TO_POINTER(new_msp->first_frame), new_msp->running_size, sizeof(RETURN_NEWLINE), TRUE);
314 
315                             new_msp->running_size += sizeof(RETURN_NEWLINE);
316                         }
317 
318                         ippusb_last_pdu = pinfo->num;
319                     }
320                     else {
321                         /* This segment contains the end of ipp chunked transfer information */
322 
323                         new_msp->finished = TRUE;
324                         ippusb_last_pdu = -1;
325 
326                         fragment_head *head = fragment_add_check(&ippusb_reassembly_table, tvb, offset, pinfo, new_msp->first_frame,
327                                                             GUINT_TO_POINTER(new_msp->first_frame), previous_msp->running_size, captured_length, FALSE);
328                         tvbuff_t *processed_tvb = process_reassembled_data(tvb, offset, pinfo, "Reassembled IPPUSB", head, &ippusb_frag_items, NULL, tree);
329 
330                         new_msp->reassembled = TRUE;
331                         pinfo->can_desegment = 0;
332 
333                         if(processed_tvb){
334                             ret = dissector_try_uint_new(ippusb_dissector_table, HTTP, processed_tvb, pinfo, tree, TRUE, data);
335                             col_append_fstr(pinfo->cinfo, COL_INFO, " Reassembled Data");
336                         }
337                     }
338                 }
339 
340                 pinfo->fragmented = save_fragmented;
341             }
342         }
343         else {
344             /* Not the first time this segment is seen */
345 
346             gboolean save_fragmented = pinfo->fragmented;
347             pinfo->fragmented = TRUE;
348             current_msp = (struct ippusb_multisegment_pdu *)wmem_tree_lookup32_le(ippusbd->multisegment_pdus, pinfo->num);
349 
350             /* This is not an ipp packet */
351             if(current_msp && !(current_msp->is_ipp)){
352                 return captured_length;
353             }
354 
355             if (current_msp && !current_msp->finished && current_msp->nxtpdu == 0) {
356                 /* This is a packet that was not completed and assembly will be attempted */
357 
358                 proto_tree_add_item(tree, proto_ippusb, tvb, offset, -1, 0);
359                 fragment_head *head;
360 
361                 if (!current_msp->reassembled) {
362                     /* The first time this segment is passed over after the initial round
363                      * it will be added to the pdu and reassembled */
364 
365                     pinfo->fd->visited = FALSE;
366 
367                     if (captured_length < reported_length && (current_msp->document & MSP_HAS_DOCUMENT)) {
368                         /* The attached document segment is smaller than it says it should be and cannot be reaseembled properly */
369 
370                         tvbuff_t *new_tvb = tvb_new_subset_length(tvb, 0, captured_length);
371 
372                         head = fragment_add_check(&ippusb_reassembly_table, new_tvb, offset, pinfo, current_msp->first_frame,
373                                             GUINT_TO_POINTER(current_msp->first_frame), current_msp->running_size - captured_length, captured_length, FALSE);
374 
375                         current_msp->document |= MSP_DOCUMENT_TRUNCATED;
376                     }
377                     else {
378                          head = fragment_add_check(&ippusb_reassembly_table, tvb, 0, pinfo, current_msp->first_frame,
379                                             GUINT_TO_POINTER(current_msp->first_frame), current_msp->running_size - captured_length, captured_length, FALSE);
380                     }
381 
382                     pinfo->fd->visited = TRUE;
383 
384                     current_msp->reassembled = TRUE;
385                 }
386                 else {
387                     /* Packet has already been reassembled */
388 
389                     head = fragment_get_reassembled_id(&ippusb_reassembly_table, pinfo, current_msp->first_frame);
390                 }
391 
392                 tvbuff_t *processed_tvb = process_reassembled_data(tvb, offset, pinfo, " Reassembled IPPUSB", head, &ippusb_frag_items, NULL, tree);
393 
394                 if (processed_tvb) {
395                     pinfo->can_desegment = 0;
396 
397                     ret = dissector_try_uint_new(ippusb_dissector_table, HTTP, processed_tvb, pinfo, tree, TRUE, data);
398 
399                     if (current_msp->document & MSP_DOCUMENT_TRUNCATED) {
400                         col_append_fstr(pinfo->cinfo, COL_INFO, " Document Truncated");
401                     }
402                 }
403             }
404             else if (current_msp &&last_chunk && strncmp(last_chunk, CHUNKED_END, CHUNK_LENGTH_MIN) == 0) {
405                 /* This is the last segment of the chunked transfer and reassembled packet */
406 
407                 proto_tree_add_item(tree, proto_ippusb, tvb, offset, -1, 0);
408 
409                 fragment_head *head = fragment_get_reassembled_id(&ippusb_reassembly_table, pinfo, current_msp->first_frame);
410 
411                 tvbuff_t *processed_tvb = process_reassembled_data(tvb, offset, pinfo, " Reassembled IPPUSB", head, &ippusb_frag_items, NULL, tree);
412 
413                 if (processed_tvb) {
414                     pinfo->can_desegment = 0;
415 
416                     ret = dissector_try_uint_new(ippusb_dissector_table, HTTP, processed_tvb, pinfo, tree, TRUE, data);
417 
418                     col_append_fstr(pinfo->cinfo, COL_INFO, " Reassembled Data");
419 
420                     /* If the document was truncated mark it as such in the UX */
421                     if (current_msp->document & MSP_DOCUMENT_TRUNCATED) {
422                         col_append_fstr(pinfo->cinfo, COL_INFO, " Document Truncated");
423                     }
424                 }
425             }
426 
427             pinfo->fragmented = save_fragmented;
428         }
429     }
430 
431     if (ret) {
432         return tvb_captured_length(tvb);
433     }
434     else {
435         return 0;
436     }
437 }
438 
439 static gint
440 is_http_header(guint first_linelen, const guchar *first_line) {
441     if ((first_linelen >= strlen("HTTP/") && strncmp(first_line, "HTTP/", strlen("HTTP/")) == 0) ||
442         (first_linelen >= strlen("POST /ipp") && strncmp(first_line, "POST /ipp", strlen("POST /ipp")) == 0) ||
443         (first_linelen >= strlen("POST / HTTP") && strncmp(first_line, "POST / HTTP", strlen("POST / HTTP")) == 0)) {
444 
445         return TRUE;
446     }
447     else {
448         return FALSE;
449     }
450 }
451 
452 static void
453 ippusb_shutdown(void) {
454     tvb_free(return_newline_tvb);
455 }
456 
457 void
458 proto_register_ippusb(void)
459 {
460     static hf_register_info hf[] = {
461 
462         /* Reassembly */
463         { &hf_ippusb_fragment,
464             { "Fragment", "ippusb.fragment", FT_FRAMENUM, BASE_NONE,
465             NULL, 0x0, NULL, HFILL }},
466         { &hf_ippusb_fragments,
467             { "Fragments", "ippusb.fragments", FT_BYTES, BASE_NONE,
468             NULL, 0x0, NULL, HFILL }},
469         { &hf_ippusb_fragment_overlap,
470             { "Fragment overlap", "ippusb.fragment.overlap", FT_BOOLEAN, BASE_NONE,
471             NULL, 0x0, "Fragment overlaps with other fragments", HFILL }},
472         { &hf_ippusb_fragment_overlap_conflict,
473             { "Conflicting data in fragment overlap", "ippusb.fragment.overlap.conflict",
474             FT_BOOLEAN, BASE_NONE, NULL, 0x0,
475             "Overlapping fragments contained conflicting data", HFILL }},
476         { &hf_ippusb_fragment_multiple_tails,
477             { "Multiple tail fragments found", "ippusb.fragment.multipletails",
478             FT_BOOLEAN, BASE_NONE, NULL, 0x0,
479             "Several tails were found when defragmenting the packet", HFILL }},
480         { &hf_ippusb_fragment_too_long_fragment,
481             { "Fragment too long", "ippusb.fragment.toolongfragment",
482             FT_BOOLEAN, BASE_NONE, NULL, 0x0,
483             "Fragment contained data past end of packet", HFILL }},
484         { &hf_ippusb_fragment_error,
485             { "Defragmentation error", "ippusb.fragment.error", FT_FRAMENUM, BASE_NONE,
486             NULL, 0x0, "Defragmentation error due to illegal fragments", HFILL }},
487         { &hf_ippusb_fragment_count,
488             { "Fragment count", "ippusb.fragment.count", FT_UINT32, BASE_DEC,
489             NULL, 0x0, NULL, HFILL }},
490         { &hf_ippusb_reassembled_in,
491             { "Reassembled payload in frame", "ippusb.reassembled_in", FT_FRAMENUM, BASE_NONE,
492             NULL, 0x0, "This payload packet is reassembled in this frame", HFILL }},
493         { &hf_ippusb_reassembled_length,
494             { "Reassembled payload length", "ippusb.reassembled.length", FT_UINT32, BASE_DEC,
495             NULL, 0x0, "The total length of the reassembled payload", HFILL }},
496         { &hf_ippusb_reassembled_data,
497             { "Reassembled data", "ippusb.reassembled.data", FT_BYTES, BASE_NONE,
498             NULL, 0x0, "The reassembled payload", HFILL }},
499         };
500 
501    static gint *ett[] = {
502         &ett_ippusb,
503         &ett_ippusb_as,
504         &ett_ippusb_attr,
505         &ett_ippusb_member,
506         &ett_ippusb_fragments,
507         &ett_ippusb_fragment
508     };
509 
510     proto_ippusb = proto_register_protocol("Internet Printing Protocol Over USB", "IPPUSB", "ippusb");
511 
512     ippusb_dissector_table = register_dissector_table("ippusb", "IPP Over USB", proto_ippusb, FT_UINT8, BASE_DEC);
513 
514     proto_register_field_array(proto_ippusb, hf, array_length(hf));
515     proto_register_subtree_array(ett, array_length(ett));
516 
517     /* Register reassembly table. */
518     reassembly_table_register(&ippusb_reassembly_table, &ippusb_reassembly_table_functions);
519 
520     /* Preferences */
521      module_t *ippusb_module = prefs_register_protocol(proto_ippusb, NULL);
522 
523     /* Reassembly, made an option due to memory costs */
524     prefs_register_bool_preference(ippusb_module, "attempt_reassembly", "Reassemble payload", "", &global_ippusb_reassemble);
525 
526     return_newline_tvb = tvb_new_real_data(RETURN_NEWLINE, sizeof(RETURN_NEWLINE), sizeof(RETURN_NEWLINE));
527 
528     register_shutdown_routine(ippusb_shutdown);
529 }
530 
531 void
532 proto_reg_handoff_ippusb(void)
533 {
534     dissector_handle_t ippusb_handle;
535 
536     ippusb_handle = create_dissector_handle(dissect_ippusb, proto_ippusb);
537     dissector_add_uint("usb.bulk", IF_CLASS_PRINTER, ippusb_handle);
538 }
539 
540 /*
541  * Editor modelines  -  https://www.wireshark.org/tools/modelines.html
542  *
543  * Local variables:
544  * c-basic-offset: 4
545  * tab-width: 8
546  * indent-tabs-mode: nil
547  * End:
548  *
549  * vi: set shiftwidth=4 tabstop=8 expandtab:
550  * :indentSize=4:tabSize=8:noTabs=true:
551  */
552