/src/wireshark/epan/dissectors/packet-imap.c
Line | Count | Source |
1 | | /* packet-imap.c |
2 | | * Routines for imap packet dissection |
3 | | * Copyright 1999, Richard Sharpe <rsharpe@ns.aus.com> |
4 | | * |
5 | | * Wireshark - Network traffic analyzer |
6 | | * By Gerald Combs <gerald@wireshark.org> |
7 | | * Copyright 1998 Gerald Combs |
8 | | * |
9 | | * Copied from packet-tftp.c |
10 | | * |
11 | | * SPDX-License-Identifier: GPL-2.0-or-later |
12 | | */ |
13 | | |
14 | | #include "config.h" |
15 | | |
16 | | |
17 | | #include <epan/packet.h> |
18 | | #include <epan/strutil.h> |
19 | | #include "packet-tls.h" |
20 | | #include "packet-tls-utils.h" |
21 | | #include <epan/credentials.h> |
22 | | #include <tap.h> |
23 | | |
24 | | void proto_register_imap(void); |
25 | | void proto_reg_handoff_imap(void); |
26 | | |
27 | | static int proto_imap; |
28 | | static int hf_imap_isrequest; |
29 | | static int hf_imap_line; |
30 | | static int hf_imap_request; |
31 | | static int hf_imap_request_tag; |
32 | | static int hf_imap_response; |
33 | | static int hf_imap_response_tag; |
34 | | static int hf_imap_request_command; |
35 | | static int hf_imap_response_command; |
36 | | static int hf_imap_tag; |
37 | | static int hf_imap_command; |
38 | | static int hf_imap_response_status; |
39 | | static int hf_imap_request_folder; |
40 | | static int hf_imap_request_username; |
41 | | static int hf_imap_request_password; |
42 | | static int hf_imap_request_uid; |
43 | | static int hf_imap_response_in; |
44 | | static int hf_imap_response_to; |
45 | | static int hf_imap_time; |
46 | | |
47 | | static int ett_imap; |
48 | | static int ett_imap_reqresp; |
49 | | |
50 | | static int credentials_tap; |
51 | | |
52 | | static dissector_handle_t imap_handle; |
53 | | static dissector_handle_t tls_handle; |
54 | | static dissector_handle_t imf_handle; |
55 | | |
56 | | static bool imap_ssl_heuristic = true; |
57 | | |
58 | | /* patterns used for tvb_ws_mempbrk_pattern_uint8 */ |
59 | | static ws_mempbrk_pattern pbrk_whitespace; |
60 | | |
61 | 14 | #define TCP_PORT_IMAP 143 |
62 | 14 | #define TCP_PORT_SSL_IMAP 993 |
63 | 109 | #define IMAP_HEUR_LEN 5 |
64 | 136 | #define NUM_LOOKAHEAD_TOKENS 3 |
65 | | |
66 | | struct simple_token_info |
67 | | { |
68 | | uint8_t* token; |
69 | | unsigned token_start_offset; |
70 | | unsigned token_end_offset; |
71 | | }; |
72 | | |
73 | | typedef struct imap_state { |
74 | | bool ssl_requested; |
75 | | int ssl_heur_tries_left; |
76 | | } imap_state_t; |
77 | | |
78 | | typedef struct imap_request_key { |
79 | | char* tag; |
80 | | uint32_t conversation; |
81 | | } imap_request_key_t; |
82 | | |
83 | | typedef struct imap_request_val { |
84 | | wmem_tree_t *frames; |
85 | | } imap_request_val_t; |
86 | | |
87 | | typedef struct { |
88 | | uint32_t req_num; |
89 | | uint32_t rep_num; |
90 | | nstime_t req_time; |
91 | | } imap_request_info_t; |
92 | | |
93 | | static wmem_map_t *imap_requests; |
94 | | |
95 | | static int |
96 | | imap_request_equal(const void *v, const void *w) |
97 | 172 | { |
98 | 172 | const imap_request_key_t *v1 = (const imap_request_key_t*)v; |
99 | 172 | const imap_request_key_t *v2 = (const imap_request_key_t*)w; |
100 | | |
101 | 172 | if ((v1->conversation == v2->conversation) && |
102 | 172 | (!strcmp(v1->tag, v2->tag))) |
103 | 172 | return 1; |
104 | | |
105 | 0 | return 0; |
106 | 172 | } |
107 | | |
108 | | static unsigned |
109 | | imap_request_hash(const void *v) |
110 | 998 | { |
111 | 998 | const imap_request_key_t *key = (const imap_request_key_t*)v; |
112 | 998 | unsigned val; |
113 | | |
114 | 998 | val = (unsigned)(wmem_str_hash(key->tag) * 37 + key->conversation * 765); |
115 | | |
116 | 998 | return val; |
117 | 998 | } |
118 | | |
119 | | static void |
120 | | imap_match_request(packet_info *pinfo, proto_tree *tree, imap_request_key_t *request_key, bool is_request) |
121 | 978 | { |
122 | 978 | imap_request_key_t *new_request_key; |
123 | 978 | imap_request_val_t *request_val; |
124 | 978 | imap_request_info_t *request_info = NULL; |
125 | | |
126 | 978 | request_info = NULL; |
127 | 978 | request_val = (imap_request_val_t *)wmem_map_lookup(imap_requests, request_key); |
128 | 978 | if (!pinfo->fd->visited) |
129 | 978 | { |
130 | 978 | if (is_request) |
131 | 206 | { |
132 | 206 | if (request_val == NULL) |
133 | 34 | { |
134 | 34 | new_request_key = (imap_request_key_t *)wmem_memdup(wmem_file_scope(), request_key, sizeof(imap_request_key_t)); |
135 | 34 | new_request_key->tag = wmem_strdup(wmem_file_scope(), request_key->tag); |
136 | | |
137 | 34 | request_val = wmem_new(wmem_file_scope(), imap_request_val_t); |
138 | 34 | request_val->frames = wmem_tree_new(wmem_file_scope()); |
139 | | |
140 | 34 | wmem_map_insert(imap_requests, new_request_key, request_val); |
141 | 34 | } |
142 | | |
143 | 206 | request_info = wmem_new(wmem_file_scope(), imap_request_info_t); |
144 | 206 | request_info->req_num = pinfo->num; |
145 | 206 | request_info->rep_num = 0; |
146 | 206 | request_info->req_time = pinfo->abs_ts; |
147 | 206 | wmem_tree_insert32(request_val->frames, pinfo->num, (void *)request_info); |
148 | 206 | } |
149 | 978 | if (request_val && !is_request) |
150 | 0 | { |
151 | 0 | request_info = (imap_request_info_t*)wmem_tree_lookup32_le(request_val->frames, pinfo->num); |
152 | 0 | if (request_info) |
153 | 0 | { |
154 | 0 | request_info->rep_num = pinfo->num; |
155 | 0 | } |
156 | 0 | } |
157 | 978 | } |
158 | 0 | else |
159 | 0 | { |
160 | 0 | if (request_val) |
161 | 0 | request_info = (imap_request_info_t *)wmem_tree_lookup32_le(request_val->frames, pinfo->num); |
162 | 0 | } |
163 | | |
164 | 978 | if (tree && request_info) |
165 | 206 | { |
166 | 206 | proto_item *it; |
167 | | |
168 | | /* print request/response tracking in the tree */ |
169 | 206 | if (is_request) |
170 | 206 | { |
171 | | /* This is a request */ |
172 | 206 | if (request_info->rep_num) |
173 | 0 | { |
174 | |
|
175 | 0 | it = proto_tree_add_uint(tree, hf_imap_response_in, NULL, 0, 0, request_info->rep_num); |
176 | 0 | proto_item_set_generated(it); |
177 | 0 | } |
178 | 206 | } |
179 | 0 | else |
180 | 0 | { |
181 | | /* This is a reply */ |
182 | 0 | if (request_info->req_num) |
183 | 0 | { |
184 | 0 | nstime_t ns; |
185 | |
|
186 | 0 | it = proto_tree_add_uint(tree, hf_imap_response_to, NULL, 0, 0, request_info->req_num); |
187 | 0 | proto_item_set_generated(it); |
188 | |
|
189 | 0 | nstime_delta(&ns, &pinfo->abs_ts, &request_info->req_time); |
190 | 0 | it = proto_tree_add_time(tree, hf_imap_time, NULL, 0, 0, &ns); |
191 | 0 | proto_item_set_generated(it); |
192 | 0 | } |
193 | 0 | } |
194 | 206 | } |
195 | | |
196 | 978 | } |
197 | | |
198 | | static bool |
199 | | dissect_imap_fetch(tvbuff_t *tvb, packet_info *pinfo, |
200 | | proto_tree* main_tree, proto_tree* imap_tree, proto_tree** reqresp_tree, |
201 | | unsigned fetch_offset, unsigned offset, unsigned* next_offset, bool* first_line) |
202 | 0 | { |
203 | 0 | tvbuff_t *next_tvb; |
204 | 0 | bool need_more = true; |
205 | | |
206 | | //All information in encapsulated in () so make sure there are existing and matching parenthesis |
207 | 0 | unsigned first_parenthesis; |
208 | 0 | if (tvb_find_uint8_remaining(tvb, fetch_offset, '(', &first_parenthesis)) |
209 | 0 | { |
210 | 0 | unsigned remaining_size = tvb_reported_length_remaining(tvb, first_parenthesis + 1); |
211 | 0 | if (remaining_size > 0) |
212 | 0 | { |
213 | | //look for the size field |
214 | 0 | unsigned size_start; |
215 | 0 | if (tvb_find_uint8_length(tvb, first_parenthesis, remaining_size, '{', &size_start)) |
216 | 0 | { |
217 | 0 | unsigned size_end; |
218 | 0 | if (tvb_find_uint8_length(tvb, size_start + 1, remaining_size - (size_start - first_parenthesis), '}', &size_end)) |
219 | 0 | { |
220 | | //Have a size field, convert it to an integer to see how long the contents are |
221 | 0 | uint32_t size = 0; |
222 | 0 | if (tvb_get_string_uint(tvb, size_start + 1, size_end - size_start - 1, ENC_STR_DEC, &size, NULL)) |
223 | 0 | { |
224 | 0 | unsigned remaining = tvb_reported_length_remaining(tvb, size_end + size); |
225 | 0 | if (remaining > 0) |
226 | 0 | { |
227 | | //Look for the ) after the size field |
228 | 0 | unsigned parenthesis_end; |
229 | 0 | if (tvb_find_uint8_length(tvb, size_end + size, remaining, ')', &parenthesis_end)) |
230 | 0 | { |
231 | 0 | need_more = false; |
232 | | |
233 | | // Put the line into the protocol tree. |
234 | 0 | proto_item *ti = proto_tree_add_item(imap_tree, hf_imap_line, tvb, offset, *next_offset - offset, ENC_ASCII); |
235 | 0 | *reqresp_tree = proto_item_add_subtree(ti, ett_imap_reqresp); |
236 | | |
237 | | //no need to overwrite column information since subdissector was called |
238 | 0 | *first_line = false; |
239 | |
|
240 | 0 | next_tvb = tvb_new_subset_length(tvb, *next_offset, size); |
241 | 0 | call_dissector(imf_handle, next_tvb, pinfo, main_tree); |
242 | 0 | if ((*next_offset + size) > *next_offset) |
243 | 0 | (*next_offset) += size; |
244 | 0 | } |
245 | 0 | } |
246 | 0 | } |
247 | 0 | } |
248 | 0 | } |
249 | 0 | else |
250 | 0 | { |
251 | | //See if there is no size field, just and end of line |
252 | 0 | unsigned linelen; |
253 | 0 | if (tvb_find_line_end_remaining(tvb, first_parenthesis, &linelen, next_offset)) |
254 | 0 | { |
255 | 0 | need_more = false; |
256 | | |
257 | | // Put the line into the protocol tree. |
258 | 0 | proto_item *ti = proto_tree_add_item(imap_tree, hf_imap_line, tvb, offset, *next_offset - offset, ENC_ASCII); |
259 | 0 | *reqresp_tree = proto_item_add_subtree(ti, ett_imap_reqresp); |
260 | 0 | } |
261 | 0 | } |
262 | 0 | } |
263 | 0 | } |
264 | |
|
265 | 0 | return need_more; |
266 | 0 | } |
267 | | |
268 | | /* Heuristic to detect plaintext or TLS ciphertext IMAP */ |
269 | | static bool |
270 | | check_imap_heur(tvbuff_t *tvb) |
271 | 56 | { |
272 | 56 | if (!tvb_bytes_exist(tvb, 0, IMAP_HEUR_LEN)) { |
273 | 3 | return true; |
274 | 3 | } |
275 | | |
276 | 53 | if (!tvb_ascii_isprint(tvb, 0, IMAP_HEUR_LEN)) |
277 | 4 | return false; |
278 | | |
279 | 49 | return true; |
280 | 53 | } |
281 | | |
282 | | static int |
283 | | dissect_imap(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_) |
284 | 66 | { |
285 | 66 | bool is_request; |
286 | 66 | proto_tree *imap_tree, *reqresp_tree; |
287 | 66 | proto_item *ti, *hidden_item; |
288 | 66 | unsigned offset = 0; |
289 | 66 | unsigned uid_offset = 0; |
290 | 66 | unsigned folder_offset = 0; |
291 | 66 | unsigned next_offset; |
292 | 66 | unsigned linelen, tokenlen, uidlen, uid_tokenlen, folderlen, folder_tokenlen; |
293 | 66 | unsigned next_token, uid_next_token, folder_next_token; |
294 | 66 | const char *tokenbuf = NULL; |
295 | 66 | const char *command_token = NULL; |
296 | 66 | unsigned commandlen; |
297 | 66 | bool first_line = true; |
298 | 66 | imap_request_key_t request_key; |
299 | | |
300 | 66 | conversation_t *conversation; |
301 | 66 | imap_state_t *session_state; |
302 | | |
303 | 66 | conversation = find_or_create_conversation(pinfo); |
304 | 66 | session_state = (imap_state_t *)conversation_get_proto_data(conversation, proto_imap); |
305 | 66 | if (!session_state) { |
306 | 49 | session_state = wmem_new0(wmem_file_scope(), imap_state_t); |
307 | 49 | session_state->ssl_requested = false; |
308 | 49 | if (imap_ssl_heuristic) |
309 | 49 | session_state->ssl_heur_tries_left = 2; |
310 | 0 | else |
311 | 0 | session_state->ssl_heur_tries_left = -1; /* Disabled */ |
312 | 49 | conversation_add_proto_data(conversation, proto_imap, session_state); |
313 | 49 | } |
314 | | |
315 | 66 | request_key.tag = NULL; |
316 | 66 | request_key.conversation = conversation->conv_index; |
317 | | |
318 | 66 | if (imap_ssl_heuristic && session_state->ssl_heur_tries_left < 0) { |
319 | | /* Preference changed to enabled */ |
320 | 0 | session_state->ssl_heur_tries_left = 2; |
321 | 0 | } |
322 | 66 | else if (!imap_ssl_heuristic && session_state->ssl_heur_tries_left >= 0) { |
323 | | /* Preference changed to disabled */ |
324 | 0 | session_state->ssl_heur_tries_left = -1; |
325 | 0 | } |
326 | | |
327 | | /* |
328 | | * It is possible the IMAP session is already running over TLS and the |
329 | | * STARTTLS request/response happened before the capture began. Don't assume |
330 | | * we have plaintext without performing some heuristic checks first. |
331 | | * We have three cases: |
332 | | * 1. capture includes STARTTLS command: no need for heuristics |
333 | | * 2. capture starts with STARTTLS OK response: next frame will be TLS (need to retry heuristic) |
334 | | * 3. capture start after STARTTLS negotiation: current frame is TLS |
335 | | */ |
336 | 66 | if (session_state->ssl_heur_tries_left > 0) { |
337 | 56 | session_state->ssl_heur_tries_left--; |
338 | 56 | if (!check_imap_heur(tvb)) { |
339 | 4 | ssl_starttls_post_ack(tls_handle, pinfo, imap_handle); |
340 | 4 | session_state->ssl_heur_tries_left = 0; |
341 | 4 | return call_dissector(tls_handle, tvb, pinfo, tree); |
342 | 4 | } |
343 | 56 | } |
344 | | |
345 | 62 | col_set_str(pinfo->cinfo, COL_PROTOCOL, "IMAP"); |
346 | | |
347 | 62 | if (pinfo->match_uint == pinfo->destport) |
348 | 26 | is_request = true; |
349 | 36 | else |
350 | 36 | is_request = false; |
351 | | |
352 | | /* |
353 | | * Put the first line from the buffer into the summary |
354 | | * (but leave out the line terminator). |
355 | | */ |
356 | 62 | if (!tvb_find_line_end_remaining(tvb, offset, &linelen, &next_offset)) |
357 | 7 | { |
358 | 7 | pinfo->desegment_offset = 0; |
359 | 7 | pinfo->desegment_len = DESEGMENT_ONE_MORE_SEGMENT; |
360 | 7 | return tvb_captured_length(tvb); |
361 | 7 | } |
362 | | |
363 | 55 | ti = proto_tree_add_item(tree, proto_imap, tvb, offset, -1, ENC_NA); |
364 | 55 | imap_tree = proto_item_add_subtree(ti, ett_imap); |
365 | | |
366 | 55 | hidden_item = proto_tree_add_boolean(imap_tree, hf_imap_isrequest, tvb, 0, 0, is_request); |
367 | 55 | proto_item_set_hidden(hidden_item); |
368 | | |
369 | 1.54k | while(tvb_offset_exists(tvb, offset)) { |
370 | | |
371 | 1.53k | commandlen = 0; |
372 | 1.53k | folder_offset = 0; |
373 | 1.53k | folder_tokenlen = 0; |
374 | | |
375 | | /* |
376 | | * Find the end of each line |
377 | | * |
378 | | * Note that "tvb_find_line_end_remaining()" will return a value that is |
379 | | * not longer than what's in the buffer, so the "tvb_get_ptr()" |
380 | | * call won't throw an exception. |
381 | | */ |
382 | 1.53k | if (!tvb_find_line_end_remaining(tvb, offset, &linelen, &next_offset)) |
383 | 51 | { |
384 | 51 | pinfo->desegment_offset = offset; |
385 | 51 | pinfo->desegment_len = DESEGMENT_ONE_MORE_SEGMENT; |
386 | 51 | return tvb_captured_length(tvb); |
387 | 51 | } |
388 | | |
389 | | /* |
390 | | * Check that the line doesn't begin with '*', because that's a continuation line. |
391 | | * Otherwise if a tag is present then extract tokens. |
392 | | */ |
393 | 1.48k | if (tvb_get_uint8(tvb, offset) == '*') { |
394 | 47 | bool show_line = true; |
395 | | |
396 | | //find up to NUM_LOOKAHEAD_TOKENS tokens |
397 | 47 | unsigned start_offset; |
398 | 47 | unsigned next_pattern = offset, token_count = 0; |
399 | 47 | struct simple_token_info tokens[NUM_LOOKAHEAD_TOKENS]; |
400 | 47 | bool found; |
401 | 47 | do |
402 | 160 | { |
403 | 160 | start_offset = next_pattern+1; |
404 | 160 | found = tvb_ws_mempbrk_uint8_length(tvb, start_offset, next_offset - start_offset, &pbrk_whitespace, &next_pattern, NULL); |
405 | 160 | if (next_pattern > start_offset) |
406 | 94 | { |
407 | 94 | tokens[token_count].token = tvb_get_string_enc(pinfo->pool, tvb, start_offset, next_pattern-start_offset, ENC_ASCII); |
408 | 94 | tokens[token_count].token_start_offset = start_offset; |
409 | 94 | tokens[token_count].token_end_offset = next_pattern; |
410 | 94 | token_count++; |
411 | 94 | } |
412 | 160 | } while ((found != false) && (token_count < NUM_LOOKAHEAD_TOKENS)); |
413 | | |
414 | 47 | if (token_count >= 2) |
415 | 28 | { |
416 | 28 | bool need_more = false; |
417 | 107 | for (unsigned token = 0; token < token_count; token++) |
418 | 79 | { |
419 | 79 | if (!tvb_strncaseeql(tvb, tokens[token].token_start_offset, "FETCH", tokens[token].token_end_offset - tokens[token].token_start_offset)) |
420 | 0 | { |
421 | | //FETCH command. Presume we need more data until we find a complete command |
422 | 0 | need_more = dissect_imap_fetch(tvb, pinfo, tree, imap_tree, &reqresp_tree, |
423 | 0 | tokens[token].token_end_offset, offset, &next_offset, &first_line); |
424 | 0 | if (!need_more) |
425 | 0 | { |
426 | 0 | show_line = false; |
427 | 0 | } |
428 | 0 | break; |
429 | 0 | } |
430 | 79 | } |
431 | | |
432 | 28 | if (need_more) |
433 | 0 | { |
434 | 0 | pinfo->desegment_offset = offset; |
435 | 0 | pinfo->desegment_len = DESEGMENT_ONE_MORE_SEGMENT; |
436 | 0 | return tvb_captured_length(tvb); |
437 | 0 | } |
438 | 28 | } |
439 | | |
440 | 47 | if (show_line) |
441 | 47 | proto_tree_add_item(imap_tree, hf_imap_line, tvb, offset, next_offset - offset, ENC_ASCII); |
442 | | |
443 | 1.43k | } else { |
444 | | |
445 | 1.43k | if (first_line) { |
446 | 54 | col_add_fstr(pinfo->cinfo, COL_INFO, "%s: %s", is_request ? "Request" : "Response", tvb_format_text(pinfo->pool, tvb, offset, linelen)); |
447 | 54 | first_line = false; |
448 | 54 | } |
449 | | |
450 | | // Put the line into the protocol tree. |
451 | 1.43k | ti = proto_tree_add_item(imap_tree, hf_imap_line, tvb, offset, next_offset - offset, ENC_ASCII); |
452 | 1.43k | reqresp_tree = proto_item_add_subtree(ti, ett_imap_reqresp); |
453 | | |
454 | | /* |
455 | | * Show each line as requests or replies + tags. |
456 | | */ |
457 | | |
458 | | /* |
459 | | * Add the line as request or reply data. |
460 | | */ |
461 | 1.43k | if (linelen != 0) { |
462 | 366 | proto_tree_add_item(reqresp_tree, (is_request) ? hf_imap_request : hf_imap_response, tvb, offset, linelen, ENC_ASCII|ENC_NA); |
463 | 366 | } |
464 | | |
465 | | /* |
466 | | * Extract the first token, and, if there is a first |
467 | | * token, add it as the request or reply tag. |
468 | | */ |
469 | 1.43k | if (tvb_get_token_len_length(tvb, offset, linelen, &tokenlen, &next_token)) { |
470 | 167 | const char* tag = (const char*)tvb_get_string_enc(pinfo->pool, tvb, offset, tokenlen, ENC_ASCII); |
471 | 167 | request_key.tag = wmem_ascii_strdown(pinfo->pool, tag, strlen(tag)); |
472 | | |
473 | 167 | proto_tree_add_string(reqresp_tree, (is_request) ? hf_imap_request_tag : hf_imap_response_tag, tvb, offset, tokenlen, tag); |
474 | 167 | hidden_item = proto_tree_add_string(reqresp_tree, hf_imap_tag, tvb, offset, tokenlen, tag); |
475 | 167 | proto_item_set_hidden(hidden_item); |
476 | | |
477 | 167 | linelen -= (next_token-offset); |
478 | 167 | offset = next_token; |
479 | 167 | } |
480 | | |
481 | | /* |
482 | | * Extract second token, and, if there is a second |
483 | | * token, and it's not uid, add it as the request or reply command. |
484 | | */ |
485 | 1.43k | if (tvb_get_token_len_length(tvb, offset, linelen, &tokenlen, &next_token)) { |
486 | | |
487 | 130 | tokenbuf = (const char*)tvb_get_string_enc(pinfo->pool, tvb, offset, tokenlen, ENC_ASCII); |
488 | 130 | tokenbuf = wmem_ascii_strdown(pinfo->pool, tokenbuf, tokenlen); |
489 | | |
490 | 130 | if (is_request && !tvb_strncaseeql(tvb, offset, "UID", tokenlen)) { |
491 | 0 | proto_tree_add_item(reqresp_tree, hf_imap_request_uid, tvb, offset, tokenlen, ENC_ASCII|ENC_NA); |
492 | | /* |
493 | | * UID is a precursor to a command, if following the tag, |
494 | | * so move to next token to grab the actual command. |
495 | | */ |
496 | 0 | uidlen = linelen - (next_token - offset); |
497 | 0 | uid_offset = next_token; |
498 | 0 | if (tvb_get_token_len_length(tvb, next_token, uidlen, &uid_tokenlen, &uid_next_token)) { |
499 | 0 | proto_tree_add_item(reqresp_tree, hf_imap_request_command, tvb, uid_offset, uid_tokenlen, ENC_ASCII); |
500 | 0 | hidden_item = proto_tree_add_item(reqresp_tree, hf_imap_command, tvb, offset, tokenlen, ENC_ASCII); |
501 | 0 | proto_item_set_hidden(hidden_item); |
502 | | |
503 | | /* |
504 | | * Save command string to do specialized processing. |
505 | | */ |
506 | 0 | commandlen = uid_tokenlen; |
507 | 0 | command_token = (const char*)tvb_get_string_enc(pinfo->pool, tvb, next_token, commandlen, ENC_ASCII); |
508 | 0 | command_token = wmem_ascii_strdown(pinfo->pool, command_token, commandlen); |
509 | |
|
510 | 0 | folderlen = linelen - (uid_next_token - offset); |
511 | 0 | folder_offset = uid_next_token; |
512 | 0 | tvb_get_token_len_length(tvb, uid_next_token, folderlen, &folder_tokenlen, &folder_next_token); |
513 | 0 | } |
514 | 130 | } else { |
515 | | /* |
516 | | * Not a UID request so perform normal parsing. |
517 | | */ |
518 | 130 | proto_tree_add_item(reqresp_tree, (is_request) ? hf_imap_request_command : hf_imap_response_status, tvb, offset, tokenlen, ENC_ASCII|ENC_NA); |
519 | 130 | if (is_request) { |
520 | 33 | hidden_item = proto_tree_add_item(reqresp_tree, hf_imap_command, tvb, offset, tokenlen, ENC_ASCII); |
521 | 33 | proto_item_set_hidden(hidden_item); |
522 | | |
523 | | /* |
524 | | * Save command string to do specialized processing. |
525 | | */ |
526 | 33 | commandlen = tokenlen; |
527 | 33 | command_token = (const char*)tvb_get_string_enc(pinfo->pool, tvb, offset, commandlen, ENC_ASCII); |
528 | 33 | command_token = wmem_ascii_strdown(pinfo->pool, command_token, commandlen); |
529 | | |
530 | 33 | folderlen = linelen - (next_token - offset); |
531 | 33 | folder_offset = next_token; |
532 | 33 | tvb_get_token_len_length(tvb, next_token, folderlen, &folder_tokenlen, &folder_next_token); |
533 | 33 | } |
534 | 130 | } |
535 | | |
536 | 130 | if (commandlen > 0) { // implies is_request (i.e. can be true only if is_request but is not equivalent) |
537 | 28 | if (strncmp(command_token, "select", commandlen) == 0 || |
538 | 28 | strncmp(command_token, "examine", commandlen) == 0 || |
539 | 28 | strncmp(command_token, "create", commandlen) == 0 || |
540 | 28 | strncmp(command_token, "delete", commandlen) == 0 || |
541 | 28 | strncmp(command_token, "rename", commandlen) == 0 || |
542 | 28 | strncmp(command_token, "subscribe", commandlen) == 0 || |
543 | 28 | strncmp(command_token, "unsubscribe", commandlen) == 0 || |
544 | 28 | strncmp(command_token, "status", commandlen) == 0 || |
545 | 28 | strncmp(command_token, "append", commandlen) == 0 || |
546 | 22 | strncmp(command_token, "search", commandlen) == 0) { |
547 | | |
548 | | /* |
549 | | * These commands support folder as an argument, |
550 | | * so parse out the folder name. |
551 | | */ |
552 | 6 | if (folder_tokenlen != 0) |
553 | 6 | proto_tree_add_item(reqresp_tree, hf_imap_request_folder, tvb, folder_offset, folder_tokenlen, ENC_ASCII); |
554 | 6 | } |
555 | 22 | else if ((linelen > 0) && strncmp(command_token, "copy", commandlen) == 0) { |
556 | | /* |
557 | | * Handle the copy command separately since folder |
558 | | * is the second argument for this command. |
559 | | */ |
560 | 0 | folderlen = linelen - (folder_next_token - offset); |
561 | 0 | folder_offset = folder_next_token; |
562 | 0 | folder_tokenlen = tvb_get_token_len_length(tvb, folder_offset, folderlen, &folder_tokenlen , &folder_next_token); |
563 | |
|
564 | 0 | if (folder_tokenlen != 0) |
565 | 0 | proto_tree_add_item(reqresp_tree, hf_imap_request_folder, tvb, folder_offset, folder_tokenlen, ENC_ASCII); |
566 | 0 | } |
567 | 22 | else if (strncmp(command_token, "starttls", commandlen) == 0) { |
568 | | /* If next response is OK, then TLS should be commenced. */ |
569 | 0 | session_state->ssl_requested = true; |
570 | 0 | } |
571 | 22 | else if (strncmp(command_token, "login", commandlen) == 0) { |
572 | 0 | unsigned usernamelen = linelen - (next_token - offset); |
573 | 0 | unsigned username_offset = next_token; |
574 | 0 | unsigned username_next_token; |
575 | 0 | unsigned username_tokenlen; |
576 | 0 | tvb_get_token_len_length(tvb, next_token, usernamelen, &username_tokenlen, &username_next_token); |
577 | 0 | char *username = (char*)tvb_get_string_enc(pinfo->pool, tvb, username_offset, username_tokenlen, ENC_ASCII | ENC_NA); |
578 | 0 | proto_tree_add_string(reqresp_tree, hf_imap_request_username, tvb, username_offset, username_tokenlen, username); |
579 | |
|
580 | 0 | unsigned passwordlen = linelen - (username_next_token - offset); |
581 | 0 | unsigned password_offset = username_next_token; |
582 | 0 | unsigned password_tokenlen; |
583 | 0 | tvb_get_token_len_length(tvb, username_next_token, passwordlen, NULL, &password_tokenlen); |
584 | 0 | const char* password = (char*)tvb_get_string_enc(pinfo->pool, tvb, password_offset + 1, password_tokenlen - 2, ENC_ASCII | ENC_NA); |
585 | 0 | proto_tree_add_string(reqresp_tree, hf_imap_request_password, tvb, password_offset, password_tokenlen, password); |
586 | |
|
587 | 0 | tap_credential_t* auth = wmem_new0(pinfo->pool, tap_credential_t); |
588 | 0 | auth->num = auth->username_num = pinfo->num; |
589 | 0 | auth->password_hf_id = hf_imap_request_password; |
590 | 0 | auth->username = username; |
591 | 0 | auth->proto = "IMAP"; |
592 | 0 | tap_queue_packet(credentials_tap, pinfo, auth); |
593 | 0 | } |
594 | 28 | } |
595 | | |
596 | 130 | if (!is_request) { |
597 | | //See if there is the response command |
598 | 97 | unsigned command_next_token; |
599 | 97 | unsigned command_offset = next_token; |
600 | 97 | commandlen = linelen - (next_token-offset); |
601 | 97 | tvb_get_token_len_length(tvb, next_token, commandlen, &commandlen , &command_next_token); |
602 | 97 | if (commandlen > 0) { |
603 | 88 | proto_tree_add_item(reqresp_tree, hf_imap_response_command, tvb, command_offset, commandlen, ENC_ASCII); |
604 | 88 | hidden_item = proto_tree_add_item(reqresp_tree, hf_imap_command, tvb, command_offset, commandlen, ENC_ASCII); |
605 | 88 | proto_item_set_hidden(hidden_item); |
606 | 88 | } |
607 | 97 | } |
608 | | |
609 | | /* If not yet switched to TLS, check for STARTTLS. */ |
610 | 130 | if (session_state->ssl_requested) { |
611 | 0 | if (!is_request && (tokenbuf != NULL) && strncmp(tokenbuf, "ok", tokenlen) == 0) { |
612 | | /* STARTTLS accepted, next reply will be TLS. */ |
613 | 0 | ssl_starttls_ack(tls_handle, pinfo, imap_handle); |
614 | 0 | if (session_state->ssl_heur_tries_left > 0) { |
615 | 0 | session_state->ssl_heur_tries_left = 0; |
616 | 0 | } |
617 | 0 | session_state->ssl_requested = false; |
618 | 0 | } |
619 | 0 | } |
620 | 130 | } |
621 | | |
622 | | /* Add request/response statistics */ |
623 | 1.43k | if (request_key.tag != NULL) |
624 | 978 | { |
625 | 978 | imap_match_request(pinfo, reqresp_tree, &request_key, is_request); |
626 | 978 | } |
627 | 1.43k | } |
628 | | |
629 | 1.48k | offset = next_offset; /* Skip over last line and \r\n at the end of it */ |
630 | 1.48k | } |
631 | | |
632 | | // If there is only lines that begin with *, at least show the first one |
633 | 4 | if (first_line) { |
634 | 0 | col_add_fstr(pinfo->cinfo, COL_INFO, "%s: %s", is_request ? "Request" : "Response", tvb_format_text(pinfo->pool, tvb, 0, linelen)); |
635 | 0 | } |
636 | | |
637 | 4 | return tvb_captured_length(tvb); |
638 | 55 | } |
639 | | |
640 | | void |
641 | | proto_register_imap(void) |
642 | 14 | { |
643 | 14 | static hf_register_info hf[] = { |
644 | | |
645 | 14 | { &hf_imap_isrequest, |
646 | 14 | { "Request", "imap.isrequest", |
647 | 14 | FT_BOOLEAN, BASE_NONE, NULL, 0x0, |
648 | 14 | "true if IMAP request, false otherwise", HFILL } |
649 | 14 | }, |
650 | 14 | { &hf_imap_line, |
651 | 14 | { "Line", "imap.line", |
652 | 14 | FT_STRINGZ, BASE_NONE, NULL, 0x0, |
653 | 14 | "A line of an IMAP message", HFILL } |
654 | 14 | }, |
655 | 14 | { &hf_imap_request, |
656 | 14 | { "Request", "imap.request", |
657 | 14 | FT_STRINGZ, BASE_NONE, NULL, 0x0, |
658 | 14 | "Remainder of request line", HFILL } |
659 | 14 | }, |
660 | 14 | { &hf_imap_request_tag, |
661 | 14 | { "Request Tag", "imap.request_tag", |
662 | 14 | FT_STRINGZ, BASE_NONE, NULL, 0x0, |
663 | 14 | "First token of request line", HFILL } |
664 | 14 | }, |
665 | 14 | { &hf_imap_response, |
666 | 14 | { "Response", "imap.response", |
667 | 14 | FT_STRINGZ, BASE_NONE, NULL, 0x0, |
668 | 14 | "Remainder of response line", HFILL } |
669 | 14 | }, |
670 | 14 | { &hf_imap_response_tag, |
671 | 14 | { "Response Tag", "imap.response_tag", |
672 | 14 | FT_STRINGZ, BASE_NONE, NULL, 0x0, |
673 | 14 | "First token of response line", HFILL } |
674 | 14 | }, |
675 | 14 | { &hf_imap_request_command, |
676 | 14 | { "Request Command", "imap.request.command", |
677 | 14 | FT_STRINGZ, BASE_NONE, NULL, 0x0, |
678 | 14 | "Request command name", HFILL } |
679 | 14 | }, |
680 | 14 | { &hf_imap_response_command, |
681 | 14 | { "Response Command", "imap.response.command", |
682 | 14 | FT_STRINGZ, BASE_NONE, NULL, 0x0, |
683 | 14 | "Response command name", HFILL } |
684 | 14 | }, |
685 | 14 | { &hf_imap_response_status, |
686 | 14 | { "Response Status", "imap.response.status", |
687 | 14 | FT_STRINGZ, BASE_NONE, NULL, 0x0, |
688 | 14 | "Response status code", HFILL } |
689 | 14 | }, |
690 | 14 | { &hf_imap_tag, |
691 | 14 | { "Tag", "imap.tag", |
692 | 14 | FT_STRINGZ, BASE_NONE, NULL, 0x0, |
693 | 14 | "First token of line", HFILL } |
694 | 14 | }, |
695 | 14 | { &hf_imap_command, |
696 | 14 | { "Command", "imap.command", |
697 | 14 | FT_STRINGZ, BASE_NONE, NULL, 0x0, |
698 | 14 | "Request or Response command name", HFILL } |
699 | 14 | }, |
700 | 14 | { &hf_imap_request_folder, |
701 | 14 | { "Request Folder", "imap.request.folder", |
702 | 14 | FT_STRINGZ, BASE_NONE, NULL, 0x0, |
703 | 14 | "Request command folder", HFILL } |
704 | 14 | }, |
705 | 14 | { &hf_imap_request_uid, |
706 | 14 | { "Request isUID", "imap.request.command.uid", |
707 | 14 | FT_BOOLEAN, BASE_NONE, NULL, 0x0, |
708 | 14 | "Request command uid", HFILL } |
709 | 14 | }, |
710 | 14 | { &hf_imap_request_username, |
711 | 14 | { "Request Username", "imap.request.username", |
712 | 14 | FT_STRINGZ, BASE_NONE, NULL, 0x0, |
713 | 14 | "Request command username", HFILL } |
714 | 14 | }, |
715 | 14 | { &hf_imap_request_password, |
716 | 14 | { "Request Password", "imap.request.password", |
717 | 14 | FT_STRINGZ, BASE_NONE, NULL, 0x0, |
718 | 14 | "Request command password", HFILL } |
719 | 14 | }, |
720 | | |
721 | | /* Request/Response Matching */ |
722 | 14 | { &hf_imap_response_in, |
723 | 14 | { "Response In", "imap.response_in", |
724 | 14 | FT_FRAMENUM, BASE_NONE, FRAMENUM_TYPE(FT_FRAMENUM_RESPONSE), 0x0, |
725 | 14 | "The response to this IMAP request is in this frame", HFILL } |
726 | 14 | }, |
727 | 14 | { &hf_imap_response_to, |
728 | 14 | { "Request In", "imap.response_to", |
729 | 14 | FT_FRAMENUM, BASE_NONE, FRAMENUM_TYPE(FT_FRAMENUM_REQUEST), 0x0, |
730 | 14 | "This is a response to the IMAP request in this frame", HFILL } |
731 | 14 | }, |
732 | 14 | { &hf_imap_time, |
733 | 14 | { "Response Time", "imap.time", |
734 | 14 | FT_RELATIVE_TIME, BASE_NONE, NULL, 0x0, |
735 | 14 | "The time between the request and response", HFILL } |
736 | 14 | }, |
737 | 14 | }; |
738 | | |
739 | 14 | static int *ett[] = { |
740 | 14 | &ett_imap, |
741 | 14 | &ett_imap_reqresp, |
742 | 14 | }; |
743 | | |
744 | 14 | module_t *imap_module; |
745 | | |
746 | 14 | proto_imap = proto_register_protocol("Internet Message Access Protocol", "IMAP", "imap"); |
747 | | |
748 | 14 | imap_handle = register_dissector("imap", dissect_imap, proto_imap); |
749 | | |
750 | 14 | proto_register_field_array(proto_imap, hf, array_length(hf)); |
751 | 14 | proto_register_subtree_array(ett, array_length(ett)); |
752 | | |
753 | 14 | imap_module = prefs_register_protocol(proto_imap, NULL); |
754 | 14 | prefs_register_bool_preference(imap_module, "ssl_heuristic", |
755 | 14 | "Use heuristic detection for TLS", |
756 | 14 | "Whether to use heuristics for post-STARTTLS detection of encrypted IMAP conversations", |
757 | 14 | &imap_ssl_heuristic); |
758 | | |
759 | 14 | imap_requests = wmem_map_new_autoreset(wmem_epan_scope(), wmem_file_scope(), imap_request_hash, imap_request_equal); |
760 | | |
761 | | /* compile patterns */ |
762 | 14 | ws_mempbrk_compile(&pbrk_whitespace, " \t\r\n"); |
763 | | |
764 | 14 | credentials_tap = register_tap("credentials"); |
765 | 14 | } |
766 | | |
767 | | void |
768 | | proto_reg_handoff_imap(void) |
769 | 14 | { |
770 | 14 | dissector_add_uint_with_preference("tcp.port", TCP_PORT_IMAP, imap_handle); |
771 | 14 | ssl_dissector_add(TCP_PORT_SSL_IMAP, imap_handle); |
772 | 14 | tls_handle = find_dissector("tls"); |
773 | 14 | imf_handle = find_dissector("imf"); |
774 | 14 | } |
775 | | /* |
776 | | * Editor modelines - https://www.wireshark.org/tools/modelines.html |
777 | | * |
778 | | * Local variables: |
779 | | * c-basic-offset: 2 |
780 | | * tab-width: 8 |
781 | | * indent-tabs-mode: nil |
782 | | * End: |
783 | | * |
784 | | * vi: set shiftwidth=2 tabstop=8 expandtab: |
785 | | * :indentSize=2:tabSize=8:noTabs=true |
786 | | */ |