/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 | | |