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