xref: /freebsd/libexec/tftpd/tftp-options.c (revision 3f0164abf32b9b761e0a2cb4bdca3a8b84f156d4)
1 /*
2  * Copyright (C) 2008 Edwin Groothuis. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
14  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16  * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
17  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
19  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
20  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
21  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
22  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
23  * SUCH DAMAGE.
24  */
25 
26 #include <sys/cdefs.h>
27 __FBSDID("$FreeBSD$");
28 
29 #include <sys/socket.h>
30 #include <sys/types.h>
31 #include <sys/sysctl.h>
32 #include <sys/stat.h>
33 
34 #include <netinet/in.h>
35 #include <arpa/tftp.h>
36 
37 #include <ctype.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 	{ NULL,		NULL, NULL, NULL, 0 }
58 };
59 
60 /* By default allow them */
61 int options_rfc_enabled = 1;
62 int options_extra_enabled = 1;
63 
64 /*
65  * Rules for the option handlers:
66  * - If there is no o_request, there will be no processing.
67  *
68  * For servers
69  * - Logging is done as warnings.
70  * - The handler exit()s if there is a serious problem with the
71  *   values submitted in the option.
72  *
73  * For clients
74  * - Logging is done as errors. After all, the server shouldn't
75  *   return rubbish.
76  * - The handler returns if there is a serious problem with the
77  *   values submitted in the option.
78  * - Sending the EBADOP packets is done by the handler.
79  */
80 
81 int
82 option_tsize(int peer __unused, struct tftphdr *tp __unused, int mode,
83     struct stat *stbuf)
84 {
85 
86 	if (options[OPT_TSIZE].o_request == NULL)
87 		return (0);
88 
89 	if (mode == RRQ)
90 		asprintf(&options[OPT_TSIZE].o_reply,
91 			"%ju", stbuf->st_size);
92 	else
93 		/* XXX Allows writes of all sizes. */
94 		options[OPT_TSIZE].o_reply =
95 			strdup(options[OPT_TSIZE].o_request);
96 	return (0);
97 }
98 
99 int
100 option_timeout(int peer)
101 {
102 	int to;
103 
104 	if (options[OPT_TIMEOUT].o_request == NULL)
105 		return (0);
106 
107 	to = atoi(options[OPT_TIMEOUT].o_request);
108 	if (to < TIMEOUT_MIN || to > TIMEOUT_MAX) {
109 		tftp_log(acting_as_client ? LOG_ERR : LOG_WARNING,
110 		    "Received bad value for timeout. "
111 		    "Should be between %d and %d, received %d",
112 		    TIMEOUT_MIN, TIMEOUT_MAX, to);
113 		send_error(peer, EBADOP);
114 		if (acting_as_client)
115 			return (1);
116 		exit(1);
117 	} else {
118 		timeoutpacket = to;
119 		options[OPT_TIMEOUT].o_reply =
120 			strdup(options[OPT_TIMEOUT].o_request);
121 	}
122 	settimeouts(timeoutpacket, timeoutnetwork, maxtimeouts);
123 
124 	if (debug&DEBUG_OPTIONS)
125 		tftp_log(LOG_DEBUG, "Setting timeout to '%s'",
126 			options[OPT_TIMEOUT].o_reply);
127 
128 	return (0);
129 }
130 
131 int
132 option_rollover(int peer)
133 {
134 
135 	if (options[OPT_ROLLOVER].o_request == NULL)
136 		return (0);
137 
138 	if (strcmp(options[OPT_ROLLOVER].o_request, "0") != 0
139 	 && strcmp(options[OPT_ROLLOVER].o_request, "1") != 0) {
140 		tftp_log(acting_as_client ? LOG_ERR : LOG_WARNING,
141 		    "Bad value for rollover, "
142 		    "should be either 0 or 1, received '%s', "
143 		    "ignoring request",
144 		    options[OPT_ROLLOVER].o_request);
145 		if (acting_as_client) {
146 			send_error(peer, EBADOP);
147 			return (1);
148 		}
149 		return (0);
150 	}
151 	options[OPT_ROLLOVER].o_reply =
152 		strdup(options[OPT_ROLLOVER].o_request);
153 
154 	if (debug&DEBUG_OPTIONS)
155 		tftp_log(LOG_DEBUG, "Setting rollover to '%s'",
156 			options[OPT_ROLLOVER].o_reply);
157 
158 	return (0);
159 }
160 
161 int
162 option_blksize(int peer)
163 {
164 	u_long maxdgram;
165 	size_t len;
166 
167 	if (options[OPT_BLKSIZE].o_request == NULL)
168 		return (0);
169 
170 	/* maximum size of an UDP packet according to the system */
171 	len = sizeof(maxdgram);
172 	if (sysctlbyname("net.inet.udp.maxdgram",
173 	    &maxdgram, &len, NULL, 0) < 0) {
174 		tftp_log(LOG_ERR, "sysctl: net.inet.udp.maxdgram");
175 		return (acting_as_client ? 1 : 0);
176 	}
177 
178 	int size = atoi(options[OPT_BLKSIZE].o_request);
179 	if (size < BLKSIZE_MIN || size > BLKSIZE_MAX) {
180 		if (acting_as_client) {
181 			tftp_log(LOG_ERR,
182 			    "Invalid blocksize (%d bytes), aborting",
183 			    size);
184 			send_error(peer, EBADOP);
185 			return (1);
186 		} else {
187 			tftp_log(LOG_WARNING,
188 			    "Invalid blocksize (%d bytes), ignoring request",
189 			    size);
190 			return (0);
191 		}
192 	}
193 
194 	if (size > (int)maxdgram) {
195 		if (acting_as_client) {
196 			tftp_log(LOG_ERR,
197 			    "Invalid blocksize (%d bytes), "
198 			    "net.inet.udp.maxdgram sysctl limits it to "
199 			    "%ld bytes.\n", size, maxdgram);
200 			send_error(peer, EBADOP);
201 			return (1);
202 		} else {
203 			tftp_log(LOG_WARNING,
204 			    "Invalid blocksize (%d bytes), "
205 			    "net.inet.udp.maxdgram sysctl limits it to "
206 			    "%ld bytes.\n", size, maxdgram);
207 			size = maxdgram;
208 			/* No reason to return */
209 		}
210 	}
211 
212 	asprintf(&options[OPT_BLKSIZE].o_reply, "%d", size);
213 	segsize = size;
214 	pktsize = size + 4;
215 	if (debug&DEBUG_OPTIONS)
216 		tftp_log(LOG_DEBUG, "Setting blksize to '%s'",
217 		    options[OPT_BLKSIZE].o_reply);
218 
219 	return (0);
220 }
221 
222 int
223 option_blksize2(int peer __unused)
224 {
225 	u_long	maxdgram;
226 	int	size, i;
227 	size_t	len;
228 
229 	int sizes[] = {
230 		8, 16, 32, 64, 128, 256, 512, 1024,
231 		2048, 4096, 8192, 16384, 32768, 0
232 	};
233 
234 	if (options[OPT_BLKSIZE2].o_request == NULL)
235 		return (0);
236 
237 	/* maximum size of an UDP packet according to the system */
238 	len = sizeof(maxdgram);
239 	if (sysctlbyname("net.inet.udp.maxdgram",
240 	    &maxdgram, &len, NULL, 0) < 0) {
241 		tftp_log(LOG_ERR, "sysctl: net.inet.udp.maxdgram");
242 		return (acting_as_client ? 1 : 0);
243 	}
244 
245 	size = atoi(options[OPT_BLKSIZE2].o_request);
246 	for (i = 0; sizes[i] != 0; i++) {
247 		if (size == sizes[i]) break;
248 	}
249 	if (sizes[i] == 0) {
250 		tftp_log(LOG_INFO,
251 		    "Invalid blocksize2 (%d bytes), ignoring request", size);
252 		return (acting_as_client ? 1 : 0);
253 	}
254 
255 	if (size > (int)maxdgram) {
256 		for (i = 0; sizes[i+1] != 0; i++) {
257 			if ((int)maxdgram < sizes[i+1]) break;
258 		}
259 		tftp_log(LOG_INFO,
260 		    "Invalid blocksize2 (%d bytes), net.inet.udp.maxdgram "
261 		    "sysctl limits it to %ld bytes.\n", size, maxdgram);
262 		size = sizes[i];
263 		/* No need to return */
264 	}
265 
266 	asprintf(&options[OPT_BLKSIZE2].o_reply, "%d", size);
267 	segsize = size;
268 	pktsize = size + 4;
269 	if (debug&DEBUG_OPTIONS)
270 		tftp_log(LOG_DEBUG, "Setting blksize2 to '%s'",
271 		    options[OPT_BLKSIZE2].o_reply);
272 
273 	return (0);
274 }
275 
276 /*
277  * Append the available options to the header
278  */
279 uint16_t
280 make_options(int peer __unused, char *buffer, uint16_t size) {
281 	int	i;
282 	char	*value;
283 	const char *option;
284 	uint16_t length;
285 	uint16_t returnsize = 0;
286 
287 	if (!options_rfc_enabled) return (0);
288 
289 	for (i = 0; options[i].o_type != NULL; i++) {
290 		if (options[i].rfc == 0 && !options_extra_enabled)
291 			continue;
292 
293 		option = options[i].o_type;
294 		if (acting_as_client)
295 			value = options[i].o_request;
296 		else
297 			value = options[i].o_reply;
298 		if (value == NULL)
299 			continue;
300 
301 		length = strlen(value) + strlen(option) + 2;
302 		if (size <= length) {
303 			tftp_log(LOG_ERR,
304 			    "Running out of option space for "
305 			    "option '%s' with value '%s': "
306 			    "needed %d bytes, got %d bytes",
307 			    option, value, size, length);
308 			continue;
309 		}
310 
311 		sprintf(buffer, "%s%c%s%c", option, '\000', value, '\000');
312 		size -= length;
313 		buffer += length;
314 		returnsize += length;
315 	}
316 
317 	return (returnsize);
318 }
319 
320 /*
321  * Parse the received options in the header
322  */
323 int
324 parse_options(int peer, char *buffer, uint16_t size)
325 {
326 	int	i, options_failed;
327 	char	*c, *cp, *option, *value;
328 
329 	if (!options_rfc_enabled) return (0);
330 
331 	/* Parse the options */
332 	cp = buffer;
333 	options_failed = 0;
334 	while (size > 0) {
335 		option = cp;
336 		i = get_field(peer, cp, size);
337 		cp += i;
338 
339 		value = cp;
340 		i = get_field(peer, cp, size);
341 		cp += i;
342 
343 		/* We are at the end */
344 		if (*option == '\0') break;
345 
346 		if (debug&DEBUG_OPTIONS)
347 			tftp_log(LOG_DEBUG,
348 			    "option: '%s' value: '%s'", option, value);
349 
350 		for (c = option; *c; c++)
351 			if (isupper(*c))
352 				*c = tolower(*c);
353 		for (i = 0; options[i].o_type != NULL; i++) {
354 			if (strcmp(option, options[i].o_type) == 0) {
355 				if (!acting_as_client)
356 					options[i].o_request = value;
357 				if (!options_extra_enabled && !options[i].rfc) {
358 					tftp_log(LOG_INFO,
359 					    "Option '%s' with value '%s' found "
360 					    "but it is not an RFC option",
361 					    option, value);
362 					continue;
363 				}
364 				if (options[i].o_handler)
365 					options_failed +=
366 					    (options[i].o_handler)(peer);
367 				break;
368 			}
369 		}
370 		if (options[i].o_type == NULL)
371 			tftp_log(LOG_WARNING,
372 			    "Unknown option: '%s'", option);
373 
374 		size -= strlen(option) + strlen(value) + 2;
375 	}
376 
377 	return (options_failed);
378 }
379 
380 /*
381  * Set some default values in the options
382  */
383 void
384 init_options(void)
385 {
386 
387 	options[OPT_ROLLOVER].o_request = strdup("0");
388 }
389