Coverage Report

Created: 2026-06-30 07:22

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/wireshark/epan/dissectors/packet-pop.c
Line
Count
Source
1
/* packet-pop.c
2
 * Routines for pop packet dissection
3
 * RFC 1939
4
 * Copyright 1999, Richard Sharpe <rsharpe@ns.aus.com>
5
 *
6
 * Wireshark - Network traffic analyzer
7
 * By Gerald Combs <gerald@wireshark.org>
8
 * Copyright 1998 Gerald Combs
9
 *
10
 * Copied from packet-tftp.c
11
 *
12
 * SPDX-License-Identifier: GPL-2.0-or-later
13
 */
14
15
#include "config.h"
16
17
#include <stdlib.h>
18
19
#include <epan/packet.h>
20
#include <epan/strutil.h>
21
#include <epan/conversation.h>
22
#include <epan/prefs.h>
23
#include <epan/reassemble.h>
24
#include <epan/proto_data.h>
25
#include <epan/expert.h>
26
#include <epan/credentials.h>
27
28
#include <wsutil/str_util.h>
29
#include <wsutil/strtoi.h>
30
31
#include <tap.h>
32
33
#include "packet-tls.h"
34
#include "packet-tls-utils.h"
35
36
void proto_register_pop(void);
37
void proto_reg_handoff_pop(void);
38
39
static int proto_pop;
40
41
static int credentials_tap;
42
43
static int hf_pop_response;
44
static int hf_pop_response_indicator;
45
static int hf_pop_response_description;
46
static int hf_pop_response_data;
47
48
static int hf_pop_request;
49
static int hf_pop_request_command;
50
static int hf_pop_request_parameter;
51
static int hf_pop_request_data;
52
53
static int hf_pop_data_fragments;
54
static int hf_pop_data_fragment;
55
static int hf_pop_data_fragment_overlap;
56
static int hf_pop_data_fragment_overlap_conflicts;
57
static int hf_pop_data_fragment_multiple_tails;
58
static int hf_pop_data_fragment_too_long_fragment;
59
static int hf_pop_data_fragment_error;
60
static int hf_pop_data_fragment_count;
61
static int hf_pop_data_reassembled_in;
62
static int hf_pop_data_reassembled_length;
63
64
static expert_field ei_pop_resp_tot_len_invalid;
65
66
static int ett_pop;
67
static int ett_pop_reqresp;
68
69
static int ett_pop_data_fragment;
70
static int ett_pop_data_fragments;
71
72
static dissector_handle_t pop_handle;
73
static dissector_handle_t imf_handle;
74
static dissector_handle_t tls_handle;
75
76
14
#define TCP_PORT_POP            110
77
14
#define TCP_PORT_SSL_POP        995
78
79
/* desegmentation of POP command and response lines */
80
static bool pop_data_desegment = true;
81
82
static reassembly_table pop_data_reassembly_table;
83
84
static const fragment_items pop_data_frag_items = {
85
  /* Fragment subtrees */
86
  &ett_pop_data_fragment,
87
  &ett_pop_data_fragments,
88
  /* Fragment fields */
89
  &hf_pop_data_fragments,
90
  &hf_pop_data_fragment,
91
  &hf_pop_data_fragment_overlap,
92
  &hf_pop_data_fragment_overlap_conflicts,
93
  &hf_pop_data_fragment_multiple_tails,
94
  &hf_pop_data_fragment_too_long_fragment,
95
  &hf_pop_data_fragment_error,
96
  &hf_pop_data_fragment_count,
97
  /* Reassembled in field */
98
  &hf_pop_data_reassembled_in,
99
  /* Reassembled length field */
100
  &hf_pop_data_reassembled_length,
101
  /* Reassembled data field */
102
  NULL,
103
  /* Tag */
104
  "DATA fragments"
105
};
106
107
typedef enum {
108
  pop_arg_type_unknown,
109
  pop_arg_type_username,
110
  pop_arg_type_password
111
} pop_arg_type_t;
112
113
struct pop_proto_data {
114
  uint16_t conversation_id;
115
  bool more_frags;
116
  bool ei_tot_len_invalid;
117
  pop_arg_type_t arg_type;
118
};
119
120
struct pop_data_val {
121
  bool msg_request;
122
  uint32_t msg_read_len;  /* Length of RETR message read so far */
123
  uint32_t msg_tot_len;   /* Total length of RETR message */
124
  bool stls_request;  /* Received STLS request */
125
  char* username;
126
  unsigned username_num;
127
};
128
129
static bool response_is_continuation(const char *data);
130
131
static int
132
dissect_pop(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_)
133
102
{
134
102
  struct pop_proto_data  *frame_data_p;
135
102
  bool                   is_request;
136
102
  bool                   is_continuation;
137
102
  proto_tree             *pop_tree, *reqresp_tree;
138
102
  proto_item             *ti;
139
102
  unsigned               offset = 0;
140
102
  unsigned char          *line;
141
102
  unsigned               next_offset;
142
102
  unsigned               linelen;
143
102
  unsigned               tokenlen;
144
102
  const unsigned char    *next_token;
145
102
  fragment_head          *frag_msg;
146
102
  tvbuff_t               *next_tvb;
147
102
  conversation_t         *conversation;
148
102
  struct pop_data_val    *data_val;
149
102
  unsigned               length_remaining;
150
102
  pop_arg_type_t         pop_arg_type = pop_arg_type_unknown;
151
152
102
  col_set_str(pinfo->cinfo, COL_PROTOCOL, "POP");
153
154
102
  conversation = find_or_create_conversation(pinfo);
155
102
  data_val = (struct pop_data_val *)conversation_get_proto_data(conversation, proto_pop);
156
102
  if (!data_val) {
157
158
     /*
159
      * No conversation - create one and attach it.
160
      */
161
73
     data_val = wmem_new0(wmem_file_scope(), struct pop_data_val);
162
163
73
     conversation_add_proto_data(conversation, proto_pop, data_val);
164
73
  }
165
166
  /*
167
   * Find the end of the first line.
168
   */
169
102
  tvb_find_line_end_remaining(tvb, offset, &linelen, &next_offset);
170
102
  line = (unsigned char*)wmem_alloc(pinfo->pool, linelen+1);
171
102
  tvb_memcpy(tvb, line, offset, linelen);
172
102
  line[linelen] = '\0';
173
174
102
  if (pinfo->match_uint == pinfo->destport) {
175
42
    is_request = true;
176
42
    is_continuation = false;
177
60
  } else {
178
60
    is_request = false;
179
60
    is_continuation = response_is_continuation((const char*)line);
180
60
  }
181
182
  /*
183
   * Put the first line from the buffer into the summary
184
   * if it's a POP request or reply (but leave out the
185
   * line terminator).
186
   * Otherwise, just call it a continuation.
187
   */
188
102
  if (is_continuation) {
189
55
    length_remaining = tvb_reported_length_remaining(tvb, offset);
190
55
    col_add_fstr(pinfo->cinfo, COL_INFO, "S: DATA fragment, %d byte%s",
191
55
                   length_remaining, plurality (length_remaining, "", "s"));
192
55
  }
193
47
  else
194
47
    col_add_fstr(pinfo->cinfo, COL_INFO, "%s: %s", is_request ? "C" : "S",
195
47
                   format_text(pinfo->pool, (char*)line, linelen));
196
197
102
  ti = proto_tree_add_item(tree, proto_pop, tvb, offset, -1, ENC_NA);
198
102
  pop_tree = proto_item_add_subtree(ti, ett_pop);
199
200
102
  if (is_continuation) {
201
202
55
    if (pop_data_desegment) {
203
204
55
      if (!PINFO_FD_VISITED(pinfo)) {
205
206
55
        data_val->msg_read_len += tvb_reported_length(tvb);
207
208
55
        frame_data_p = wmem_new0(wmem_file_scope(), struct pop_proto_data);
209
210
55
        frame_data_p->conversation_id = conversation->conv_index;
211
55
        frame_data_p->more_frags = data_val->msg_read_len < data_val->msg_tot_len;
212
213
55
        p_add_proto_data(wmem_file_scope(), pinfo, proto_pop, 0, frame_data_p);
214
55
      } else {
215
0
          frame_data_p = (struct pop_proto_data *)p_get_proto_data(wmem_file_scope(), pinfo, proto_pop, 0);
216
0
          DISSECTOR_ASSERT(frame_data_p);
217
0
      }
218
219
55
      frag_msg = fragment_add_seq_next(&pop_data_reassembly_table, tvb, 0,
220
55
                                       pinfo,
221
55
                                       frame_data_p->conversation_id,
222
55
                                       NULL,
223
55
                                       tvb_reported_length(tvb),
224
55
                                       frame_data_p->more_frags);
225
226
55
      next_tvb = process_reassembled_data(tvb, offset, pinfo,
227
55
                                          "Reassembled DATA",
228
55
                                          frag_msg, &pop_data_frag_items,
229
55
                                          NULL, pop_tree);
230
231
55
      if (next_tvb) {
232
233
55
        if (imf_handle)
234
55
          call_dissector(imf_handle, next_tvb, pinfo, tree);
235
236
55
        if (!PINFO_FD_VISITED(pinfo)) {
237
          /* we have read everything - reset */
238
239
55
          data_val->msg_read_len = 0;
240
55
          data_val->msg_tot_len = 0;
241
55
        }
242
55
        pinfo->fragmented = false;
243
55
      } else {
244
0
        pinfo->fragmented = true;
245
0
      }
246
247
55
    } else {
248
249
      /*
250
       * Put the whole packet into the tree as data.
251
       */
252
0
      call_data_dissector(tvb, pinfo, pop_tree);
253
254
0
    }
255
55
    return tvb_captured_length(tvb);
256
55
  }
257
258
  /*
259
   * Put the line into the protocol tree.
260
   */
261
47
  ti = proto_tree_add_string_format(pop_tree,
262
47
                                    (is_request) ?
263
42
                                        hf_pop_request :
264
47
                                        hf_pop_response,
265
47
                                    tvb, offset,
266
47
                                    next_offset - offset,
267
47
                                    "", "%s",
268
47
                                    tvb_format_text(pinfo->pool, tvb, offset, next_offset - offset));
269
47
  reqresp_tree = proto_item_add_subtree(ti, ett_pop_reqresp);
270
271
  /*
272
   * Extract the first token, and, if there is a first
273
   * token, add it as the request or reply code.
274
   */
275
47
  tokenlen = get_token_len(line, line + linelen, &next_token);
276
47
  if (tokenlen != 0) {
277
46
    proto_tree_add_item(reqresp_tree,
278
46
                        (is_request) ?
279
41
                            hf_pop_request_command :
280
46
                            hf_pop_response_indicator,
281
46
                        tvb, offset, tokenlen, ENC_ASCII|ENC_NA);
282
283
46
    if (!PINFO_FD_VISITED(pinfo)) {
284
46
      if (is_request) {
285
        /* see if this is RETR or TOP command */
286
41
        if (g_ascii_strncasecmp((char*)line, "RETR", 4) == 0 ||
287
41
           g_ascii_strncasecmp((char*)line, "TOP", 3) == 0)
288
          /* the next response will tell us how many bytes */
289
0
          data_val->msg_request = true;
290
291
41
        if (g_ascii_strncasecmp((char*)line, "STLS", 4) == 0) {
292
0
          data_val->stls_request = true;
293
0
        }
294
295
41
        if (g_ascii_strncasecmp((char*)line, "USER", 4) == 0) {
296
0
          pop_arg_type = pop_arg_type_username;
297
0
        }
298
299
41
        if (g_ascii_strncasecmp((char*)line, "PASS", 4) == 0) {
300
0
          pop_arg_type = pop_arg_type_password;
301
0
        }
302
303
41
        if (pop_arg_type != pop_arg_type_unknown) {
304
          /* store info for subsequent pass */
305
0
          frame_data_p = wmem_new0(wmem_file_scope(), struct pop_proto_data);
306
0
          frame_data_p->arg_type = pop_arg_type;
307
0
          p_add_proto_data(wmem_file_scope(), pinfo, proto_pop, 0, frame_data_p);
308
0
        }
309
41
      } else {
310
5
        if (data_val->msg_request) {
311
          /* this is a response to a RETR or TOP command */
312
313
0
          if (g_ascii_strncasecmp((char*)line, "+OK ", 4) == 0 && linelen > 4) {
314
            /* the message will be sent - work out how many bytes */
315
0
            data_val->msg_read_len = 0;
316
0
            data_val->msg_tot_len = 0;
317
0
            if (sscanf((char*)line, "%*s %u %*s", &data_val->msg_tot_len) != 1) {
318
0
              expert_add_info(pinfo, ti, &ei_pop_resp_tot_len_invalid);
319
              /* store expect info presence to add it to the tree during subsequent pass */
320
0
              frame_data_p = wmem_new0(wmem_file_scope(), struct pop_proto_data);
321
0
              frame_data_p->ei_tot_len_invalid = true;
322
0
              p_add_proto_data(wmem_file_scope(), pinfo, proto_pop, 0, frame_data_p);
323
0
            }
324
0
          }
325
0
          data_val->msg_request = false;
326
0
        }
327
328
5
        if (data_val->stls_request) {
329
0
          if (g_ascii_strncasecmp((char*)line, "+OK ", 4) == 0) {
330
              /* This is the last non-TLS frame. */
331
0
              ssl_starttls_ack(tls_handle, pinfo, pop_handle);
332
0
          }
333
0
          data_val->stls_request = false;
334
0
        }
335
5
      }
336
46
    } else {
337
0
      frame_data_p = (struct pop_proto_data *)p_get_proto_data(wmem_file_scope(), pinfo, proto_pop, 0);
338
0
      if (frame_data_p) {
339
0
        pop_arg_type = frame_data_p->arg_type;
340
0
        if (frame_data_p->ei_tot_len_invalid)
341
0
          expert_add_info(pinfo, ti, &ei_pop_resp_tot_len_invalid);
342
0
      }
343
0
    }
344
345
46
    offset += (int) (next_token - line);
346
46
    linelen -= (int) (next_token - line);
347
46
  }
348
349
350
  /*
351
   * Add the rest of the first line as request or
352
   * reply param/description.
353
   */
354
47
  if (linelen != 0) {
355
24
    tap_credential_t* auth;
356
24
    proto_tree_add_item(reqresp_tree,
357
24
                        (is_request) ?
358
20
                            hf_pop_request_parameter :
359
24
                            hf_pop_response_description,
360
24
                        tvb, offset, linelen, ENC_ASCII|ENC_NA);
361
24
    switch (pop_arg_type) {
362
0
      case pop_arg_type_username:
363
0
        if (!data_val->username && linelen > 0) {
364
0
          data_val->username = (char*)tvb_get_string_enc(wmem_file_scope(), tvb, offset, linelen, ENC_NA|ENC_ASCII);
365
0
          data_val->username_num = pinfo->num;
366
0
        }
367
0
        break;
368
0
      case pop_arg_type_password:
369
0
        auth = wmem_new0(pinfo->pool, tap_credential_t);
370
0
        auth->num = pinfo->num;
371
0
        auth->username_num = data_val->username_num;
372
0
        auth->password_hf_id = hf_pop_request_parameter;
373
0
        auth->username = data_val->username;
374
0
        auth->proto = "POP3";
375
0
        auth->info = wmem_strdup_printf(pinfo->pool, "Username in packet %u", data_val->username_num);
376
0
        tap_queue_packet(credentials_tap, pinfo, auth);
377
0
        break;
378
24
      default:
379
24
        break;
380
24
    }
381
24
  }
382
47
  offset = next_offset;
383
384
  /*
385
   * Show the rest of the request or response as text,
386
   * a line at a time.
387
   */
388
370
  while (tvb_offset_exists(tvb, offset)) {
389
    /*
390
     * Find the end of the line.
391
     */
392
323
    tvb_find_line_end_remaining(tvb, offset, NULL, &next_offset);
393
394
    /*
395
     * Put this line.
396
     */
397
323
    proto_tree_add_string_format(pop_tree,
398
323
                                 (is_request) ?
399
283
                                     hf_pop_request_data :
400
323
                                     hf_pop_response_data,
401
323
                                 tvb, offset,
402
323
                                 next_offset - offset,
403
323
                                 "", "%s",
404
323
                                 tvb_format_text(pinfo->pool, tvb, offset, next_offset - offset));
405
323
    offset = next_offset;
406
323
  }
407
47
  return tvb_captured_length(tvb);
408
47
}
409
410
static bool response_is_continuation(const char *data)
411
60
{
412
60
  if (strncmp(data, "+OK", strlen("+OK")) == 0)
413
2
    return false;
414
415
58
  if (strncmp(data, "-ERR", strlen("-ERR")) == 0)
416
3
    return false;
417
418
55
  return true;
419
58
}
420
421
void
422
proto_register_pop(void)
423
14
{
424
14
  expert_module_t* expert_pop;
425
426
14
  static hf_register_info hf[] = {
427
14
    { &hf_pop_response,
428
14
      { "Response",           "pop.response",
429
14
        FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL }},
430
14
    { &hf_pop_response_indicator,
431
14
      { "Response indicator",           "pop.response.indicator",
432
14
         FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL }},
433
14
    { &hf_pop_response_description,
434
14
      { "Response description",           "pop.response.description",
435
14
         FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL }},
436
14
    { &hf_pop_response_data,
437
14
      { "Data",           "pop.response.data",
438
14
        FT_STRING, BASE_NONE, NULL, 0x0, "Response Data", HFILL }},
439
14
    { &hf_pop_request,
440
14
      { "Request",           "pop.request",
441
14
         FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL }},
442
14
    { &hf_pop_request_command,
443
14
      { "Request command",            "pop.request.command",
444
14
        FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL }},
445
14
    { &hf_pop_request_parameter,
446
14
      { "Request parameter",            "pop.request.parameter",
447
14
        FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL }},
448
14
    { &hf_pop_request_data,
449
14
      { "Data",           "pop.request.data",
450
14
         FT_STRING, BASE_NONE, NULL, 0x0, "Request data", HFILL }},
451
    /* Fragment entries */
452
14
    { &hf_pop_data_fragments,
453
14
      { "DATA fragments", "pop.data.fragments", FT_NONE, BASE_NONE,
454
14
        NULL, 0x00, "Message fragments", HFILL } },
455
14
    { &hf_pop_data_fragment,
456
14
      { "DATA fragment", "pop.data.fragment", FT_FRAMENUM, BASE_NONE,
457
14
        NULL, 0x00, "Message fragment", HFILL } },
458
14
    { &hf_pop_data_fragment_overlap,
459
14
      { "DATA fragment overlap", "pop.data.fragment.overlap", FT_BOOLEAN,
460
14
        BASE_NONE, NULL, 0x0, "Message fragment overlap", HFILL } },
461
14
    { &hf_pop_data_fragment_overlap_conflicts,
462
14
      { "DATA fragment overlapping with conflicting data",
463
14
        "pop.data.fragment.overlap.conflicts", FT_BOOLEAN, BASE_NONE, NULL,
464
14
        0x0, "Message fragment overlapping with conflicting data", HFILL } },
465
14
    { &hf_pop_data_fragment_multiple_tails,
466
14
      { "DATA has multiple tail fragments",
467
14
        "pop.data.fragment.multiple_tails", FT_BOOLEAN, BASE_NONE,
468
14
        NULL, 0x0, "Message has multiple tail fragments", HFILL } },
469
14
    { &hf_pop_data_fragment_too_long_fragment,
470
14
      { "DATA fragment too long", "pop.data.fragment.too_long_fragment",
471
14
        FT_BOOLEAN, BASE_NONE, NULL, 0x0, "Message fragment too long",
472
14
        HFILL } },
473
14
    { &hf_pop_data_fragment_error,
474
14
      { "DATA defragmentation error", "pop.data.fragment.error", FT_FRAMENUM,
475
14
        BASE_NONE, NULL, 0x00, "Message defragmentation error", HFILL } },
476
14
    { &hf_pop_data_fragment_count,
477
14
      { "DATA fragment count", "pop.data.fragment.count", FT_UINT32, BASE_DEC,
478
14
        NULL, 0x00, NULL, HFILL } },
479
14
    { &hf_pop_data_reassembled_in,
480
14
      { "Reassembled DATA in frame", "pop.data.reassembled.in", FT_FRAMENUM, BASE_NONE,
481
14
        NULL, 0x00, "This DATA fragment is reassembled in this frame", HFILL } },
482
14
    { &hf_pop_data_reassembled_length,
483
14
      { "Reassembled DATA length", "pop.data.reassembled.length", FT_UINT32, BASE_DEC,
484
14
        NULL, 0x00, "The total length of the reassembled payload", HFILL } },
485
14
  };
486
487
14
  static ei_register_info ei[] = {
488
14
    { &ei_pop_resp_tot_len_invalid, { "pop.response.tot_len.invalid", PI_MALFORMED, PI_ERROR,
489
14
      "Length must be a string containing an integer", EXPFILL }}
490
14
  };
491
492
14
  static int *ett[] = {
493
14
    &ett_pop,
494
14
    &ett_pop_reqresp,
495
14
    &ett_pop_data_fragment,
496
14
    &ett_pop_data_fragments
497
14
  };
498
14
  module_t *pop_module;
499
500
501
14
  proto_pop = proto_register_protocol("Post Office Protocol", "POP", "pop");
502
14
  pop_handle = register_dissector("pop", dissect_pop, proto_pop);
503
14
  proto_register_field_array(proto_pop, hf, array_length(hf));
504
14
  proto_register_subtree_array(ett, array_length(ett));
505
506
14
  reassembly_table_register (&pop_data_reassembly_table,
507
14
                         &addresses_ports_reassembly_table_functions);
508
509
  /* Preferences */
510
14
  pop_module = prefs_register_protocol(proto_pop, NULL);
511
512
14
  prefs_register_bool_preference(pop_module, "desegment_data",
513
14
    "Reassemble POP RETR and TOP responses spanning multiple TCP segments",
514
14
    "Whether the POP dissector should reassemble RETR and TOP responses and spanning multiple TCP segments."
515
14
    " To use this option, you must also enable \"Allow subdissectors to reassemble TCP streams\" in the TCP protocol settings.",
516
14
    &pop_data_desegment);
517
518
14
  expert_pop = expert_register_protocol(proto_pop);
519
14
  expert_register_field_array(expert_pop, ei, array_length(ei));
520
521
14
  credentials_tap = register_tap("credentials");
522
14
}
523
524
void
525
proto_reg_handoff_pop(void)
526
14
{
527
14
  dissector_add_uint_with_preference("tcp.port", TCP_PORT_POP, pop_handle);
528
14
  ssl_dissector_add(TCP_PORT_SSL_POP, pop_handle);
529
530
  /* find the IMF dissector */
531
14
  imf_handle = find_dissector_add_dependency("imf", proto_pop);
532
533
  /* find the TLS dissector */
534
14
  tls_handle = find_dissector_add_dependency("tls", proto_pop);
535
14
}
536
537
/*
538
 * Editor modelines  -  https://www.wireshark.org/tools/modelines.html
539
 *
540
 * Local Variables:
541
 * c-basic-offset: 2
542
 * tab-width: 8
543
 * indent-tabs-mode: nil
544
 * End:
545
 *
546
 * ex: set shiftwidth=2 tabstop=8 expandtab:
547
 * :indentSize=2:tabSize=8:noTabs=true:
548
 */