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