Coverage Report

Created: 2025-06-13 06:05

/src/ndpi/src/lib/protocols/tftp.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * tftp.c
3
 *
4
 * Copyright (C) 2009-11 - ipoque GmbH
5
 * Copyright (C) 2011-25 - ntop.org
6
 *
7
 * This file is part of nDPI, an open source deep packet inspection
8
 * library based on the OpenDPI and PACE technology by ipoque GmbH
9
 *
10
 * nDPI is free software: you can redistribute it and/or modify
11
 * it under the terms of the GNU Lesser General Public License as published by
12
 * the Free Software Foundation, either version 3 of the License, or
13
 * (at your option) any later version.
14
 *
15
 * nDPI is distributed in the hope that it will be useful,
16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
 * GNU Lesser General Public License for more details.
19
 *
20
 * You should have received a copy of the GNU Lesser General Public License
21
 * along with nDPI.  If not, see <http://www.gnu.org/licenses/>.
22
 *
23
 */
24
25
#include "ndpi_protocol_ids.h"
26
27
#define NDPI_CURRENT_PROTO NDPI_PROTOCOL_TFTP
28
29
#include "ndpi_api.h"
30
#include "ndpi_private.h"
31
32
static void ndpi_int_tftp_add_connection(struct ndpi_detection_module_struct
33
           *ndpi_struct, struct ndpi_flow_struct *flow)
34
0
{
35
0
  ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_TFTP, NDPI_PROTOCOL_UNKNOWN, NDPI_CONFIDENCE_DPI);
36
0
}
37
38
static size_t tftp_dissect_szstr(struct ndpi_packet_struct const * const packet,
39
                                 size_t * const offset,
40
                                 char const ** const string_start)
41
0
{
42
0
  if (packet->payload_packet_len <= *offset)
43
0
  {
44
0
    return 0;
45
0
  }
46
47
0
  const union {
48
0
    uint8_t const * const as_ptr;
49
0
    char const * const as_str;
50
0
  } payload = { .as_ptr = packet->payload + *offset };
51
52
0
  size_t len = strnlen(payload.as_str, packet->payload_packet_len - *offset);
53
0
  if (len == 0 ||
54
0
      packet->payload_packet_len <= *offset + len ||
55
0
      payload.as_str[len] != '\0')
56
0
  {
57
0
    return 0;
58
0
  }
59
60
0
  if (string_start != NULL)
61
0
  {
62
0
    *string_start = payload.as_str;
63
0
  }
64
0
  *offset += len + 1;
65
0
  return len;
66
0
}
67
68
static int tftp_dissect_mode(struct ndpi_packet_struct const * const packet,
69
                             size_t * const offset)
70
0
{
71
0
  static char const * const valid_modes[] = {
72
0
    "netascii", "octet", "mail"
73
0
  };
74
0
  char const * string_start;
75
0
  size_t string_length = tftp_dissect_szstr(packet, offset, &string_start);
76
0
  size_t i;
77
78
0
  if (string_length == 0)
79
0
  {
80
0
    return 1;
81
0
  }
82
83
0
  for (i = 0; i < NDPI_ARRAY_LENGTH(valid_modes); ++i)
84
0
  {
85
0
    if (strncasecmp(string_start, valid_modes[i], string_length) == 0)
86
0
    {
87
0
      break;
88
0
    }
89
0
  }
90
91
0
  return i == NDPI_ARRAY_LENGTH(valid_modes);
92
0
}
93
94
static int tftp_dissect_options(struct ndpi_packet_struct const * const packet,
95
                                size_t * const offset)
96
0
{
97
0
  static char const * const valid_options[] = {
98
0
    "blksize", "tsize"
99
0
  };
100
0
  uint8_t options_used[NDPI_ARRAY_LENGTH(valid_options)] = {0, 0};
101
0
  size_t i;
102
103
0
  do {
104
0
    char const * string_start;
105
0
    size_t string_length = tftp_dissect_szstr(packet, offset, &string_start);
106
107
0
    if (string_length == 0 ||
108
0
        tftp_dissect_szstr(packet, offset, NULL) == 0 /* value, not interested */)
109
0
    {
110
0
      break;
111
0
    }
112
113
0
    for (i = 0; i < NDPI_ARRAY_LENGTH(valid_options); ++i)
114
0
    {
115
0
      if (strncasecmp(string_start, valid_options[i], string_length) == 0)
116
0
      {
117
0
        break;
118
0
      }
119
0
    }
120
121
0
    if (i == NDPI_ARRAY_LENGTH(valid_options) /* option not found in valid_options */ ||
122
0
        options_used[i] != 0 /* duplicate options are not allowed */)
123
0
    {
124
0
      break;
125
0
    }
126
127
0
    options_used[i] = 1;
128
0
  } while (1);
129
130
0
  return *offset != packet->payload_packet_len;
131
0
}
132
133
static void ndpi_search_tftp(struct ndpi_detection_module_struct *ndpi_struct,
134
           struct ndpi_flow_struct *flow)
135
0
{
136
0
  struct ndpi_packet_struct const * const packet = &ndpi_struct->packet;
137
0
  u_int16_t block_num;
138
0
  u_int16_t prev_num;
139
140
0
  NDPI_LOG_DBG(ndpi_struct, "search TFTP\n");
141
142
0
  if (packet->payload_packet_len < 4 /* min. header size */ ||
143
0
      get_u_int8_t(packet->payload, 0) != 0x00)
144
0
  {
145
0
    NDPI_EXCLUDE_DISSECTOR(ndpi_struct, flow);
146
0
    return;
147
0
  }
148
149
  /* parse TFTP opcode */
150
0
  switch (get_u_int8_t(packet->payload, 1))
151
0
  {
152
0
    case 0x01:
153
        /* Read request (RRQ) */
154
0
    case 0x02:
155
        /* Write request (WWQ) */
156
157
0
        if (packet->payload[packet->payload_packet_len - 1] != 0x00 /* last pdu element is a nul terminated string */)
158
0
        {
159
0
          NDPI_EXCLUDE_DISSECTOR(ndpi_struct, flow);
160
0
          return;
161
0
        }
162
163
0
        {
164
0
          size_t filename_len = 0;
165
0
          size_t offset = 2;
166
0
          char const * filename_start;
167
168
0
          filename_len = tftp_dissect_szstr(packet, &offset, &filename_start);
169
170
          /* Exclude the flow as TFPT if there was no filename and mode in the first two strings. */
171
0
          if (filename_len == 0 || ndpi_is_printable_buffer((uint8_t *)filename_start, filename_len) == 0)
172
0
          {
173
0
            NDPI_EXCLUDE_DISSECTOR(ndpi_struct, flow);
174
0
            return;
175
0
          }
176
177
0
          if (tftp_dissect_mode(packet, &offset) != 0)
178
0
          {
179
0
            NDPI_EXCLUDE_DISSECTOR(ndpi_struct, flow);
180
0
            return;
181
0
          }
182
183
0
          if (tftp_dissect_options(packet, &offset) != 0)
184
0
          {
185
0
            NDPI_EXCLUDE_DISSECTOR(ndpi_struct, flow);
186
0
            return;
187
0
          }
188
189
          /* Dissect RRQ/WWQ filename. */
190
0
          filename_len = ndpi_min(filename_len, sizeof(flow->protos.tftp.filename) - 1);
191
0
          memcpy(flow->protos.tftp.filename, filename_start, filename_len);
192
0
          flow->protos.tftp.filename[filename_len] = '\0';
193
194
          /* We have seen enough and do not need any more TFTP packets. */
195
0
          NDPI_LOG_INFO(ndpi_struct, "found tftp (RRQ/WWQ)\n");
196
0
          ndpi_int_tftp_add_connection(ndpi_struct, flow);
197
0
        }
198
0
        return;
199
200
0
    case 0x03:
201
        /* Data (DATA) */
202
0
        if (packet->payload_packet_len <= 4 /* min DATA header size */)
203
0
        {
204
0
          NDPI_EXCLUDE_DISSECTOR(ndpi_struct, flow);
205
0
          return;
206
0
        }
207
        /* First 2 bytes were opcode so next 16 bits are the block number.
208
         * This should increment every packet but give some leeway for midstream and packet loss. */
209
0
        block_num = ntohs(get_u_int16_t(packet->payload, 2));
210
0
        prev_num = flow->l4.udp.tftp_data_num;
211
0
        flow->l4.udp.tftp_data_num = block_num;
212
0
        if (!(block_num == prev_num + 1 || (prev_num != 0 && block_num == prev_num)))
213
0
        {
214
0
          return;
215
0
        }
216
0
        break;
217
218
0
    case 0x04:
219
        /* Acknowledgment (ACK) */
220
221
0
        if (packet->payload_packet_len != 4 /* ACK has a fixed packet size */)
222
0
        {
223
0
          NDPI_EXCLUDE_DISSECTOR(ndpi_struct, flow);
224
0
          return;
225
0
        }
226
        /* First 2 bytes were opcode so next 16 bits are the block number.
227
         * This should increment every packet but give some leeway for midstream and packet loss. */
228
0
        block_num = ntohs(get_u_int16_t(packet->payload, 2));
229
0
        prev_num = flow->l4.udp.tftp_ack_num;
230
0
        flow->l4.udp.tftp_ack_num = block_num;
231
0
        if (!(block_num == prev_num + 1 || (block_num == prev_num)))
232
0
        {
233
0
          return;
234
0
        }
235
0
        break;
236
237
0
    case 0x05:
238
        /* Error (ERROR) */
239
240
0
        if (packet->payload_packet_len < 5 ||
241
0
            packet->payload[packet->payload_packet_len - 1] != 0x00 ||
242
0
            packet->payload[2] != 0x00 || packet->payload[3] > 0x07)
243
0
        {
244
0
          NDPI_EXCLUDE_DISSECTOR(ndpi_struct, flow);
245
0
          return;
246
0
        }
247
0
        break;
248
249
0
    case 0x06:
250
        /* Option Acknowledgment (OACK) */
251
252
0
        {
253
0
          size_t offset = 2;
254
255
0
          if (tftp_dissect_options(packet, &offset) != 0)
256
0
          {
257
0
            NDPI_EXCLUDE_DISSECTOR(ndpi_struct, flow);
258
0
            return;
259
0
          }
260
0
        }
261
262
        /* We have seen enough and do not need any more TFTP packets. */
263
0
        NDPI_LOG_INFO(ndpi_struct, "found tftp (OACK)\n");
264
0
        ndpi_int_tftp_add_connection(ndpi_struct, flow);
265
0
        break;
266
267
0
    default:
268
0
        NDPI_EXCLUDE_DISSECTOR(ndpi_struct, flow);
269
0
        return;
270
0
  }
271
272
0
  if (flow->l4.udp.tftp_stage < 3)
273
0
  {
274
0
    NDPI_LOG_DBG2(ndpi_struct, "maybe tftp. need next packet\n");
275
0
    flow->l4.udp.tftp_stage++;
276
0
    return;
277
0
  }
278
279
0
  NDPI_LOG_INFO(ndpi_struct, "found tftp\n");
280
0
  ndpi_int_tftp_add_connection(ndpi_struct, flow);
281
0
}
282
283
284
void init_tftp_dissector(struct ndpi_detection_module_struct *ndpi_struct)
285
1
{
286
1
  register_dissector("TFTP", ndpi_struct,
287
1
                     ndpi_search_tftp,
288
1
                     NDPI_SELECTION_BITMASK_PROTOCOL_V4_V6_UDP_WITH_PAYLOAD,
289
1
                     1, NDPI_PROTOCOL_TFTP);
290
1
}
291