xref: /freebsd/libexec/tftpd/tftp-options.c (revision bdd1243d)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
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$");
30 
31 #include <sys/types.h>
32 #include <sys/socket.h>
33 #include <sys/stat.h>
34 #include <sys/sysctl.h>
35 
36 #include <netinet/in.h>
37 #include <arpa/tftp.h>
38 
39 #include <ctype.h>
40 #include <stdarg.h>
41 #include <stdio.h>
42 #include <stdlib.h>
43 #include <string.h>
44 #include <syslog.h>
45 
46 #include "tftp-utils.h"
47 #include "tftp-io.h"
48 #include "tftp-options.h"
49 
50 /*
51  * Option handlers
52  */
53 
54 struct options options[] = {
55 	{ "tsize",	NULL, NULL, NULL /* option_tsize */, 1 },
56 	{ "timeout",	NULL, NULL, option_timeout, 1 },
57 	{ "blksize",	NULL, NULL, option_blksize, 1 },
58 	{ "blksize2",	NULL, NULL, option_blksize2, 0 },
59 	{ "rollover",	NULL, NULL, option_rollover, 0 },
60 	{ "windowsize",	NULL, NULL, option_windowsize, 1 },
61 	{ NULL,		NULL, NULL, NULL, 0 }
62 };
63 
64 /* By default allow them */
65 int options_rfc_enabled = 1;
66 int options_extra_enabled = 1;
67 
68 int
69 options_set_request(enum opt_enum opt, const char *fmt, ...)
70 {
71 	va_list ap;
72 	char *str;
73 	int ret;
74 
75 	if (fmt == NULL) {
76 		str = NULL;
77 	} else {
78 		va_start(ap, fmt);
79 		ret = vasprintf(&str, fmt, ap);
80 		va_end(ap);
81 		if (ret < 0)
82 			return (ret);
83 	}
84 	if (options[opt].o_request != NULL &&
85 	    options[opt].o_request != options[opt].o_reply)
86 		free(options[opt].o_request);
87 	options[opt].o_request = str;
88 	return (0);
89 }
90 
91 int
92 options_set_reply(enum opt_enum opt, const char *fmt, ...)
93 {
94 	va_list ap;
95 	char *str;
96 	int ret;
97 
98 	if (fmt == NULL) {
99 		str = NULL;
100 	} else {
101 		va_start(ap, fmt);
102 		ret = vasprintf(&str, fmt, ap);
103 		va_end(ap);
104 		if (ret < 0)
105 			return (ret);
106 	}
107 	if (options[opt].o_reply != NULL &&
108 	    options[opt].o_reply != options[opt].o_request)
109 		free(options[opt].o_reply);
110 	options[opt].o_reply = str;
111 	return (0);
112 }
113 
114 static void
115 options_set_reply_equal_request(enum opt_enum opt)
116 {
117 
118 	if (options[opt].o_reply != NULL &&
119 	    options[opt].o_reply != options[opt].o_request)
120 		free(options[opt].o_reply);
121 	options[opt].o_reply = options[opt].o_request;
122 }
123 
124 /*
125  * Rules for the option handlers:
126  * - If there is no o_request, there will be no processing.
127  *
128  * For servers
129  * - Logging is done as warnings.
130  * - The handler exit()s if there is a serious problem with the
131  *   values submitted in the option.
132  *
133  * For clients
134  * - Logging is done as errors. After all, the server shouldn't
135  *   return rubbish.
136  * - The handler returns if there is a serious problem with the
137  *   values submitted in the option.
138  * - Sending the EBADOP packets is done by the handler.
139  */
140 
141 int
142 option_tsize(int peer __unused, struct tftphdr *tp __unused, int mode,
143     struct stat *stbuf)
144 {
145 
146 	if (options[OPT_TSIZE].o_request == NULL)
147 		return (0);
148 
149 	if (mode == RRQ)
150 		options_set_reply(OPT_TSIZE, "%ju", (uintmax_t)stbuf->st_size);
151 	else
152 		/* XXX Allows writes of all sizes. */
153 		options_set_reply_equal_request(OPT_TSIZE);
154 	return (0);
155 }
156 
157 int
158 option_timeout(int peer)
159 {
160 	int to;
161 
162 	if (options[OPT_TIMEOUT].o_request == NULL)
163 		return (0);
164 
165 	to = atoi(options[OPT_TIMEOUT].o_request);
166 	if (to < TIMEOUT_MIN || to > TIMEOUT_MAX) {
167 		tftp_log(acting_as_client ? LOG_ERR : LOG_WARNING,
168 		    "Received bad value for timeout. "
169 		    "Should be between %d and %d, received %d",
170 		    TIMEOUT_MIN, TIMEOUT_MAX, to);
171 		send_error(peer, EBADOP);
172 		if (acting_as_client)
173 			return (1);
174 		exit(1);
175 	} else {
176 		timeoutpacket = to;
177 		options_set_reply_equal_request(OPT_TIMEOUT);
178 	}
179 	settimeouts(timeoutpacket, timeoutnetwork, maxtimeouts);
180 
181 	if (debug & DEBUG_OPTIONS)
182 		tftp_log(LOG_DEBUG, "Setting timeout to '%s'",
183 			options[OPT_TIMEOUT].o_reply);
184 
185 	return (0);
186 }
187 
188 int
189 option_rollover(int peer)
190 {
191 
192 	if (options[OPT_ROLLOVER].o_request == NULL)
193 		return (0);
194 
195 	if (strcmp(options[OPT_ROLLOVER].o_request, "0") != 0
196 	 && strcmp(options[OPT_ROLLOVER].o_request, "1") != 0) {
197 		tftp_log(acting_as_client ? LOG_ERR : LOG_WARNING,
198 		    "Bad value for rollover, "
199 		    "should be either 0 or 1, received '%s', "
200 		    "ignoring request",
201 		    options[OPT_ROLLOVER].o_request);
202 		if (acting_as_client) {
203 			send_error(peer, EBADOP);
204 			return (1);
205 		}
206 		return (0);
207 	}
208 	options_set_reply_equal_request(OPT_ROLLOVER);
209 
210 	if (debug & DEBUG_OPTIONS)
211 		tftp_log(LOG_DEBUG, "Setting rollover to '%s'",
212 			options[OPT_ROLLOVER].o_reply);
213 
214 	return (0);
215 }
216 
217 int
218 option_blksize(int peer)
219 {
220 	u_long maxdgram;
221 	size_t len;
222 
223 	if (options[OPT_BLKSIZE].o_request == NULL)
224 		return (0);
225 
226 	/* maximum size of an UDP packet according to the system */
227 	len = sizeof(maxdgram);
228 	if (sysctlbyname("net.inet.udp.maxdgram",
229 	    &maxdgram, &len, NULL, 0) < 0) {
230 		tftp_log(LOG_ERR, "sysctl: net.inet.udp.maxdgram");
231 		return (acting_as_client ? 1 : 0);
232 	}
233 	maxdgram -= 4; /* leave room for header */
234 
235 	int size = atoi(options[OPT_BLKSIZE].o_request);
236 	if (size < BLKSIZE_MIN || size > BLKSIZE_MAX) {
237 		if (acting_as_client) {
238 			tftp_log(LOG_ERR,
239 			    "Invalid blocksize (%d bytes), aborting",
240 			    size);
241 			send_error(peer, EBADOP);
242 			return (1);
243 		} else {
244 			tftp_log(LOG_WARNING,
245 			    "Invalid blocksize (%d bytes), ignoring request",
246 			    size);
247 			return (0);
248 		}
249 	}
250 
251 	if (size > (int)maxdgram) {
252 		if (acting_as_client) {
253 			tftp_log(LOG_ERR,
254 			    "Invalid blocksize (%d bytes), "
255 			    "net.inet.udp.maxdgram sysctl limits it to "
256 			    "%ld bytes.\n", size, maxdgram);
257 			send_error(peer, EBADOP);
258 			return (1);
259 		} else {
260 			tftp_log(LOG_WARNING,
261 			    "Invalid blocksize (%d bytes), "
262 			    "net.inet.udp.maxdgram sysctl limits it to "
263 			    "%ld bytes.\n", size, maxdgram);
264 			size = maxdgram;
265 			/* No reason to return */
266 		}
267 	}
268 
269 	options_set_reply(OPT_BLKSIZE, "%d", size);
270 	segsize = size;
271 	pktsize = size + 4;
272 	if (debug & DEBUG_OPTIONS)
273 		tftp_log(LOG_DEBUG, "Setting blksize to '%s'",
274 		    options[OPT_BLKSIZE].o_reply);
275 
276 	return (0);
277 }
278 
279 int
280 option_blksize2(int peer __unused)
281 {
282 	u_long	maxdgram;
283 	int	size, i;
284 	size_t	len;
285 
286 	int sizes[] = {
287 		8, 16, 32, 64, 128, 256, 512, 1024,
288 		2048, 4096, 8192, 16384, 32768, 0
289 	};
290 
291 	if (options[OPT_BLKSIZE2].o_request == NULL)
292 		return (0);
293 
294 	/* maximum size of an UDP packet according to the system */
295 	len = sizeof(maxdgram);
296 	if (sysctlbyname("net.inet.udp.maxdgram",
297 	    &maxdgram, &len, NULL, 0) < 0) {
298 		tftp_log(LOG_ERR, "sysctl: net.inet.udp.maxdgram");
299 		return (acting_as_client ? 1 : 0);
300 	}
301 
302 	size = atoi(options[OPT_BLKSIZE2].o_request);
303 	for (i = 0; sizes[i] != 0; i++) {
304 		if (size == sizes[i]) break;
305 	}
306 	if (sizes[i] == 0) {
307 		tftp_log(LOG_INFO,
308 		    "Invalid blocksize2 (%d bytes), ignoring request", size);
309 		return (acting_as_client ? 1 : 0);
310 	}
311 
312 	if (size > (int)maxdgram) {
313 		for (i = 0; sizes[i+1] != 0; i++) {
314 			if ((int)maxdgram < sizes[i+1]) break;
315 		}
316 		tftp_log(LOG_INFO,
317 		    "Invalid blocksize2 (%d bytes), net.inet.udp.maxdgram "
318 		    "sysctl limits it to %ld bytes.\n", size, maxdgram);
319 		size = sizes[i];
320 		/* No need to return */
321 	}
322 
323 	options_set_reply(OPT_BLKSIZE2, "%d", size);
324 	segsize = size;
325 	pktsize = size + 4;
326 	if (debug & DEBUG_OPTIONS)
327 		tftp_log(LOG_DEBUG, "Setting blksize2 to '%s'",
328 		    options[OPT_BLKSIZE2].o_reply);
329 
330 	return (0);
331 }
332 
333 int
334 option_windowsize(int peer)
335 {
336 	int size;
337 
338 	if (options[OPT_WINDOWSIZE].o_request == NULL)
339 		return (0);
340 
341 	size = atoi(options[OPT_WINDOWSIZE].o_request);
342 	if (size < WINDOWSIZE_MIN || size > WINDOWSIZE_MAX) {
343 		if (acting_as_client) {
344 			tftp_log(LOG_ERR,
345 			    "Invalid windowsize (%d blocks), aborting",
346 			    size);
347 			send_error(peer, EBADOP);
348 			return (1);
349 		} else {
350 			tftp_log(LOG_WARNING,
351 			    "Invalid windowsize (%d blocks), ignoring request",
352 			    size);
353 			return (0);
354 		}
355 	}
356 
357 	/* XXX: Should force a windowsize of 1 for non-seekable files. */
358 	options_set_reply(OPT_WINDOWSIZE, "%d", size);
359 	windowsize = size;
360 
361 	if (debug & DEBUG_OPTIONS)
362 		tftp_log(LOG_DEBUG, "Setting windowsize to '%s'",
363 		    options[OPT_WINDOWSIZE].o_reply);
364 
365 	return (0);
366 }
367 
368 /*
369  * Append the available options to the header
370  */
371 uint16_t
372 make_options(int peer __unused, char *buffer, uint16_t size) {
373 	int	i;
374 	char	*value;
375 	const char *option;
376 	uint16_t length;
377 	uint16_t returnsize = 0;
378 
379 	if (!options_rfc_enabled) return (0);
380 
381 	for (i = 0; options[i].o_type != NULL; i++) {
382 		if (options[i].rfc == 0 && !options_extra_enabled)
383 			continue;
384 
385 		option = options[i].o_type;
386 		if (acting_as_client)
387 			value = options[i].o_request;
388 		else
389 			value = options[i].o_reply;
390 		if (value == NULL)
391 			continue;
392 
393 		length = strlen(value) + strlen(option) + 2;
394 		if (size <= length) {
395 			tftp_log(LOG_ERR,
396 			    "Running out of option space for "
397 			    "option '%s' with value '%s': "
398 			    "needed %d bytes, got %d bytes",
399 			    option, value, size, length);
400 			continue;
401 		}
402 
403 		sprintf(buffer, "%s%c%s%c", option, '\000', value, '\000');
404 		size -= length;
405 		buffer += length;
406 		returnsize += length;
407 	}
408 
409 	return (returnsize);
410 }
411 
412 /*
413  * Parse the received options in the header
414  */
415 int
416 parse_options(int peer, char *buffer, uint16_t size)
417 {
418 	int	i, options_failed;
419 	char	*c, *cp, *option, *value;
420 
421 	if (!options_rfc_enabled) return (0);
422 
423 	/* Parse the options */
424 	cp = buffer;
425 	options_failed = 0;
426 	while (size > 0) {
427 		option = cp;
428 		i = get_field(peer, cp, size);
429 		cp += i;
430 
431 		value = cp;
432 		i = get_field(peer, cp, size);
433 		cp += i;
434 
435 		/* We are at the end */
436 		if (*option == '\0') break;
437 
438 		if (debug & DEBUG_OPTIONS)
439 			tftp_log(LOG_DEBUG,
440 			    "option: '%s' value: '%s'", option, value);
441 
442 		for (c = option; *c; c++)
443 			if (isupper(*c))
444 				*c = tolower(*c);
445 		for (i = 0; options[i].o_type != NULL; i++) {
446 			if (strcmp(option, options[i].o_type) == 0) {
447 				if (!acting_as_client)
448 					options_set_request(i, "%s", value);
449 				if (!options_extra_enabled && !options[i].rfc) {
450 					tftp_log(LOG_INFO,
451 					    "Option '%s' with value '%s' found "
452 					    "but it is not an RFC option",
453 					    option, value);
454 					continue;
455 				}
456 				if (options[i].o_handler)
457 					options_failed +=
458 					    (options[i].o_handler)(peer);
459 				break;
460 			}
461 		}
462 		if (options[i].o_type == NULL)
463 			tftp_log(LOG_WARNING,
464 			    "Unknown option: '%s'", option);
465 
466 		size -= strlen(option) + strlen(value) + 2;
467 	}
468 
469 	return (options_failed);
470 }
471 
472 /*
473  * Set some default values in the options
474  */
475 void
476 init_options(void)
477 {
478 
479 	options_set_request(OPT_ROLLOVER, "0");
480 }
481