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
51struct 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 */
62int options_rfc_enabled = 1;
63int options_extra_enabled = 1;
64
65int
66options_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
88int
89options_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
111static void
112options_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
138int
139option_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
154int
155option_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
185int
186option_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
214int
215option_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
276int
277option_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
330int
331option_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 */
368uint16_t
369make_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 */
412int
413parse_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 */
472void
473init_options(void)
474{
475
476	options_set_request(OPT_ROLLOVER, "0");
477}
478