2 * SPDX-License-Identifier: BSD-2-Clause
4 * Copyright (C) 2008 Edwin Groothuis. All rights reserved.
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
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.
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
28 #include <sys/cdefs.h>
29 #include <sys/types.h>
30 #include <sys/socket.h>
32 #include <sys/sysctl.h>
34 #include <netinet/in.h>
35 #include <arpa/tftp.h>
44 #include "tftp-utils.h"
46 #include "tftp-options.h"
52 struct options options[] = {
53 { "tsize", NULL, NULL, NULL /* option_tsize */, 1 },
54 { "timeout", NULL, NULL, option_timeout, 1 },
55 { "blksize", NULL, NULL, option_blksize, 1 },
56 { "blksize2", NULL, NULL, option_blksize2, 0 },
57 { "rollover", NULL, NULL, option_rollover, 0 },
58 { "windowsize", NULL, NULL, option_windowsize, 1 },
59 { NULL, NULL, NULL, NULL, 0 }
62 /* By default allow them */
63 int options_rfc_enabled = 1;
64 int options_extra_enabled = 1;
67 options_set_request(enum opt_enum opt, const char *fmt, ...)
77 ret = vasprintf(&str, fmt, ap);
82 if (options[opt].o_request != NULL &&
83 options[opt].o_request != options[opt].o_reply)
84 free(options[opt].o_request);
85 options[opt].o_request = str;
90 options_set_reply(enum opt_enum opt, const char *fmt, ...)
100 ret = vasprintf(&str, fmt, ap);
105 if (options[opt].o_reply != NULL &&
106 options[opt].o_reply != options[opt].o_request)
107 free(options[opt].o_reply);
108 options[opt].o_reply = str;
113 options_set_reply_equal_request(enum opt_enum opt)
116 if (options[opt].o_reply != NULL &&
117 options[opt].o_reply != options[opt].o_request)
118 free(options[opt].o_reply);
119 options[opt].o_reply = options[opt].o_request;
123 * Rules for the option handlers:
124 * - If there is no o_request, there will be no processing.
127 * - Logging is done as warnings.
128 * - The handler exit()s if there is a serious problem with the
129 * values submitted in the option.
132 * - Logging is done as errors. After all, the server shouldn't
134 * - The handler returns if there is a serious problem with the
135 * values submitted in the option.
136 * - Sending the EBADOP packets is done by the handler.
140 option_tsize(int peer __unused, struct tftphdr *tp __unused, int mode,
144 if (options[OPT_TSIZE].o_request == NULL)
148 options_set_reply(OPT_TSIZE, "%ju", (uintmax_t)stbuf->st_size);
150 /* XXX Allows writes of all sizes. */
151 options_set_reply_equal_request(OPT_TSIZE);
156 option_timeout(int peer)
160 if (options[OPT_TIMEOUT].o_request == NULL)
163 to = atoi(options[OPT_TIMEOUT].o_request);
164 if (to < TIMEOUT_MIN || to > TIMEOUT_MAX) {
165 tftp_log(acting_as_client ? LOG_ERR : LOG_WARNING,
166 "Received bad value for timeout. "
167 "Should be between %d and %d, received %d",
168 TIMEOUT_MIN, TIMEOUT_MAX, to);
169 send_error(peer, EBADOP);
170 if (acting_as_client)
175 options_set_reply_equal_request(OPT_TIMEOUT);
177 settimeouts(timeoutpacket, timeoutnetwork, maxtimeouts);
179 if (debug & DEBUG_OPTIONS)
180 tftp_log(LOG_DEBUG, "Setting timeout to '%s'",
181 options[OPT_TIMEOUT].o_reply);
187 option_rollover(int peer)
190 if (options[OPT_ROLLOVER].o_request == NULL)
193 if (strcmp(options[OPT_ROLLOVER].o_request, "0") != 0
194 && strcmp(options[OPT_ROLLOVER].o_request, "1") != 0) {
195 tftp_log(acting_as_client ? LOG_ERR : LOG_WARNING,
196 "Bad value for rollover, "
197 "should be either 0 or 1, received '%s', "
199 options[OPT_ROLLOVER].o_request);
200 if (acting_as_client) {
201 send_error(peer, EBADOP);
206 options_set_reply_equal_request(OPT_ROLLOVER);
208 if (debug & DEBUG_OPTIONS)
209 tftp_log(LOG_DEBUG, "Setting rollover to '%s'",
210 options[OPT_ROLLOVER].o_reply);
216 option_blksize(int peer)
221 if (options[OPT_BLKSIZE].o_request == NULL)
224 /* maximum size of an UDP packet according to the system */
225 len = sizeof(maxdgram);
226 if (sysctlbyname("net.inet.udp.maxdgram",
227 &maxdgram, &len, NULL, 0) < 0) {
228 tftp_log(LOG_ERR, "sysctl: net.inet.udp.maxdgram");
229 return (acting_as_client ? 1 : 0);
231 maxdgram -= 4; /* leave room for header */
233 int size = atoi(options[OPT_BLKSIZE].o_request);
234 if (size < BLKSIZE_MIN || size > BLKSIZE_MAX) {
235 if (acting_as_client) {
237 "Invalid blocksize (%d bytes), aborting",
239 send_error(peer, EBADOP);
242 tftp_log(LOG_WARNING,
243 "Invalid blocksize (%d bytes), ignoring request",
249 if (size > (int)maxdgram) {
250 if (acting_as_client) {
252 "Invalid blocksize (%d bytes), "
253 "net.inet.udp.maxdgram sysctl limits it to "
254 "%ld bytes.\n", size, maxdgram);
255 send_error(peer, EBADOP);
258 tftp_log(LOG_WARNING,
259 "Invalid blocksize (%d bytes), "
260 "net.inet.udp.maxdgram sysctl limits it to "
261 "%ld bytes.\n", size, maxdgram);
263 /* No reason to return */
267 options_set_reply(OPT_BLKSIZE, "%d", size);
270 if (debug & DEBUG_OPTIONS)
271 tftp_log(LOG_DEBUG, "Setting blksize to '%s'",
272 options[OPT_BLKSIZE].o_reply);
278 option_blksize2(int peer __unused)
285 8, 16, 32, 64, 128, 256, 512, 1024,
286 2048, 4096, 8192, 16384, 32768, 0
289 if (options[OPT_BLKSIZE2].o_request == NULL)
292 /* maximum size of an UDP packet according to the system */
293 len = sizeof(maxdgram);
294 if (sysctlbyname("net.inet.udp.maxdgram",
295 &maxdgram, &len, NULL, 0) < 0) {
296 tftp_log(LOG_ERR, "sysctl: net.inet.udp.maxdgram");
297 return (acting_as_client ? 1 : 0);
300 size = atoi(options[OPT_BLKSIZE2].o_request);
301 for (i = 0; sizes[i] != 0; i++) {
302 if (size == sizes[i]) break;
306 "Invalid blocksize2 (%d bytes), ignoring request", size);
307 return (acting_as_client ? 1 : 0);
310 if (size > (int)maxdgram) {
311 for (i = 0; sizes[i+1] != 0; i++) {
312 if ((int)maxdgram < sizes[i+1]) break;
315 "Invalid blocksize2 (%d bytes), net.inet.udp.maxdgram "
316 "sysctl limits it to %ld bytes.\n", size, maxdgram);
318 /* No need to return */
321 options_set_reply(OPT_BLKSIZE2, "%d", size);
324 if (debug & DEBUG_OPTIONS)
325 tftp_log(LOG_DEBUG, "Setting blksize2 to '%s'",
326 options[OPT_BLKSIZE2].o_reply);
332 option_windowsize(int peer)
336 if (options[OPT_WINDOWSIZE].o_request == NULL)
339 size = atoi(options[OPT_WINDOWSIZE].o_request);
340 if (size < WINDOWSIZE_MIN || size > WINDOWSIZE_MAX) {
341 if (acting_as_client) {
343 "Invalid windowsize (%d blocks), aborting",
345 send_error(peer, EBADOP);
348 tftp_log(LOG_WARNING,
349 "Invalid windowsize (%d blocks), ignoring request",
355 /* XXX: Should force a windowsize of 1 for non-seekable files. */
356 options_set_reply(OPT_WINDOWSIZE, "%d", size);
359 if (debug & DEBUG_OPTIONS)
360 tftp_log(LOG_DEBUG, "Setting windowsize to '%s'",
361 options[OPT_WINDOWSIZE].o_reply);
367 * Append the available options to the header
370 make_options(int peer __unused, char *buffer, uint16_t size) {
375 uint16_t returnsize = 0;
377 if (!options_rfc_enabled) return (0);
379 for (i = 0; options[i].o_type != NULL; i++) {
380 if (options[i].rfc == 0 && !options_extra_enabled)
383 option = options[i].o_type;
384 if (acting_as_client)
385 value = options[i].o_request;
387 value = options[i].o_reply;
391 length = strlen(value) + strlen(option) + 2;
392 if (size <= length) {
394 "Running out of option space for "
395 "option '%s' with value '%s': "
396 "needed %d bytes, got %d bytes",
397 option, value, size, length);
401 sprintf(buffer, "%s%c%s%c", option, '\000', value, '\000');
404 returnsize += length;
411 * Parse the received options in the header
414 parse_options(int peer, char *buffer, uint16_t size)
416 int i, options_failed;
417 char *c, *cp, *option, *value;
419 if (!options_rfc_enabled) return (0);
421 /* Parse the options */
426 i = get_field(peer, cp, size);
430 i = get_field(peer, cp, size);
433 /* We are at the end */
434 if (*option == '\0') break;
436 if (debug & DEBUG_OPTIONS)
438 "option: '%s' value: '%s'", option, value);
440 for (c = option; *c; c++)
443 for (i = 0; options[i].o_type != NULL; i++) {
444 if (strcmp(option, options[i].o_type) == 0) {
445 if (!acting_as_client)
446 options_set_request(i, "%s", value);
447 if (!options_extra_enabled && !options[i].rfc) {
449 "Option '%s' with value '%s' found "
450 "but it is not an RFC option",
454 if (options[i].o_handler)
456 (options[i].o_handler)(peer);
460 if (options[i].o_type == NULL)
461 tftp_log(LOG_WARNING,
462 "Unknown option: '%s'", option);
464 size -= strlen(option) + strlen(value) + 2;
467 return (options_failed);
471 * Set some default values in the options
477 options_set_request(OPT_ROLLOVER, "0");