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
53struct 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 */
64int options_rfc_enabled = 1;
65int 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
84int
85option_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
102int
103option_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
134int
135option_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
164int
165option_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
225int
226option_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
279int
280option_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 */
317uint16_t
318make_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 */
361int
362parse_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 */
421void
422init_options(void)
423{
424
425	options[OPT_ROLLOVER].o_request = strdup("0");
426}
427