1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3 *
4 * Copyright (C) 2008 Edwin Groothuis. All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 * SUCH DAMAGE.
26 */
27
28 #include <sys/cdefs.h>
29 __FBSDID("$FreeBSD: stable/12/libexec/tftpd/tftp-options.c 367305 2020-11-03 20:43:01Z jhb $");
30
31 #include <sys/socket.h>
32 #include <sys/types.h>
33 #include <sys/sysctl.h>
34 #include <sys/stat.h>
35
36 #include <netinet/in.h>
37 #include <arpa/tftp.h>
38
39 #include <ctype.h>
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <string.h>
43 #include <syslog.h>
44
45 #include "tftp-utils.h"
46 #include "tftp-io.h"
47 #include "tftp-options.h"
48
49 /*
50 * Option handlers
51 */
52
53 struct options options[] = {
54 { "tsize", NULL, NULL, NULL /* option_tsize */, 1 },
55 { "timeout", NULL, NULL, option_timeout, 1 },
56 { "blksize", NULL, NULL, option_blksize, 1 },
57 { "blksize2", NULL, NULL, option_blksize2, 0 },
58 { "rollover", NULL, NULL, option_rollover, 0 },
59 { "windowsize", NULL, NULL, option_windowsize, 1 },
60 { NULL, NULL, NULL, NULL, 0 }
61 };
62
63 /* By default allow them */
64 int options_rfc_enabled = 1;
65 int options_extra_enabled = 1;
66
67 /*
68 * Rules for the option handlers:
69 * - If there is no o_request, there will be no processing.
70 *
71 * For servers
72 * - Logging is done as warnings.
73 * - The handler exit()s if there is a serious problem with the
74 * values submitted in the option.
75 *
76 * For clients
77 * - Logging is done as errors. After all, the server shouldn't
78 * return rubbish.
79 * - The handler returns if there is a serious problem with the
80 * values submitted in the option.
81 * - Sending the EBADOP packets is done by the handler.
82 */
83
84 int
option_tsize(int peer __unused,struct tftphdr * tp __unused,int mode,struct stat * stbuf)85 option_tsize(int peer __unused, struct tftphdr *tp __unused, int mode,
86 struct stat *stbuf)
87 {
88
89 if (options[OPT_TSIZE].o_request == NULL)
90 return (0);
91
92 if (mode == RRQ)
93 asprintf(&options[OPT_TSIZE].o_reply,
94 "%ju", stbuf->st_size);
95 else
96 /* XXX Allows writes of all sizes. */
97 options[OPT_TSIZE].o_reply =
98 strdup(options[OPT_TSIZE].o_request);
99 return (0);
100 }
101
102 int
option_timeout(int peer)103 option_timeout(int peer)
104 {
105 int to;
106
107 if (options[OPT_TIMEOUT].o_request == NULL)
108 return (0);
109
110 to = atoi(options[OPT_TIMEOUT].o_request);
111 if (to < TIMEOUT_MIN || to > TIMEOUT_MAX) {
112 tftp_log(acting_as_client ? LOG_ERR : LOG_WARNING,
113 "Received bad value for timeout. "
114 "Should be between %d and %d, received %d",
115 TIMEOUT_MIN, TIMEOUT_MAX, to);
116 send_error(peer, EBADOP);
117 if (acting_as_client)
118 return (1);
119 exit(1);
120 } else {
121 timeoutpacket = to;
122 options[OPT_TIMEOUT].o_reply =
123 strdup(options[OPT_TIMEOUT].o_request);
124 }
125 settimeouts(timeoutpacket, timeoutnetwork, maxtimeouts);
126
127 if (debug&DEBUG_OPTIONS)
128 tftp_log(LOG_DEBUG, "Setting timeout to '%s'",
129 options[OPT_TIMEOUT].o_reply);
130
131 return (0);
132 }
133
134 int
option_rollover(int peer)135 option_rollover(int peer)
136 {
137
138 if (options[OPT_ROLLOVER].o_request == NULL)
139 return (0);
140
141 if (strcmp(options[OPT_ROLLOVER].o_request, "0") != 0
142 && strcmp(options[OPT_ROLLOVER].o_request, "1") != 0) {
143 tftp_log(acting_as_client ? LOG_ERR : LOG_WARNING,
144 "Bad value for rollover, "
145 "should be either 0 or 1, received '%s', "
146 "ignoring request",
147 options[OPT_ROLLOVER].o_request);
148 if (acting_as_client) {
149 send_error(peer, EBADOP);
150 return (1);
151 }
152 return (0);
153 }
154 options[OPT_ROLLOVER].o_reply =
155 strdup(options[OPT_ROLLOVER].o_request);
156
157 if (debug&DEBUG_OPTIONS)
158 tftp_log(LOG_DEBUG, "Setting rollover to '%s'",
159 options[OPT_ROLLOVER].o_reply);
160
161 return (0);
162 }
163
164 int
option_blksize(int peer)165 option_blksize(int peer)
166 {
167 u_long maxdgram;
168 size_t len;
169
170 if (options[OPT_BLKSIZE].o_request == NULL)
171 return (0);
172
173 /* maximum size of an UDP packet according to the system */
174 len = sizeof(maxdgram);
175 if (sysctlbyname("net.inet.udp.maxdgram",
176 &maxdgram, &len, NULL, 0) < 0) {
177 tftp_log(LOG_ERR, "sysctl: net.inet.udp.maxdgram");
178 return (acting_as_client ? 1 : 0);
179 }
180
181 int size = atoi(options[OPT_BLKSIZE].o_request);
182 if (size < BLKSIZE_MIN || size > BLKSIZE_MAX) {
183 if (acting_as_client) {
184 tftp_log(LOG_ERR,
185 "Invalid blocksize (%d bytes), aborting",
186 size);
187 send_error(peer, EBADOP);
188 return (1);
189 } else {
190 tftp_log(LOG_WARNING,
191 "Invalid blocksize (%d bytes), ignoring request",
192 size);
193 return (0);
194 }
195 }
196
197 if (size > (int)maxdgram) {
198 if (acting_as_client) {
199 tftp_log(LOG_ERR,
200 "Invalid blocksize (%d bytes), "
201 "net.inet.udp.maxdgram sysctl limits it to "
202 "%ld bytes.\n", size, maxdgram);
203 send_error(peer, EBADOP);
204 return (1);
205 } else {
206 tftp_log(LOG_WARNING,
207 "Invalid blocksize (%d bytes), "
208 "net.inet.udp.maxdgram sysctl limits it to "
209 "%ld bytes.\n", size, maxdgram);
210 size = maxdgram;
211 /* No reason to return */
212 }
213 }
214
215 asprintf(&options[OPT_BLKSIZE].o_reply, "%d", size);
216 segsize = size;
217 pktsize = size + 4;
218 if (debug&DEBUG_OPTIONS)
219 tftp_log(LOG_DEBUG, "Setting blksize to '%s'",
220 options[OPT_BLKSIZE].o_reply);
221
222 return (0);
223 }
224
225 int
option_blksize2(int peer __unused)226 option_blksize2(int peer __unused)
227 {
228 u_long maxdgram;
229 int size, i;
230 size_t len;
231
232 int sizes[] = {
233 8, 16, 32, 64, 128, 256, 512, 1024,
234 2048, 4096, 8192, 16384, 32768, 0
235 };
236
237 if (options[OPT_BLKSIZE2].o_request == NULL)
238 return (0);
239
240 /* maximum size of an UDP packet according to the system */
241 len = sizeof(maxdgram);
242 if (sysctlbyname("net.inet.udp.maxdgram",
243 &maxdgram, &len, NULL, 0) < 0) {
244 tftp_log(LOG_ERR, "sysctl: net.inet.udp.maxdgram");
245 return (acting_as_client ? 1 : 0);
246 }
247
248 size = atoi(options[OPT_BLKSIZE2].o_request);
249 for (i = 0; sizes[i] != 0; i++) {
250 if (size == sizes[i]) break;
251 }
252 if (sizes[i] == 0) {
253 tftp_log(LOG_INFO,
254 "Invalid blocksize2 (%d bytes), ignoring request", size);
255 return (acting_as_client ? 1 : 0);
256 }
257
258 if (size > (int)maxdgram) {
259 for (i = 0; sizes[i+1] != 0; i++) {
260 if ((int)maxdgram < sizes[i+1]) break;
261 }
262 tftp_log(LOG_INFO,
263 "Invalid blocksize2 (%d bytes), net.inet.udp.maxdgram "
264 "sysctl limits it to %ld bytes.\n", size, maxdgram);
265 size = sizes[i];
266 /* No need to return */
267 }
268
269 asprintf(&options[OPT_BLKSIZE2].o_reply, "%d", size);
270 segsize = size;
271 pktsize = size + 4;
272 if (debug&DEBUG_OPTIONS)
273 tftp_log(LOG_DEBUG, "Setting blksize2 to '%s'",
274 options[OPT_BLKSIZE2].o_reply);
275
276 return (0);
277 }
278
279 int
option_windowsize(int peer)280 option_windowsize(int peer)
281 {
282 int size;
283
284 if (options[OPT_WINDOWSIZE].o_request == NULL)
285 return (0);
286
287 size = atoi(options[OPT_WINDOWSIZE].o_request);
288 if (size < WINDOWSIZE_MIN || size > WINDOWSIZE_MAX) {
289 if (acting_as_client) {
290 tftp_log(LOG_ERR,
291 "Invalid windowsize (%d blocks), aborting",
292 size);
293 send_error(peer, EBADOP);
294 return (1);
295 } else {
296 tftp_log(LOG_WARNING,
297 "Invalid windowsize (%d blocks), ignoring request",
298 size);
299 return (0);
300 }
301 }
302
303 /* XXX: Should force a windowsize of 1 for non-seekable files. */
304 asprintf(&options[OPT_WINDOWSIZE].o_reply, "%d", size);
305 windowsize = size;
306
307 if (debug&DEBUG_OPTIONS)
308 tftp_log(LOG_DEBUG, "Setting windowsize to '%s'",
309 options[OPT_WINDOWSIZE].o_reply);
310
311 return (0);
312 }
313
314 /*
315 * Append the available options to the header
316 */
317 uint16_t
make_options(int peer __unused,char * buffer,uint16_t size)318 make_options(int peer __unused, char *buffer, uint16_t size) {
319 int i;
320 char *value;
321 const char *option;
322 uint16_t length;
323 uint16_t returnsize = 0;
324
325 if (!options_rfc_enabled) return (0);
326
327 for (i = 0; options[i].o_type != NULL; i++) {
328 if (options[i].rfc == 0 && !options_extra_enabled)
329 continue;
330
331 option = options[i].o_type;
332 if (acting_as_client)
333 value = options[i].o_request;
334 else
335 value = options[i].o_reply;
336 if (value == NULL)
337 continue;
338
339 length = strlen(value) + strlen(option) + 2;
340 if (size <= length) {
341 tftp_log(LOG_ERR,
342 "Running out of option space for "
343 "option '%s' with value '%s': "
344 "needed %d bytes, got %d bytes",
345 option, value, size, length);
346 continue;
347 }
348
349 sprintf(buffer, "%s%c%s%c", option, '\000', value, '\000');
350 size -= length;
351 buffer += length;
352 returnsize += length;
353 }
354
355 return (returnsize);
356 }
357
358 /*
359 * Parse the received options in the header
360 */
361 int
parse_options(int peer,char * buffer,uint16_t size)362 parse_options(int peer, char *buffer, uint16_t size)
363 {
364 int i, options_failed;
365 char *c, *cp, *option, *value;
366
367 if (!options_rfc_enabled) return (0);
368
369 /* Parse the options */
370 cp = buffer;
371 options_failed = 0;
372 while (size > 0) {
373 option = cp;
374 i = get_field(peer, cp, size);
375 cp += i;
376
377 value = cp;
378 i = get_field(peer, cp, size);
379 cp += i;
380
381 /* We are at the end */
382 if (*option == '\0') break;
383
384 if (debug&DEBUG_OPTIONS)
385 tftp_log(LOG_DEBUG,
386 "option: '%s' value: '%s'", option, value);
387
388 for (c = option; *c; c++)
389 if (isupper(*c))
390 *c = tolower(*c);
391 for (i = 0; options[i].o_type != NULL; i++) {
392 if (strcmp(option, options[i].o_type) == 0) {
393 if (!acting_as_client)
394 options[i].o_request = value;
395 if (!options_extra_enabled && !options[i].rfc) {
396 tftp_log(LOG_INFO,
397 "Option '%s' with value '%s' found "
398 "but it is not an RFC option",
399 option, value);
400 continue;
401 }
402 if (options[i].o_handler)
403 options_failed +=
404 (options[i].o_handler)(peer);
405 break;
406 }
407 }
408 if (options[i].o_type == NULL)
409 tftp_log(LOG_WARNING,
410 "Unknown option: '%s'", option);
411
412 size -= strlen(option) + strlen(value) + 2;
413 }
414
415 return (options_failed);
416 }
417
418 /*
419 * Set some default values in the options
420 */
421 void
init_options(void)422 init_options(void)
423 {
424
425 options[OPT_ROLLOVER].o_request = strdup("0");
426 }
427