tftp-options.c revision 213099
138136Sdfr/*
238136Sdfr * Copyright (C) 2008 Edwin Groothuis. All rights reserved.
338136Sdfr *
438136Sdfr * Redistribution and use in source and binary forms, with or without
538136Sdfr * modification, are permitted provided that the following conditions
638136Sdfr * are met:
738136Sdfr * 1. Redistributions of source code must retain the above copyright
838136Sdfr *    notice, this list of conditions and the following disclaimer.
938136Sdfr * 2. Redistributions in binary form must reproduce the above copyright
1038136Sdfr *    notice, this list of conditions and the following disclaimer in the
1138136Sdfr *    documentation and/or other materials provided with the distribution.
1238136Sdfr *
1338136Sdfr * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
1438136Sdfr * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1538136Sdfr * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
1638136Sdfr * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
1738136Sdfr * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
1838136Sdfr * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
1938136Sdfr * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2038136Sdfr * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2138136Sdfr * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2238136Sdfr * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2338136Sdfr * SUCH DAMAGE.
2438136Sdfr */
2538136Sdfr
2650477Speter#include <sys/cdefs.h>
2738136Sdfr__FBSDID("$FreeBSD: head/libexec/tftpd/tftp-options.c 213099 2010-09-24 10:40:17Z marius $");
2838136Sdfr
2947613Sdfr#include <sys/socket.h>
3047613Sdfr#include <sys/types.h>
3147613Sdfr#include <sys/sysctl.h>
3250769Sdfr#include <sys/stat.h>
3350769Sdfr
3450769Sdfr#include <netinet/in.h>
3550769Sdfr#include <arpa/tftp.h>
3647398Sdfr
3750769Sdfr#include <ctype.h>
3847398Sdfr#include <stdio.h>
3955206Speter#include <stdlib.h>
4047613Sdfr#include <string.h>
4147578Sdfr#include <syslog.h>
4247578Sdfr
4347578Sdfr#include "tftp-utils.h"
4447578Sdfr#include "tftp-io.h"
4547578Sdfr#include "tftp-options.h"
4647578Sdfr
4747578Sdfr/*
4847578Sdfr * Option handlers
4947578Sdfr */
5047578Sdfr
5147578Sdfrstruct options options[] = {
5266840Smsmith	{ "tsize",	NULL, NULL, NULL /* option_tsize */, 1 },
5366840Smsmith	{ "timeout",	NULL, NULL, option_timeout, 1 },
5466840Smsmith	{ "blksize",	NULL, NULL, option_blksize, 1 },
5557368Sgj	{ "blksize2",	NULL, NULL, option_blksize2, 0 },
5666840Smsmith	{ "rollover",	NULL, NULL, option_rollover, 0 },
5766840Smsmith	{ NULL,		NULL, NULL, NULL, 0 }
5866840Smsmith};
5941181Sdfr
6066840Smsmith/* By default allow them */
6166840Smsmithint options_rfc_enabled = 1;
6266840Smsmithint options_extra_enabled = 1;
6366840Smsmith
6466840Smsmith/*
6566840Smsmith * Rules for the option handlers:
6666840Smsmith * - If there is no o_request, there will be no processing.
6766840Smsmith *
6857973Sphk * For servers
6957973Sphk * - Logging is done as warnings.
7057973Sphk * - The handler exit()s if there is a serious problem with the
7150769Sdfr *   values submitted in the option.
7250769Sdfr *
7350769Sdfr * For clients
7450769Sdfr * - Logging is done as errors. After all, the server shouldn't
7550769Sdfr *   return rubbish.
7650769Sdfr * - The handler returns if there is a serious problem with the
7750769Sdfr *   values submitted in the option.
7850769Sdfr * - Sending the EBADOP packets is done by the handler.
7950769Sdfr */
8050769Sdfr
8150769Sdfrint
8250769Sdfroption_tsize(int peer __unused, struct tftphdr *tp __unused, int mode,
8350769Sdfr    struct stat *stbuf)
8450769Sdfr{
8550769Sdfr
8650769Sdfr	if (options[OPT_TSIZE].o_request == NULL)
8750769Sdfr		return (0);
8850769Sdfr
8950769Sdfr	if (mode == RRQ)
9050769Sdfr		asprintf(&options[OPT_TSIZE].o_reply,
9150769Sdfr			"%ju", stbuf->st_size);
9250769Sdfr	else
9350769Sdfr		/* XXX Allows writes of all sizes. */
9450769Sdfr		options[OPT_TSIZE].o_reply =
9550769Sdfr			strdup(options[OPT_TSIZE].o_request);
9650769Sdfr	return (0);
9750769Sdfr}
9850769Sdfr
9950769Sdfrint
10050769Sdfroption_timeout(int peer)
10150769Sdfr{
10250769Sdfr
10350769Sdfr	if (options[OPT_TIMEOUT].o_request == NULL)
10438136Sdfr		return (0);
10541181Sdfr
10641181Sdfr	int to = atoi(options[OPT_TIMEOUT].o_request);
10741181Sdfr	if (to < TIMEOUT_MIN || to > TIMEOUT_MAX) {
10841181Sdfr		tftp_log(acting_as_client ? LOG_ERR : LOG_WARNING,
10941181Sdfr		    "Received bad value for timeout. "
11041181Sdfr		    "Should be between %d and %d, received %s",
11141181Sdfr		    TIMEOUT_MIN, TIMEOUT_MAX);
11241181Sdfr		send_error(peer, EBADOP);
11341181Sdfr		if (acting_as_client)
114105139Sjhb			return (1);
115105139Sjhb		exit(1);
116105139Sjhb	} else {
11741181Sdfr		timeoutpacket = to;
11841181Sdfr		options[OPT_TIMEOUT].o_reply =
11941181Sdfr			strdup(options[OPT_TIMEOUT].o_request);
12041181Sdfr	}
12141181Sdfr	settimeouts(timeoutpacket, timeoutnetwork, maxtimeouts);
12247613Sdfr
12347613Sdfr	if (debug&DEBUG_OPTIONS)
12447613Sdfr		tftp_log(LOG_DEBUG, "Setting timeout to '%s'",
12547613Sdfr			options[OPT_TIMEOUT].o_reply);
12682863Syokota
12782863Syokota	return (0);
12838136Sdfr}
12938136Sdfr
13038136Sdfrint
13183051Syokotaoption_rollover(int peer)
13283051Syokota{
13383051Syokota
13483051Syokota	if (options[OPT_ROLLOVER].o_request == NULL)
13583051Syokota		return (0);
13683051Syokota
13783051Syokota	if (strcmp(options[OPT_ROLLOVER].o_request, "0") != 0
13838136Sdfr	 && strcmp(options[OPT_ROLLOVER].o_request, "1") != 0) {
13938136Sdfr		tftp_log(acting_as_client ? LOG_ERR : LOG_WARNING,
140105139Sjhb		    "Bad value for rollover, "
141105139Sjhb		    "should be either 0 or 1, received '%s', "
14238136Sdfr		    "ignoring request",
14338136Sdfr		    options[OPT_ROLLOVER].o_request);
14438136Sdfr		if (acting_as_client) {
14538136Sdfr			send_error(peer, EBADOP);
14645720Speter			return (1);
14745720Speter		}
148105139Sjhb		return (0);
14947613Sdfr	}
15047613Sdfr	options[OPT_ROLLOVER].o_reply =
15147613Sdfr		strdup(options[OPT_ROLLOVER].o_request);
15247613Sdfr
15382863Syokota	if (debug&DEBUG_OPTIONS)
15447613Sdfr		tftp_log(LOG_DEBUG, "Setting rollover to '%s'",
155117337Sjhb			options[OPT_ROLLOVER].o_reply);
156117337Sjhb
157117337Sjhb	return (0);
15850769Sdfr}
15950769Sdfr
16047613Sdfrint
16192756Salfredoption_blksize(int peer)
16292756Salfred{
163135262Sphk	u_long maxdgram;
16492756Salfred	size_t len;
16592756Salfred
16692756Salfred	if (options[OPT_BLKSIZE].o_request == NULL)
16792756Salfred		return (0);
16892756Salfred
16950769Sdfr	/* maximum size of an UDP packet according to the system */
170135262Sphk	len = sizeof(maxdgram);
171135262Sphk	if (sysctlbyname("net.inet.udp.maxdgram",
172135262Sphk	    &maxdgram, &len, NULL, 0) < 0) {
173135262Sphk		tftp_log(LOG_ERR, "sysctl: net.inet.udp.maxdgram");
174135262Sphk		return (acting_as_client ? 1 : 0);
175135262Sphk	}
176117337Sjhb
177117337Sjhb	int size = atoi(options[OPT_BLKSIZE].o_request);
17867442Snyan	if (size < BLKSIZE_MIN || size > BLKSIZE_MAX) {
17967442Snyan		if (acting_as_client) {
18067442Snyan			tftp_log(LOG_ERR,
18167442Snyan			    "Invalid blocksize (%d bytes), aborting",
18267442Snyan			    size);
18367442Snyan			send_error(peer, EBADOP);
18467442Snyan			return (1);
18567442Snyan		} else {
18667442Snyan			tftp_log(LOG_WARNING,
18767442Snyan			    "Invalid blocksize (%d bytes), ignoring request",
18867442Snyan			    size);
18967442Snyan			return (0);
19067442Snyan		}
19155206Speter	}
19247613Sdfr
19347613Sdfr	if (size > (int)maxdgram) {
194		if (acting_as_client) {
195			tftp_log(LOG_ERR,
196			    "Invalid blocksize (%d bytes), "
197			    "net.inet.udp.maxdgram sysctl limits it to "
198			    "%d bytes.\n", size, maxdgram);
199			send_error(peer, EBADOP);
200			return (1);
201		} else {
202			tftp_log(LOG_WARNING,
203			    "Invalid blocksize (%d bytes), "
204			    "net.inet.udp.maxdgram sysctl limits it to "
205			    "%d bytes.\n", size, maxdgram);
206			size = maxdgram;
207			/* No reason to return */
208		}
209	}
210
211	asprintf(&options[OPT_BLKSIZE].o_reply, "%d", size);
212	segsize = size;
213	pktsize = size + 4;
214	if (debug&DEBUG_OPTIONS)
215		tftp_log(LOG_DEBUG, "Setting blksize to '%s'",
216		    options[OPT_BLKSIZE].o_reply);
217
218	return (0);
219}
220
221int
222option_blksize2(int peer __unused)
223{
224	u_long	maxdgram;
225	int	size, i;
226	size_t	len;
227
228	int sizes[] = {
229		8, 16, 32, 64, 128, 256, 512, 1024,
230		2048, 4096, 8192, 16384, 32768, 0
231	};
232
233	if (options[OPT_BLKSIZE2].o_request == NULL)
234		return (0);
235
236	/* maximum size of an UDP packet according to the system */
237	len = sizeof(maxdgram);
238	if (sysctlbyname("net.inet.udp.maxdgram",
239	    &maxdgram, &len, NULL, 0) < 0) {
240		tftp_log(LOG_ERR, "sysctl: net.inet.udp.maxdgram");
241		return (acting_as_client ? 1 : 0);
242	}
243
244	size = atoi(options[OPT_BLKSIZE2].o_request);
245	for (i = 0; sizes[i] != 0; i++) {
246		if (size == sizes[i]) break;
247	}
248	if (sizes[i] == 0) {
249		tftp_log(LOG_INFO,
250		    "Invalid blocksize2 (%d bytes), ignoring request", size);
251		return (acting_as_client ? 1 : 0);
252	}
253
254	if (size > (int)maxdgram) {
255		for (i = 0; sizes[i+1] != 0; i++) {
256			if ((int)maxdgram < sizes[i+1]) break;
257		}
258		tftp_log(LOG_INFO,
259		    "Invalid blocksize2 (%d bytes), net.inet.udp.maxdgram "
260		    "sysctl limits it to %d bytes.\n", size, maxdgram);
261		size = sizes[i];
262		/* No need to return */
263	}
264
265	asprintf(&options[OPT_BLKSIZE2].o_reply, "%d", size);
266	segsize = size;
267	pktsize = size + 4;
268	if (debug&DEBUG_OPTIONS)
269		tftp_log(LOG_DEBUG, "Setting blksize2 to '%s'",
270		    options[OPT_BLKSIZE2].o_reply);
271
272	return (0);
273}
274
275/*
276 * Append the available options to the header
277 */
278uint16_t
279make_options(int peer __unused, char *buffer, uint16_t size) {
280	int	i;
281	char	*value;
282	const char *option;
283	uint16_t length;
284	uint16_t returnsize = 0;
285
286	if (!options_rfc_enabled) return (0);
287
288	for (i = 0; options[i].o_type != NULL; i++) {
289		if (options[i].rfc == 0 && !options_extra_enabled)
290			continue;
291
292		option = options[i].o_type;
293		if (acting_as_client)
294			value = options[i].o_request;
295		else
296			value = options[i].o_reply;
297		if (value == NULL)
298			continue;
299
300		length = strlen(value) + strlen(option) + 2;
301		if (size <= length) {
302			tftp_log(LOG_ERR,
303			    "Running out of option space for "
304			    "option '%s' with value '%s': "
305			    "needed %d bytes, got %d bytes",
306			    option, value, size, length);
307			continue;
308		}
309
310		sprintf(buffer, "%s%c%s%c", option, '\000', value, '\000');
311		size -= length;
312		buffer += length;
313		returnsize += length;
314	}
315
316	return (returnsize);
317}
318
319/*
320 * Parse the received options in the header
321 */
322int
323parse_options(int peer, char *buffer, uint16_t size)
324{
325	int	i, options_failed;
326	char	*c, *cp, *option, *value;
327
328	if (!options_rfc_enabled) return (0);
329
330	/* Parse the options */
331	cp = buffer;
332	options_failed = 0;
333	while (size > 0) {
334		option = cp;
335		i = get_field(peer, cp, size);
336		cp += i;
337
338		value = cp;
339		i = get_field(peer, cp, size);
340		cp += i;
341
342		/* We are at the end */
343		if (*option == '\0') break;
344
345		if (debug&DEBUG_OPTIONS)
346			tftp_log(LOG_DEBUG,
347			    "option: '%s' value: '%s'", option, value);
348
349		for (c = option; *c; c++)
350			if (isupper(*c))
351				*c = tolower(*c);
352		for (i = 0; options[i].o_type != NULL; i++) {
353			if (strcmp(option, options[i].o_type) == 0) {
354				if (!acting_as_client)
355					options[i].o_request = value;
356				if (!options_extra_enabled && !options[i].rfc) {
357					tftp_log(LOG_INFO,
358					    "Option '%s' with value '%s' found "
359					    "but it is not an RFC option",
360					    option, value);
361					continue;
362				}
363				if (options[i].o_handler)
364					options_failed +=
365					    (options[i].o_handler)(peer);
366				break;
367			}
368		}
369		if (options[i].o_type == NULL)
370			tftp_log(LOG_WARNING,
371			    "Unknown option: '%s'", option);
372
373		size -= strlen(option) + strlen(value) + 2;
374	}
375
376	return (options_failed);
377}
378
379/*
380 * Set some default values in the options
381 */
382void
383init_options(void)
384{
385
386	options[OPT_ROLLOVER].o_request = strdup("0");
387}
388