1207614Simp/*
2207614Simp * Copyright (C) 2008 Edwin Groothuis. All rights reserved.
3207614Simp *
4207614Simp * Redistribution and use in source and binary forms, with or without
5207614Simp * modification, are permitted provided that the following conditions
6207614Simp * are met:
7207614Simp * 1. Redistributions of source code must retain the above copyright
8207614Simp *    notice, this list of conditions and the following disclaimer.
9207614Simp * 2. Redistributions in binary form must reproduce the above copyright
10207614Simp *    notice, this list of conditions and the following disclaimer in the
11207614Simp *    documentation and/or other materials provided with the distribution.
12207614Simp *
13207614Simp * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
14207614Simp * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15207614Simp * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16207614Simp * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
17207614Simp * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18207614Simp * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
19207614Simp * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
20207614Simp * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
21207614Simp * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
22207614Simp * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
23207614Simp * SUCH DAMAGE.
24207614Simp */
25207614Simp
26207614Simp#include <sys/cdefs.h>
27207614Simp__FBSDID("$FreeBSD$");
28207614Simp
29207614Simp#include <sys/socket.h>
30207614Simp#include <sys/types.h>
31207614Simp#include <sys/sysctl.h>
32207614Simp#include <sys/stat.h>
33207614Simp
34207614Simp#include <netinet/in.h>
35207614Simp#include <arpa/tftp.h>
36207614Simp
37207614Simp#include <ctype.h>
38207614Simp#include <stdio.h>
39207614Simp#include <stdlib.h>
40207614Simp#include <string.h>
41207614Simp#include <syslog.h>
42207614Simp
43207614Simp#include "tftp-utils.h"
44207614Simp#include "tftp-io.h"
45207614Simp#include "tftp-options.h"
46207614Simp
47207614Simp/*
48207614Simp * Option handlers
49207614Simp */
50207614Simp
51207614Simpstruct options options[] = {
52207614Simp	{ "tsize",	NULL, NULL, NULL /* option_tsize */, 1 },
53207614Simp	{ "timeout",	NULL, NULL, option_timeout, 1 },
54207614Simp	{ "blksize",	NULL, NULL, option_blksize, 1 },
55207614Simp	{ "blksize2",	NULL, NULL, option_blksize2, 0 },
56207614Simp	{ "rollover",	NULL, NULL, option_rollover, 0 },
57207614Simp	{ NULL,		NULL, NULL, NULL, 0 }
58207614Simp};
59207614Simp
60207614Simp/* By default allow them */
61207614Simpint options_rfc_enabled = 1;
62207614Simpint options_extra_enabled = 1;
63207614Simp
64207614Simp/*
65207614Simp * Rules for the option handlers:
66207614Simp * - If there is no o_request, there will be no processing.
67207614Simp *
68207614Simp * For servers
69207614Simp * - Logging is done as warnings.
70207614Simp * - The handler exit()s if there is a serious problem with the
71207614Simp *   values submitted in the option.
72207614Simp *
73207614Simp * For clients
74207614Simp * - Logging is done as errors. After all, the server shouldn't
75207614Simp *   return rubbish.
76207614Simp * - The handler returns if there is a serious problem with the
77207614Simp *   values submitted in the option.
78207614Simp * - Sending the EBADOP packets is done by the handler.
79207614Simp */
80207614Simp
81207614Simpint
82213099Smariusoption_tsize(int peer __unused, struct tftphdr *tp __unused, int mode,
83213099Smarius    struct stat *stbuf)
84207614Simp{
85207614Simp
86207614Simp	if (options[OPT_TSIZE].o_request == NULL)
87207614Simp		return (0);
88207614Simp
89207614Simp	if (mode == RRQ)
90207614Simp		asprintf(&options[OPT_TSIZE].o_reply,
91207614Simp			"%ju", stbuf->st_size);
92207614Simp	else
93207614Simp		/* XXX Allows writes of all sizes. */
94207614Simp		options[OPT_TSIZE].o_reply =
95207614Simp			strdup(options[OPT_TSIZE].o_request);
96207614Simp	return (0);
97207614Simp}
98207614Simp
99207614Simpint
100207614Simpoption_timeout(int peer)
101207614Simp{
102247644Smarius	int to;
103207614Simp
104207614Simp	if (options[OPT_TIMEOUT].o_request == NULL)
105207614Simp		return (0);
106207614Simp
107247644Smarius	to = atoi(options[OPT_TIMEOUT].o_request);
108207614Simp	if (to < TIMEOUT_MIN || to > TIMEOUT_MAX) {
109207614Simp		tftp_log(acting_as_client ? LOG_ERR : LOG_WARNING,
110207614Simp		    "Received bad value for timeout. "
111247644Smarius		    "Should be between %d and %d, received %d",
112247644Smarius		    TIMEOUT_MIN, TIMEOUT_MAX, to);
113207614Simp		send_error(peer, EBADOP);
114207614Simp		if (acting_as_client)
115207614Simp			return (1);
116207614Simp		exit(1);
117207614Simp	} else {
118207614Simp		timeoutpacket = to;
119207614Simp		options[OPT_TIMEOUT].o_reply =
120207614Simp			strdup(options[OPT_TIMEOUT].o_request);
121207614Simp	}
122207614Simp	settimeouts(timeoutpacket, timeoutnetwork, maxtimeouts);
123207614Simp
124207614Simp	if (debug&DEBUG_OPTIONS)
125207614Simp		tftp_log(LOG_DEBUG, "Setting timeout to '%s'",
126207614Simp			options[OPT_TIMEOUT].o_reply);
127207614Simp
128207614Simp	return (0);
129207614Simp}
130207614Simp
131207614Simpint
132207614Simpoption_rollover(int peer)
133207614Simp{
134207614Simp
135207614Simp	if (options[OPT_ROLLOVER].o_request == NULL)
136207614Simp		return (0);
137207614Simp
138207614Simp	if (strcmp(options[OPT_ROLLOVER].o_request, "0") != 0
139207614Simp	 && strcmp(options[OPT_ROLLOVER].o_request, "1") != 0) {
140207614Simp		tftp_log(acting_as_client ? LOG_ERR : LOG_WARNING,
141207614Simp		    "Bad value for rollover, "
142207614Simp		    "should be either 0 or 1, received '%s', "
143207614Simp		    "ignoring request",
144207614Simp		    options[OPT_ROLLOVER].o_request);
145207614Simp		if (acting_as_client) {
146207614Simp			send_error(peer, EBADOP);
147207614Simp			return (1);
148207614Simp		}
149207614Simp		return (0);
150207614Simp	}
151207614Simp	options[OPT_ROLLOVER].o_reply =
152207614Simp		strdup(options[OPT_ROLLOVER].o_request);
153207614Simp
154207614Simp	if (debug&DEBUG_OPTIONS)
155207614Simp		tftp_log(LOG_DEBUG, "Setting rollover to '%s'",
156207614Simp			options[OPT_ROLLOVER].o_reply);
157207614Simp
158207614Simp	return (0);
159207614Simp}
160207614Simp
161207614Simpint
162207614Simpoption_blksize(int peer)
163207614Simp{
164213099Smarius	u_long maxdgram;
165207614Simp	size_t len;
166207614Simp
167207614Simp	if (options[OPT_BLKSIZE].o_request == NULL)
168207614Simp		return (0);
169207614Simp
170207614Simp	/* maximum size of an UDP packet according to the system */
171213099Smarius	len = sizeof(maxdgram);
172207614Simp	if (sysctlbyname("net.inet.udp.maxdgram",
173213099Smarius	    &maxdgram, &len, NULL, 0) < 0) {
174207614Simp		tftp_log(LOG_ERR, "sysctl: net.inet.udp.maxdgram");
175207614Simp		return (acting_as_client ? 1 : 0);
176207614Simp	}
177207614Simp
178207614Simp	int size = atoi(options[OPT_BLKSIZE].o_request);
179207614Simp	if (size < BLKSIZE_MIN || size > BLKSIZE_MAX) {
180207614Simp		if (acting_as_client) {
181207614Simp			tftp_log(LOG_ERR,
182207614Simp			    "Invalid blocksize (%d bytes), aborting",
183207614Simp			    size);
184207614Simp			send_error(peer, EBADOP);
185207614Simp			return (1);
186207614Simp		} else {
187207614Simp			tftp_log(LOG_WARNING,
188207614Simp			    "Invalid blocksize (%d bytes), ignoring request",
189207614Simp			    size);
190207614Simp			return (0);
191207614Simp		}
192207614Simp	}
193207614Simp
194213099Smarius	if (size > (int)maxdgram) {
195207614Simp		if (acting_as_client) {
196207614Simp			tftp_log(LOG_ERR,
197207614Simp			    "Invalid blocksize (%d bytes), "
198207614Simp			    "net.inet.udp.maxdgram sysctl limits it to "
199247644Smarius			    "%ld bytes.\n", size, maxdgram);
200207614Simp			send_error(peer, EBADOP);
201207614Simp			return (1);
202207614Simp		} else {
203207614Simp			tftp_log(LOG_WARNING,
204207614Simp			    "Invalid blocksize (%d bytes), "
205207614Simp			    "net.inet.udp.maxdgram sysctl limits it to "
206247644Smarius			    "%ld bytes.\n", size, maxdgram);
207213099Smarius			size = maxdgram;
208207614Simp			/* No reason to return */
209207614Simp		}
210207614Simp	}
211207614Simp
212207614Simp	asprintf(&options[OPT_BLKSIZE].o_reply, "%d", size);
213207614Simp	segsize = size;
214207614Simp	pktsize = size + 4;
215207614Simp	if (debug&DEBUG_OPTIONS)
216207614Simp		tftp_log(LOG_DEBUG, "Setting blksize to '%s'",
217207614Simp		    options[OPT_BLKSIZE].o_reply);
218207614Simp
219207614Simp	return (0);
220207614Simp}
221207614Simp
222207614Simpint
223213099Smariusoption_blksize2(int peer __unused)
224207614Simp{
225213099Smarius	u_long	maxdgram;
226207614Simp	int	size, i;
227207614Simp	size_t	len;
228207614Simp
229207614Simp	int sizes[] = {
230207614Simp		8, 16, 32, 64, 128, 256, 512, 1024,
231207614Simp		2048, 4096, 8192, 16384, 32768, 0
232207614Simp	};
233207614Simp
234207614Simp	if (options[OPT_BLKSIZE2].o_request == NULL)
235207614Simp		return (0);
236207614Simp
237207614Simp	/* maximum size of an UDP packet according to the system */
238213099Smarius	len = sizeof(maxdgram);
239207614Simp	if (sysctlbyname("net.inet.udp.maxdgram",
240213099Smarius	    &maxdgram, &len, NULL, 0) < 0) {
241207614Simp		tftp_log(LOG_ERR, "sysctl: net.inet.udp.maxdgram");
242207614Simp		return (acting_as_client ? 1 : 0);
243207614Simp	}
244207614Simp
245207614Simp	size = atoi(options[OPT_BLKSIZE2].o_request);
246207614Simp	for (i = 0; sizes[i] != 0; i++) {
247207614Simp		if (size == sizes[i]) break;
248207614Simp	}
249207614Simp	if (sizes[i] == 0) {
250207614Simp		tftp_log(LOG_INFO,
251207614Simp		    "Invalid blocksize2 (%d bytes), ignoring request", size);
252207614Simp		return (acting_as_client ? 1 : 0);
253207614Simp	}
254207614Simp
255213099Smarius	if (size > (int)maxdgram) {
256207614Simp		for (i = 0; sizes[i+1] != 0; i++) {
257213099Smarius			if ((int)maxdgram < sizes[i+1]) break;
258207614Simp		}
259207614Simp		tftp_log(LOG_INFO,
260207614Simp		    "Invalid blocksize2 (%d bytes), net.inet.udp.maxdgram "
261247644Smarius		    "sysctl limits it to %ld bytes.\n", size, maxdgram);
262207614Simp		size = sizes[i];
263207614Simp		/* No need to return */
264207614Simp	}
265207614Simp
266207614Simp	asprintf(&options[OPT_BLKSIZE2].o_reply, "%d", size);
267207614Simp	segsize = size;
268207614Simp	pktsize = size + 4;
269207614Simp	if (debug&DEBUG_OPTIONS)
270207614Simp		tftp_log(LOG_DEBUG, "Setting blksize2 to '%s'",
271207614Simp		    options[OPT_BLKSIZE2].o_reply);
272207614Simp
273207614Simp	return (0);
274207614Simp}
275207614Simp
276207614Simp/*
277207614Simp * Append the available options to the header
278207614Simp */
279207614Simpuint16_t
280213099Smariusmake_options(int peer __unused, char *buffer, uint16_t size) {
281207614Simp	int	i;
282207614Simp	char	*value;
283207614Simp	const char *option;
284207614Simp	uint16_t length;
285207614Simp	uint16_t returnsize = 0;
286207614Simp
287207614Simp	if (!options_rfc_enabled) return (0);
288207614Simp
289207614Simp	for (i = 0; options[i].o_type != NULL; i++) {
290207614Simp		if (options[i].rfc == 0 && !options_extra_enabled)
291207614Simp			continue;
292207614Simp
293207614Simp		option = options[i].o_type;
294207614Simp		if (acting_as_client)
295207614Simp			value = options[i].o_request;
296207614Simp		else
297207614Simp			value = options[i].o_reply;
298207614Simp		if (value == NULL)
299207614Simp			continue;
300207614Simp
301207614Simp		length = strlen(value) + strlen(option) + 2;
302207614Simp		if (size <= length) {
303207614Simp			tftp_log(LOG_ERR,
304207614Simp			    "Running out of option space for "
305207614Simp			    "option '%s' with value '%s': "
306207614Simp			    "needed %d bytes, got %d bytes",
307207614Simp			    option, value, size, length);
308207614Simp			continue;
309207614Simp		}
310207614Simp
311207614Simp		sprintf(buffer, "%s%c%s%c", option, '\000', value, '\000');
312207614Simp		size -= length;
313207614Simp		buffer += length;
314207614Simp		returnsize += length;
315207614Simp	}
316207614Simp
317207614Simp	return (returnsize);
318207614Simp}
319207614Simp
320207614Simp/*
321207614Simp * Parse the received options in the header
322207614Simp */
323207614Simpint
324207614Simpparse_options(int peer, char *buffer, uint16_t size)
325207614Simp{
326207614Simp	int	i, options_failed;
327207614Simp	char	*c, *cp, *option, *value;
328207614Simp
329207614Simp	if (!options_rfc_enabled) return (0);
330207614Simp
331207614Simp	/* Parse the options */
332207614Simp	cp = buffer;
333207614Simp	options_failed = 0;
334207614Simp	while (size > 0) {
335207614Simp		option = cp;
336207614Simp		i = get_field(peer, cp, size);
337207614Simp		cp += i;
338207614Simp
339207614Simp		value = cp;
340207614Simp		i = get_field(peer, cp, size);
341207614Simp		cp += i;
342207614Simp
343207614Simp		/* We are at the end */
344207614Simp		if (*option == '\0') break;
345207614Simp
346207614Simp		if (debug&DEBUG_OPTIONS)
347207614Simp			tftp_log(LOG_DEBUG,
348207614Simp			    "option: '%s' value: '%s'", option, value);
349207614Simp
350207614Simp		for (c = option; *c; c++)
351207614Simp			if (isupper(*c))
352207614Simp				*c = tolower(*c);
353207614Simp		for (i = 0; options[i].o_type != NULL; i++) {
354207614Simp			if (strcmp(option, options[i].o_type) == 0) {
355207614Simp				if (!acting_as_client)
356207614Simp					options[i].o_request = value;
357207614Simp				if (!options_extra_enabled && !options[i].rfc) {
358207614Simp					tftp_log(LOG_INFO,
359207614Simp					    "Option '%s' with value '%s' found "
360207614Simp					    "but it is not an RFC option",
361207614Simp					    option, value);
362207614Simp					continue;
363207614Simp				}
364207614Simp				if (options[i].o_handler)
365207614Simp					options_failed +=
366207614Simp					    (options[i].o_handler)(peer);
367207614Simp				break;
368207614Simp			}
369207614Simp		}
370207614Simp		if (options[i].o_type == NULL)
371207614Simp			tftp_log(LOG_WARNING,
372207614Simp			    "Unknown option: '%s'", option);
373207614Simp
374207614Simp		size -= strlen(option) + strlen(value) + 2;
375207614Simp	}
376207614Simp
377207614Simp	return (options_failed);
378207614Simp}
379207614Simp
380207614Simp/*
381207614Simp * Set some default values in the options
382207614Simp */
383207614Simpvoid
384207614Simpinit_options(void)
385207614Simp{
386207614Simp
387207614Simp	options[OPT_ROLLOVER].o_request = strdup("0");
388207614Simp}
389