xref: /freebsd/libexec/tftpd/tftp-options.c (revision c66ec88fed842fbaad62c30d510644ceb7bd2d71)
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$");
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
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
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
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
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
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
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
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
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
422 init_options(void)
423 {
424 
425 	options[OPT_ROLLOVER].o_request = strdup("0");
426 }
427