xref: /freebsd/libexec/tftpd/tftp-options.c (revision 4db78cacdee1b6f3b7880eb8c5560e8edaf91698)
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 	{ 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 /*
67  * Rules for the option handlers:
68  * - If there is no o_request, there will be no processing.
69  *
70  * For servers
71  * - Logging is done as warnings.
72  * - The handler exit()s if there is a serious problem with the
73  *   values submitted in the option.
74  *
75  * For clients
76  * - Logging is done as errors. After all, the server shouldn't
77  *   return rubbish.
78  * - The handler returns if there is a serious problem with the
79  *   values submitted in the option.
80  * - Sending the EBADOP packets is done by the handler.
81  */
82 
83 int
84 option_tsize(int peer __unused, struct tftphdr *tp __unused, int mode,
85     struct stat *stbuf)
86 {
87 
88 	if (options[OPT_TSIZE].o_request == NULL)
89 		return (0);
90 
91 	if (mode == RRQ)
92 		asprintf(&options[OPT_TSIZE].o_reply,
93 			"%ju", stbuf->st_size);
94 	else
95 		/* XXX Allows writes of all sizes. */
96 		options[OPT_TSIZE].o_reply =
97 			strdup(options[OPT_TSIZE].o_request);
98 	return (0);
99 }
100 
101 int
102 option_timeout(int peer)
103 {
104 	int to;
105 
106 	if (options[OPT_TIMEOUT].o_request == NULL)
107 		return (0);
108 
109 	to = atoi(options[OPT_TIMEOUT].o_request);
110 	if (to < TIMEOUT_MIN || to > TIMEOUT_MAX) {
111 		tftp_log(acting_as_client ? LOG_ERR : LOG_WARNING,
112 		    "Received bad value for timeout. "
113 		    "Should be between %d and %d, received %d",
114 		    TIMEOUT_MIN, TIMEOUT_MAX, to);
115 		send_error(peer, EBADOP);
116 		if (acting_as_client)
117 			return (1);
118 		exit(1);
119 	} else {
120 		timeoutpacket = to;
121 		options[OPT_TIMEOUT].o_reply =
122 			strdup(options[OPT_TIMEOUT].o_request);
123 	}
124 	settimeouts(timeoutpacket, timeoutnetwork, maxtimeouts);
125 
126 	if (debug&DEBUG_OPTIONS)
127 		tftp_log(LOG_DEBUG, "Setting timeout to '%s'",
128 			options[OPT_TIMEOUT].o_reply);
129 
130 	return (0);
131 }
132 
133 int
134 option_rollover(int peer)
135 {
136 
137 	if (options[OPT_ROLLOVER].o_request == NULL)
138 		return (0);
139 
140 	if (strcmp(options[OPT_ROLLOVER].o_request, "0") != 0
141 	 && strcmp(options[OPT_ROLLOVER].o_request, "1") != 0) {
142 		tftp_log(acting_as_client ? LOG_ERR : LOG_WARNING,
143 		    "Bad value for rollover, "
144 		    "should be either 0 or 1, received '%s', "
145 		    "ignoring request",
146 		    options[OPT_ROLLOVER].o_request);
147 		if (acting_as_client) {
148 			send_error(peer, EBADOP);
149 			return (1);
150 		}
151 		return (0);
152 	}
153 	options[OPT_ROLLOVER].o_reply =
154 		strdup(options[OPT_ROLLOVER].o_request);
155 
156 	if (debug&DEBUG_OPTIONS)
157 		tftp_log(LOG_DEBUG, "Setting rollover to '%s'",
158 			options[OPT_ROLLOVER].o_reply);
159 
160 	return (0);
161 }
162 
163 int
164 option_blksize(int peer)
165 {
166 	u_long maxdgram;
167 	size_t len;
168 
169 	if (options[OPT_BLKSIZE].o_request == NULL)
170 		return (0);
171 
172 	/* maximum size of an UDP packet according to the system */
173 	len = sizeof(maxdgram);
174 	if (sysctlbyname("net.inet.udp.maxdgram",
175 	    &maxdgram, &len, NULL, 0) < 0) {
176 		tftp_log(LOG_ERR, "sysctl: net.inet.udp.maxdgram");
177 		return (acting_as_client ? 1 : 0);
178 	}
179 
180 	int size = atoi(options[OPT_BLKSIZE].o_request);
181 	if (size < BLKSIZE_MIN || size > BLKSIZE_MAX) {
182 		if (acting_as_client) {
183 			tftp_log(LOG_ERR,
184 			    "Invalid blocksize (%d bytes), aborting",
185 			    size);
186 			send_error(peer, EBADOP);
187 			return (1);
188 		} else {
189 			tftp_log(LOG_WARNING,
190 			    "Invalid blocksize (%d bytes), ignoring request",
191 			    size);
192 			return (0);
193 		}
194 	}
195 
196 	if (size > (int)maxdgram) {
197 		if (acting_as_client) {
198 			tftp_log(LOG_ERR,
199 			    "Invalid blocksize (%d bytes), "
200 			    "net.inet.udp.maxdgram sysctl limits it to "
201 			    "%ld bytes.\n", size, maxdgram);
202 			send_error(peer, EBADOP);
203 			return (1);
204 		} else {
205 			tftp_log(LOG_WARNING,
206 			    "Invalid blocksize (%d bytes), "
207 			    "net.inet.udp.maxdgram sysctl limits it to "
208 			    "%ld bytes.\n", size, maxdgram);
209 			size = maxdgram;
210 			/* No reason to return */
211 		}
212 	}
213 
214 	asprintf(&options[OPT_BLKSIZE].o_reply, "%d", size);
215 	segsize = size;
216 	pktsize = size + 4;
217 	if (debug&DEBUG_OPTIONS)
218 		tftp_log(LOG_DEBUG, "Setting blksize to '%s'",
219 		    options[OPT_BLKSIZE].o_reply);
220 
221 	return (0);
222 }
223 
224 int
225 option_blksize2(int peer __unused)
226 {
227 	u_long	maxdgram;
228 	int	size, i;
229 	size_t	len;
230 
231 	int sizes[] = {
232 		8, 16, 32, 64, 128, 256, 512, 1024,
233 		2048, 4096, 8192, 16384, 32768, 0
234 	};
235 
236 	if (options[OPT_BLKSIZE2].o_request == NULL)
237 		return (0);
238 
239 	/* maximum size of an UDP packet according to the system */
240 	len = sizeof(maxdgram);
241 	if (sysctlbyname("net.inet.udp.maxdgram",
242 	    &maxdgram, &len, NULL, 0) < 0) {
243 		tftp_log(LOG_ERR, "sysctl: net.inet.udp.maxdgram");
244 		return (acting_as_client ? 1 : 0);
245 	}
246 
247 	size = atoi(options[OPT_BLKSIZE2].o_request);
248 	for (i = 0; sizes[i] != 0; i++) {
249 		if (size == sizes[i]) break;
250 	}
251 	if (sizes[i] == 0) {
252 		tftp_log(LOG_INFO,
253 		    "Invalid blocksize2 (%d bytes), ignoring request", size);
254 		return (acting_as_client ? 1 : 0);
255 	}
256 
257 	if (size > (int)maxdgram) {
258 		for (i = 0; sizes[i+1] != 0; i++) {
259 			if ((int)maxdgram < sizes[i+1]) break;
260 		}
261 		tftp_log(LOG_INFO,
262 		    "Invalid blocksize2 (%d bytes), net.inet.udp.maxdgram "
263 		    "sysctl limits it to %ld bytes.\n", size, maxdgram);
264 		size = sizes[i];
265 		/* No need to return */
266 	}
267 
268 	asprintf(&options[OPT_BLKSIZE2].o_reply, "%d", size);
269 	segsize = size;
270 	pktsize = size + 4;
271 	if (debug&DEBUG_OPTIONS)
272 		tftp_log(LOG_DEBUG, "Setting blksize2 to '%s'",
273 		    options[OPT_BLKSIZE2].o_reply);
274 
275 	return (0);
276 }
277 
278 /*
279  * Append the available options to the header
280  */
281 uint16_t
282 make_options(int peer __unused, char *buffer, uint16_t size) {
283 	int	i;
284 	char	*value;
285 	const char *option;
286 	uint16_t length;
287 	uint16_t returnsize = 0;
288 
289 	if (!options_rfc_enabled) return (0);
290 
291 	for (i = 0; options[i].o_type != NULL; i++) {
292 		if (options[i].rfc == 0 && !options_extra_enabled)
293 			continue;
294 
295 		option = options[i].o_type;
296 		if (acting_as_client)
297 			value = options[i].o_request;
298 		else
299 			value = options[i].o_reply;
300 		if (value == NULL)
301 			continue;
302 
303 		length = strlen(value) + strlen(option) + 2;
304 		if (size <= length) {
305 			tftp_log(LOG_ERR,
306 			    "Running out of option space for "
307 			    "option '%s' with value '%s': "
308 			    "needed %d bytes, got %d bytes",
309 			    option, value, size, length);
310 			continue;
311 		}
312 
313 		sprintf(buffer, "%s%c%s%c", option, '\000', value, '\000');
314 		size -= length;
315 		buffer += length;
316 		returnsize += length;
317 	}
318 
319 	return (returnsize);
320 }
321 
322 /*
323  * Parse the received options in the header
324  */
325 int
326 parse_options(int peer, char *buffer, uint16_t size)
327 {
328 	int	i, options_failed;
329 	char	*c, *cp, *option, *value;
330 
331 	if (!options_rfc_enabled) return (0);
332 
333 	/* Parse the options */
334 	cp = buffer;
335 	options_failed = 0;
336 	while (size > 0) {
337 		option = cp;
338 		i = get_field(peer, cp, size);
339 		cp += i;
340 
341 		value = cp;
342 		i = get_field(peer, cp, size);
343 		cp += i;
344 
345 		/* We are at the end */
346 		if (*option == '\0') break;
347 
348 		if (debug&DEBUG_OPTIONS)
349 			tftp_log(LOG_DEBUG,
350 			    "option: '%s' value: '%s'", option, value);
351 
352 		for (c = option; *c; c++)
353 			if (isupper(*c))
354 				*c = tolower(*c);
355 		for (i = 0; options[i].o_type != NULL; i++) {
356 			if (strcmp(option, options[i].o_type) == 0) {
357 				if (!acting_as_client)
358 					options[i].o_request = value;
359 				if (!options_extra_enabled && !options[i].rfc) {
360 					tftp_log(LOG_INFO,
361 					    "Option '%s' with value '%s' found "
362 					    "but it is not an RFC option",
363 					    option, value);
364 					continue;
365 				}
366 				if (options[i].o_handler)
367 					options_failed +=
368 					    (options[i].o_handler)(peer);
369 				break;
370 			}
371 		}
372 		if (options[i].o_type == NULL)
373 			tftp_log(LOG_WARNING,
374 			    "Unknown option: '%s'", option);
375 
376 		size -= strlen(option) + strlen(value) + 2;
377 	}
378 
379 	return (options_failed);
380 }
381 
382 /*
383  * Set some default values in the options
384  */
385 void
386 init_options(void)
387 {
388 
389 	options[OPT_ROLLOVER].o_request = strdup("0");
390 }
391