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
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	{ NULL,		NULL, NULL, NULL, 0 }
58};
59
60/* By default allow them */
61int options_rfc_enabled = 1;
62int 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
81int
82option_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
99int
100option_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
131int
132option_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
161int
162option_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
222int
223option_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 */
279uint16_t
280make_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 */
323int
324parse_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 */
383void
384init_options(void)
385{
386
387	options[OPT_ROLLOVER].o_request = strdup("0");
388}
389