/src/freeradius-server/src/protocols/tftp/decode.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * This program is free software; you can redistribute it and/or modify |
3 | | * it under the terms of the GNU General Public License as published by |
4 | | * the Free Software Foundation; either version 2 of the License, or |
5 | | * (at your option) any later version. |
6 | | * |
7 | | * This program is distributed in the hope that it will be useful, |
8 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
9 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
10 | | * GNU General Public License for more details. |
11 | | * |
12 | | * You should have received a copy of the GNU General Public License |
13 | | * along with this program; if not, write to the Free Software |
14 | | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA |
15 | | */ |
16 | | |
17 | | /** |
18 | | * $Id: 5d6fd95f83a3379ca21c6f9cd57b7c8ce966f528 $ |
19 | | * @file src/protocols/tftp/decode.c |
20 | | * @brief Functions to decode TFTP packets. |
21 | | * @author Jorge Pereira <jpereira@freeradius.org> |
22 | | * |
23 | | * @copyright 2021 The FreeRADIUS server project. |
24 | | * @copyright 2021 Network RADIUS SAS (legal@networkradius.com) |
25 | | */ |
26 | | RCSID("$Id: 5d6fd95f83a3379ca21c6f9cd57b7c8ce966f528 $") |
27 | | |
28 | | #include <freeradius-devel/util/udp.h> |
29 | | |
30 | | #include <freeradius-devel/io/test_point.h> |
31 | | |
32 | | #include "tftp.h" |
33 | | #include "attrs.h" |
34 | | |
35 | | /* |
36 | | * https://tools.ietf.org/html/rfc1350 |
37 | | * |
38 | | * Order of Headers |
39 | | * |
40 | | * 2 bytes |
41 | | * ---------------------------------------------------------- |
42 | | * | Local Medium | Internet | Datagram | TFTP Opcode | |
43 | | * ---------------------------------------------------------- |
44 | | * |
45 | | * TFTP Formats |
46 | | * |
47 | | * Type Op # Format without header |
48 | | * |
49 | | * 2 bytes string 1 byte string 1 byte |
50 | | * ----------------------------------------------- |
51 | | * RRQ/ | 01/02 | Filename | 0 | Mode | 0 | |
52 | | * WRQ ----------------------------------------------- |
53 | | * 2 bytes 2 bytes n bytes |
54 | | * --------------------------------- |
55 | | * DATA | 03 | Block # | Data | |
56 | | * --------------------------------- |
57 | | * 2 bytes 2 bytes |
58 | | * ------------------- |
59 | | * ACK | 04 | Block # | |
60 | | * -------------------- |
61 | | * 2 bytes 2 bytes string 1 byte |
62 | | * ---------------------------------------- |
63 | | * ERROR | 05 | ErrorCode | ErrMsg | 0 | |
64 | | * ---------------------------------------- |
65 | | * |
66 | | * Initial Connection Protocol for reading a file |
67 | | * |
68 | | * 1. Host A sends a "RRQ" to host B with source= A's TID, |
69 | | * destination= 69. |
70 | | * |
71 | | * 2. Host B sends a "DATA" (with block number= 1) to host A with |
72 | | * source= B's TID, destination= A's TID. |
73 | | */ |
74 | | int fr_tftp_decode(TALLOC_CTX *ctx, fr_pair_list_t *out, uint8_t const *data, size_t data_len) |
75 | 321 | { |
76 | 321 | uint8_t const *q, *p, *end; |
77 | 321 | uint16_t opcode; |
78 | 321 | fr_pair_t *vp = NULL; |
79 | | |
80 | 321 | if (data_len == 0) return -1; |
81 | | |
82 | 321 | if (data_len < FR_TFTP_HDR_LEN) { |
83 | 4 | fr_strerror_printf("TFTP packet is too small. (%zu < %d)", data_len, FR_TFTP_HDR_LEN); |
84 | 117 | error: |
85 | 117 | return -1; |
86 | 4 | } |
87 | | |
88 | 317 | p = data; |
89 | 317 | end = (data + data_len); |
90 | | |
91 | | /* Opcode */ |
92 | 317 | opcode = fr_nbo_to_uint16(p); |
93 | 317 | vp = fr_pair_afrom_da(ctx, attr_tftp_opcode); |
94 | 317 | if (!vp) goto error; |
95 | | |
96 | 317 | vp->vp_uint16 = opcode; |
97 | 317 | fr_pair_append(out, vp); |
98 | 317 | p += 2; |
99 | | |
100 | 317 | switch (opcode) { |
101 | 179 | case FR_OPCODE_VALUE_READ_REQUEST: |
102 | 253 | case FR_OPCODE_VALUE_WRITE_REQUEST: |
103 | | /* |
104 | | * 2 bytes string 1 byte string 1 byte string 1 byte string 1 byte |
105 | | * +------------------------------------------------------------------------------------+ |
106 | | * | Opcode | Filename | 0 | Mode | 0 | blksize | 0 | #blksize | 0 | |
107 | | * +------------------------------------------------------------------------------------+ |
108 | | * Figure 5-1: RRQ/WRQ packet |
109 | | */ |
110 | | |
111 | | /* first of all, here we should have always a '\0' */ |
112 | 253 | if (*(end - 1) != '\0') goto error_malformed; |
113 | | |
114 | | /* first character should be alpha numeric */ |
115 | 246 | if (!isalnum(p[0])) { |
116 | 2 | fr_strerror_printf("Invalid Filename"); |
117 | 2 | goto error; |
118 | 2 | } |
119 | | |
120 | | /* <filename> */ |
121 | 244 | q = memchr(p, '\0', (end - p)); |
122 | 244 | if (!(q && q[0] == '\0')) { |
123 | 84 | error_malformed: |
124 | 84 | fr_strerror_printf("Packet contains malformed attribute"); |
125 | 84 | return -1; |
126 | 0 | } |
127 | | |
128 | 244 | vp = fr_pair_afrom_da(ctx, attr_tftp_filename); |
129 | 244 | if (!vp) goto error; |
130 | | |
131 | 244 | fr_pair_value_bstrndup(vp, (char const *)p, (q - p), true); |
132 | 244 | fr_pair_append(out, vp); |
133 | 244 | p += (q - p) + 1 /* \0 */; |
134 | | |
135 | | /* <mode> */ |
136 | 244 | q = memchr(p, '\0', (end - p)); |
137 | 244 | if (!(q && q[0] == '\0')) goto error_malformed; |
138 | | |
139 | 225 | vp = fr_pair_afrom_da(ctx, attr_tftp_mode); |
140 | 225 | if (!vp) goto error; |
141 | | |
142 | | /* (netascii || ascii || octet) + \0 */ |
143 | 225 | if ((q - p) == 5 && !memcmp(p, "octet", 5)) { |
144 | 117 | p += 5; |
145 | 117 | vp->vp_uint8 = FR_MODE_VALUE_OCTET; |
146 | 117 | } else if ((q - p) == 5 && !memcmp(p, "ascii", 5)) { |
147 | 3 | p += 5; |
148 | 3 | vp->vp_uint8 = FR_MODE_VALUE_ASCII; |
149 | 105 | } else if ((q - p) == 8 && !memcmp(p, "netascii", 8)) { |
150 | 1 | p += 8; |
151 | 1 | vp->vp_uint8 = FR_MODE_VALUE_ASCII; |
152 | 104 | } else { |
153 | 104 | fr_strerror_printf("Invalid Mode"); |
154 | 104 | goto error; |
155 | 104 | } |
156 | | |
157 | 121 | fr_pair_append(out, vp); |
158 | 121 | p += 1 /* \0 */; |
159 | | |
160 | 121 | if (p >= end) goto done; |
161 | | |
162 | | /* |
163 | | * Once here, the next 'blksize' is optional. |
164 | | * At least: | blksize | \0 | #blksize | \0 | |
165 | | */ |
166 | 118 | if ((end - p) < 10) goto error_malformed; |
167 | | |
168 | 112 | if (!memcmp(p, "blksize\0", 8)) { |
169 | 93 | char *p_end = NULL; |
170 | 93 | long blksize; |
171 | | |
172 | 93 | p += 8; |
173 | | |
174 | 93 | if (p >= end || (end - p) < 1 || (end - p) > 6) goto error_malformed; |
175 | | |
176 | 46 | vp = fr_pair_afrom_da(ctx, attr_tftp_block_size); |
177 | 46 | if (!vp) goto error; |
178 | | |
179 | 46 | blksize = strtol((const char *)p, &p_end, 10); |
180 | | |
181 | 46 | if (p == (const uint8_t *)p_end || blksize > FR_TFTP_BLOCK_MAX_SIZE) { |
182 | 3 | fr_strerror_printf("Invalid Block-Size %ld value", blksize); |
183 | 3 | goto error; |
184 | 3 | } |
185 | | |
186 | 43 | vp->vp_uint16 = (uint16_t)blksize; |
187 | 43 | fr_pair_append(out, vp); |
188 | 43 | } |
189 | | |
190 | 62 | break; |
191 | | |
192 | 62 | case FR_OPCODE_VALUE_ACKNOWLEDGEMENT: |
193 | 37 | case FR_OPCODE_VALUE_DATA: |
194 | | /** |
195 | | * 2 bytes 2 bytes |
196 | | * --------------------- |
197 | | * | Opcode | Block # | |
198 | | * --------------------- |
199 | | * Figure 5-3: ACK packet |
200 | | */ |
201 | | |
202 | 37 | vp = fr_pair_afrom_da(ctx, attr_tftp_block); |
203 | 37 | if (!vp) goto error; |
204 | | |
205 | 37 | vp->vp_uint16 = fr_nbo_to_uint16(p); |
206 | | |
207 | 37 | fr_pair_append(out, vp); |
208 | | |
209 | | /* |
210 | | * From that point... |
211 | | * |
212 | | * 2 bytes 2 bytes n bytes |
213 | | * ---------------------------------- |
214 | | * | Opcode | Block # | Data | |
215 | | * ---------------------------------- |
216 | | * Figure 5-2: DATA packet |
217 | | */ |
218 | 37 | if (opcode != FR_OPCODE_VALUE_DATA) goto done; |
219 | | |
220 | 36 | if ((p + 2) >= end) goto error_malformed; |
221 | | |
222 | 35 | p += 2; |
223 | | |
224 | 35 | vp = fr_pair_afrom_da(ctx, attr_tftp_data); |
225 | 35 | if (!vp) goto error; |
226 | | |
227 | 35 | fr_pair_value_memdup(vp, p, (end - p), true); |
228 | 35 | fr_pair_append(out, vp); |
229 | | |
230 | 35 | break; |
231 | | |
232 | 23 | case FR_OPCODE_VALUE_ERROR: |
233 | | /** |
234 | | * 2 bytes 2 bytes string 1 byte |
235 | | * ----------------------------------------- |
236 | | * | Opcode | ErrorCode | ErrMsg | 0 | |
237 | | * ----------------------------------------- |
238 | | * |
239 | | * Figure 5-4: ERROR packet |
240 | | */ |
241 | | |
242 | 23 | if ((p + 2) >= end) goto error_malformed; |
243 | | |
244 | 22 | vp = fr_pair_afrom_da(ctx, attr_tftp_error_code); |
245 | 22 | if (!vp) goto error; |
246 | | |
247 | 22 | vp->vp_uint16 = fr_nbo_to_uint16(p); |
248 | | |
249 | 22 | fr_pair_append(out, vp); |
250 | | |
251 | 22 | p += 2; /* <ErrorCode> */ |
252 | 22 | q = memchr(p, '\0', (end - p)); |
253 | 22 | if (!q || q[0] != '\0') goto error_malformed; |
254 | | |
255 | 19 | vp = fr_pair_afrom_da(ctx, attr_tftp_error_message); |
256 | 19 | if (!vp) goto error; |
257 | | |
258 | 19 | fr_pair_value_bstrndup(vp, (char const *)p, (q - p), true); |
259 | 19 | fr_pair_append(out, vp); |
260 | | |
261 | 19 | break; |
262 | | |
263 | 4 | default: |
264 | 4 | fr_strerror_printf("Invalid TFTP opcode %#04x", opcode); |
265 | 4 | goto error; |
266 | 317 | } |
267 | | |
268 | 120 | done: |
269 | 120 | return data_len; |
270 | 317 | } |
271 | | |
272 | | /** |
273 | | * Used as the decoder ctx. |
274 | | */ |
275 | | typedef struct { |
276 | | int nothing; |
277 | | } fr_tftp_ctx_t; |
278 | | |
279 | | /* |
280 | | * Test points for protocol decode |
281 | | */ |
282 | | static ssize_t fr_tftp_decode_proto(TALLOC_CTX *ctx, fr_pair_list_t *out, |
283 | | uint8_t const *data, size_t data_len, UNUSED void *proto_ctx) |
284 | 321 | { |
285 | 321 | return fr_tftp_decode(ctx, out, data, data_len); |
286 | 321 | } |
287 | | |
288 | | static int _decode_test_ctx(UNUSED fr_tftp_ctx_t *proto_ctx) |
289 | 2.95k | { |
290 | 2.95k | fr_tftp_free(); |
291 | | |
292 | 2.95k | return 0; |
293 | 2.95k | } |
294 | | |
295 | | static int decode_test_ctx(void **out, TALLOC_CTX *ctx) |
296 | 7.59k | { |
297 | 7.59k | fr_tftp_ctx_t *test_ctx; |
298 | | |
299 | 7.59k | if (fr_tftp_init() < 0) return -1; |
300 | | |
301 | 7.59k | test_ctx = talloc_zero(ctx, fr_tftp_ctx_t); |
302 | 7.59k | if (!test_ctx) return -1; |
303 | | |
304 | 7.59k | talloc_set_destructor(test_ctx, _decode_test_ctx); |
305 | | |
306 | 7.59k | *out = test_ctx; |
307 | | |
308 | 7.59k | return 0; |
309 | 7.59k | } |
310 | | |
311 | | extern fr_test_point_proto_decode_t tftp_tp_decode_proto; |
312 | | fr_test_point_proto_decode_t tftp_tp_decode_proto = { |
313 | | .test_ctx = decode_test_ctx, |
314 | | .func = fr_tftp_decode_proto |
315 | | }; |