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