1290931Srodrigc/*
2290931Srodrigc * Copyright (C) 2008 Edwin Groothuis. All rights reserved.
3290931Srodrigc *
4290931Srodrigc * Redistribution and use in source and binary forms, with or without
5290931Srodrigc * modification, are permitted provided that the following conditions
6290931Srodrigc * are met:
7290931Srodrigc * 1. Redistributions of source code must retain the above copyright
8290931Srodrigc *    notice, this list of conditions and the following disclaimer.
9290931Srodrigc * 2. Redistributions in binary form must reproduce the above copyright
10290931Srodrigc *    notice, this list of conditions and the following disclaimer in the
11290931Srodrigc *    documentation and/or other materials provided with the distribution.
12290931Srodrigc *
13290931Srodrigc * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
14290931Srodrigc * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15290931Srodrigc * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16290931Srodrigc * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
17290931Srodrigc * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18290931Srodrigc * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
19290931Srodrigc * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
20290931Srodrigc * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
21290931Srodrigc * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
22290931Srodrigc * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
23290931Srodrigc * SUCH DAMAGE.
24290931Srodrigc */
25290931Srodrigc
26290931Srodrigc#include <sys/cdefs.h>
27290931Srodrigc__FBSDID("$FreeBSD$");
28290931Srodrigc
29290931Srodrigc#include <sys/socket.h>
30290931Srodrigc#include <sys/types.h>
31290931Srodrigc#include <sys/sysctl.h>
32290931Srodrigc#include <sys/stat.h>
33290931Srodrigc
34290931Srodrigc#include <netinet/in.h>
35290931Srodrigc#include <arpa/tftp.h>
36290931Srodrigc
37290931Srodrigc#include <ctype.h>
38290931Srodrigc#include <stdio.h>
39290931Srodrigc#include <stdlib.h>
40290931Srodrigc#include <string.h>
41290931Srodrigc#include <syslog.h>
42290931Srodrigc
43290931Srodrigc#include "tftp-utils.h"
44290931Srodrigc#include "tftp-io.h"
45290931Srodrigc#include "tftp-options.h"
46290931Srodrigc
47290931Srodrigc/*
48290931Srodrigc * Option handlers
49290931Srodrigc */
50290931Srodrigc
51290931Srodrigcstruct options options[] = {
52290931Srodrigc	{ "tsize",	NULL, NULL, NULL /* option_tsize */, 1 },
53290931Srodrigc	{ "timeout",	NULL, NULL, option_timeout, 1 },
54290931Srodrigc	{ "blksize",	NULL, NULL, option_blksize, 1 },
55290931Srodrigc	{ "blksize2",	NULL, NULL, option_blksize2, 0 },
56290931Srodrigc	{ "rollover",	NULL, NULL, option_rollover, 0 },
57290931Srodrigc	{ NULL,		NULL, NULL, NULL, 0 }
58290931Srodrigc};
59290931Srodrigc
60290931Srodrigc/* By default allow them */
61290931Srodrigcint options_rfc_enabled = 1;
62290931Srodrigcint options_extra_enabled = 1;
63290931Srodrigc
64290931Srodrigc/*
65290931Srodrigc * Rules for the option handlers:
66290931Srodrigc * - If there is no o_request, there will be no processing.
67290931Srodrigc *
68290931Srodrigc * For servers
69290931Srodrigc * - Logging is done as warnings.
70290931Srodrigc * - The handler exit()s if there is a serious problem with the
71290931Srodrigc *   values submitted in the option.
72290931Srodrigc *
73290931Srodrigc * For clients
74290931Srodrigc * - Logging is done as errors. After all, the server shouldn't
75290931Srodrigc *   return rubbish.
76290931Srodrigc * - The handler returns if there is a serious problem with the
77290931Srodrigc *   values submitted in the option.
78290931Srodrigc * - Sending the EBADOP packets is done by the handler.
79290931Srodrigc */
80290931Srodrigc
81290931Srodrigcint
82290931Srodrigcoption_tsize(int peer __unused, struct tftphdr *tp __unused, int mode,
83290931Srodrigc    struct stat *stbuf)
84290931Srodrigc{
85290931Srodrigc
86290931Srodrigc	if (options[OPT_TSIZE].o_request == NULL)
87290931Srodrigc		return (0);
88290931Srodrigc
89290931Srodrigc	if (mode == RRQ)
90290931Srodrigc		asprintf(&options[OPT_TSIZE].o_reply,
91290931Srodrigc			"%ju", stbuf->st_size);
92290931Srodrigc	else
93290931Srodrigc		/* XXX Allows writes of all sizes. */
94290931Srodrigc		options[OPT_TSIZE].o_reply =
95290931Srodrigc			strdup(options[OPT_TSIZE].o_request);
96290931Srodrigc	return (0);
97290931Srodrigc}
98290931Srodrigc
99290931Srodrigcint
100290931Srodrigcoption_timeout(int peer)
101290931Srodrigc{
102290931Srodrigc	int to;
103290931Srodrigc
104290931Srodrigc	if (options[OPT_TIMEOUT].o_request == NULL)
105290931Srodrigc		return (0);
106290931Srodrigc
107290931Srodrigc	to = atoi(options[OPT_TIMEOUT].o_request);
108290931Srodrigc	if (to < TIMEOUT_MIN || to > TIMEOUT_MAX) {
109290931Srodrigc		tftp_log(acting_as_client ? LOG_ERR : LOG_WARNING,
110290931Srodrigc		    "Received bad value for timeout. "
111290931Srodrigc		    "Should be between %d and %d, received %d",
112290931Srodrigc		    TIMEOUT_MIN, TIMEOUT_MAX, to);
113290931Srodrigc		send_error(peer, EBADOP);
114290931Srodrigc		if (acting_as_client)
115290931Srodrigc			return (1);
116290931Srodrigc		exit(1);
117290931Srodrigc	} else {
118290931Srodrigc		timeoutpacket = to;
119290931Srodrigc		options[OPT_TIMEOUT].o_reply =
120290931Srodrigc			strdup(options[OPT_TIMEOUT].o_request);
121290931Srodrigc	}
122290931Srodrigc	settimeouts(timeoutpacket, timeoutnetwork, maxtimeouts);
123290931Srodrigc
124290931Srodrigc	if (debug&DEBUG_OPTIONS)
125290931Srodrigc		tftp_log(LOG_DEBUG, "Setting timeout to '%s'",
126290931Srodrigc			options[OPT_TIMEOUT].o_reply);
127290931Srodrigc
128290931Srodrigc	return (0);
129290931Srodrigc}
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