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