1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (C) 2008 Edwin Groothuis. All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
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.
14 *
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
25 * SUCH DAMAGE.
26 */
27
28 #include <sys/cdefs.h>
29 #include <sys/types.h>
30 #include <sys/socket.h>
31 #include <sys/stat.h>
32 #include <sys/sysctl.h>
33
34 #include <netinet/in.h>
35 #include <arpa/tftp.h>
36
37 #include <ctype.h>
38 #include <stdarg.h>
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <syslog.h>
43
44 #include "tftp-utils.h"
45 #include "tftp-io.h"
46 #include "tftp-options.h"
47
48 /*
49 * Option handlers
50 */
51
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 }
60 };
61
62 /* By default allow them */
63 int options_rfc_enabled = 1;
64 int options_extra_enabled = 1;
65
66 int
options_set_request(enum opt_enum opt,const char * fmt,...)67 options_set_request(enum opt_enum opt, const char *fmt, ...)
68 {
69 va_list ap;
70 char *str;
71 int ret;
72
73 if (fmt == NULL) {
74 str = NULL;
75 } else {
76 va_start(ap, fmt);
77 ret = vasprintf(&str, fmt, ap);
78 va_end(ap);
79 if (ret < 0)
80 return (ret);
81 }
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;
86 return (0);
87 }
88
89 int
options_set_reply(enum opt_enum opt,const char * fmt,...)90 options_set_reply(enum opt_enum opt, const char *fmt, ...)
91 {
92 va_list ap;
93 char *str;
94 int ret;
95
96 if (fmt == NULL) {
97 str = NULL;
98 } else {
99 va_start(ap, fmt);
100 ret = vasprintf(&str, fmt, ap);
101 va_end(ap);
102 if (ret < 0)
103 return (ret);
104 }
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;
109 return (0);
110 }
111
112 static void
options_set_reply_equal_request(enum opt_enum opt)113 options_set_reply_equal_request(enum opt_enum opt)
114 {
115
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;
120 }
121
122 /*
123 * Rules for the option handlers:
124 * - If there is no o_request, there will be no processing.
125 *
126 * For servers
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.
130 *
131 * For clients
132 * - Logging is done as errors. After all, the server shouldn't
133 * return rubbish.
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.
137 */
138
139 int
option_tsize(int peer __unused,struct tftphdr * tp __unused,int mode,struct stat * stbuf)140 option_tsize(int peer __unused, struct tftphdr *tp __unused, int mode,
141 struct stat *stbuf)
142 {
143
144 if (options[OPT_TSIZE].o_request == NULL)
145 return (0);
146
147 if (mode == RRQ)
148 options_set_reply(OPT_TSIZE, "%ju", (uintmax_t)stbuf->st_size);
149 else
150 /* XXX Allows writes of all sizes. */
151 options_set_reply_equal_request(OPT_TSIZE);
152 return (0);
153 }
154
155 int
option_timeout(int peer)156 option_timeout(int peer)
157 {
158 int to;
159
160 if (options[OPT_TIMEOUT].o_request == NULL)
161 return (0);
162
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)
171 return (1);
172 exit(1);
173 } else {
174 timeoutpacket = to;
175 options_set_reply_equal_request(OPT_TIMEOUT);
176 }
177 settimeouts(timeoutpacket, timeoutnetwork, maxtimeouts);
178
179 if (debug & DEBUG_OPTIONS)
180 tftp_log(LOG_DEBUG, "Setting timeout to '%s'",
181 options[OPT_TIMEOUT].o_reply);
182
183 return (0);
184 }
185
186 int
option_rollover(int peer)187 option_rollover(int peer)
188 {
189
190 if (options[OPT_ROLLOVER].o_request == NULL)
191 return (0);
192
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', "
198 "ignoring request",
199 options[OPT_ROLLOVER].o_request);
200 if (acting_as_client) {
201 send_error(peer, EBADOP);
202 return (1);
203 }
204 return (0);
205 }
206 options_set_reply_equal_request(OPT_ROLLOVER);
207
208 if (debug & DEBUG_OPTIONS)
209 tftp_log(LOG_DEBUG, "Setting rollover to '%s'",
210 options[OPT_ROLLOVER].o_reply);
211
212 return (0);
213 }
214
215 int
option_blksize(int peer)216 option_blksize(int peer)
217 {
218 u_long maxdgram;
219 size_t len;
220
221 if (options[OPT_BLKSIZE].o_request == NULL)
222 return (0);
223
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);
230 }
231 maxdgram -= 4; /* leave room for header */
232
233 int size = atoi(options[OPT_BLKSIZE].o_request);
234 if (size < BLKSIZE_MIN || size > BLKSIZE_MAX) {
235 if (acting_as_client) {
236 tftp_log(LOG_ERR,
237 "Invalid blocksize (%d bytes), aborting",
238 size);
239 send_error(peer, EBADOP);
240 return (1);
241 } else {
242 tftp_log(LOG_WARNING,
243 "Invalid blocksize (%d bytes), ignoring request",
244 size);
245 return (0);
246 }
247 }
248
249 if (size > (int)maxdgram) {
250 if (acting_as_client) {
251 tftp_log(LOG_ERR,
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);
256 return (1);
257 } else {
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);
262 size = maxdgram;
263 /* No reason to return */
264 }
265 }
266
267 options_set_reply(OPT_BLKSIZE, "%d", size);
268 segsize = size;
269 pktsize = size + 4;
270 if (debug & DEBUG_OPTIONS)
271 tftp_log(LOG_DEBUG, "Setting blksize to '%s'",
272 options[OPT_BLKSIZE].o_reply);
273
274 return (0);
275 }
276
277 int
option_blksize2(int peer __unused)278 option_blksize2(int peer __unused)
279 {
280 u_long maxdgram;
281 int size, i;
282 size_t len;
283
284 int sizes[] = {
285 8, 16, 32, 64, 128, 256, 512, 1024,
286 2048, 4096, 8192, 16384, 32768, 0
287 };
288
289 if (options[OPT_BLKSIZE2].o_request == NULL)
290 return (0);
291
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);
298 }
299
300 size = atoi(options[OPT_BLKSIZE2].o_request);
301 for (i = 0; sizes[i] != 0; i++) {
302 if (size == sizes[i]) break;
303 }
304 if (sizes[i] == 0) {
305 tftp_log(LOG_INFO,
306 "Invalid blocksize2 (%d bytes), ignoring request", size);
307 return (acting_as_client ? 1 : 0);
308 }
309
310 if (size > (int)maxdgram) {
311 for (i = 0; sizes[i+1] != 0; i++) {
312 if ((int)maxdgram < sizes[i+1]) break;
313 }
314 tftp_log(LOG_INFO,
315 "Invalid blocksize2 (%d bytes), net.inet.udp.maxdgram "
316 "sysctl limits it to %ld bytes.\n", size, maxdgram);
317 size = sizes[i];
318 /* No need to return */
319 }
320
321 options_set_reply(OPT_BLKSIZE2, "%d", size);
322 segsize = size;
323 pktsize = size + 4;
324 if (debug & DEBUG_OPTIONS)
325 tftp_log(LOG_DEBUG, "Setting blksize2 to '%s'",
326 options[OPT_BLKSIZE2].o_reply);
327
328 return (0);
329 }
330
331 int
option_windowsize(int peer)332 option_windowsize(int peer)
333 {
334 int size;
335
336 if (options[OPT_WINDOWSIZE].o_request == NULL)
337 return (0);
338
339 size = atoi(options[OPT_WINDOWSIZE].o_request);
340 if (size < WINDOWSIZE_MIN || size > WINDOWSIZE_MAX) {
341 if (acting_as_client) {
342 tftp_log(LOG_ERR,
343 "Invalid windowsize (%d blocks), aborting",
344 size);
345 send_error(peer, EBADOP);
346 return (1);
347 } else {
348 tftp_log(LOG_WARNING,
349 "Invalid windowsize (%d blocks), ignoring request",
350 size);
351 return (0);
352 }
353 }
354
355 /* XXX: Should force a windowsize of 1 for non-seekable files. */
356 options_set_reply(OPT_WINDOWSIZE, "%d", size);
357 windowsize = size;
358
359 if (debug & DEBUG_OPTIONS)
360 tftp_log(LOG_DEBUG, "Setting windowsize to '%s'",
361 options[OPT_WINDOWSIZE].o_reply);
362
363 return (0);
364 }
365
366 /*
367 * Append the available options to the header
368 */
369 uint16_t
make_options(int peer __unused,char * buffer,uint16_t size)370 make_options(int peer __unused, char *buffer, uint16_t size) {
371 int i;
372 char *value;
373 const char *option;
374 uint16_t length;
375 uint16_t returnsize = 0;
376
377 if (!options_rfc_enabled) return (0);
378
379 for (i = 0; options[i].o_type != NULL; i++) {
380 if (options[i].rfc == 0 && !options_extra_enabled)
381 continue;
382
383 option = options[i].o_type;
384 if (acting_as_client)
385 value = options[i].o_request;
386 else
387 value = options[i].o_reply;
388 if (value == NULL)
389 continue;
390
391 length = strlen(value) + strlen(option) + 2;
392 if (size <= length) {
393 tftp_log(LOG_ERR,
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);
398 continue;
399 }
400
401 sprintf(buffer, "%s%c%s%c", option, '\000', value, '\000');
402 size -= length;
403 buffer += length;
404 returnsize += length;
405 }
406
407 return (returnsize);
408 }
409
410 /*
411 * Parse the received options in the header
412 */
413 int
parse_options(int peer,char * buffer,uint16_t size)414 parse_options(int peer, char *buffer, uint16_t size)
415 {
416 int i, options_failed;
417 char *c, *cp, *option, *value;
418
419 if (!options_rfc_enabled) return (0);
420
421 /* Parse the options */
422 cp = buffer;
423 options_failed = 0;
424 while (size > 0) {
425 option = cp;
426 i = get_field(peer, cp, size);
427 cp += i;
428
429 value = cp;
430 i = get_field(peer, cp, size);
431 cp += i;
432
433 /* We are at the end */
434 if (*option == '\0') break;
435
436 if (debug & DEBUG_OPTIONS)
437 tftp_log(LOG_DEBUG,
438 "option: '%s' value: '%s'", option, value);
439
440 for (c = option; *c; c++)
441 if (isupper(*c))
442 *c = tolower(*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) {
448 tftp_log(LOG_INFO,
449 "Option '%s' with value '%s' found "
450 "but it is not an RFC option",
451 option, value);
452 continue;
453 }
454 if (options[i].o_handler)
455 options_failed +=
456 (options[i].o_handler)(peer);
457 break;
458 }
459 }
460 if (options[i].o_type == NULL)
461 tftp_log(LOG_WARNING,
462 "Unknown option: '%s'", option);
463
464 size -= strlen(option) + strlen(value) + 2;
465 }
466
467 return (options_failed);
468 }
469
470 /*
471 * Set some default values in the options
472 */
473 void
init_options(void)474 init_options(void)
475 {
476
477 options_set_request(OPT_ROLLOVER, "0");
478 }
479