1 /*
2 * Copyright (C) 2008 Edwin Groothuis. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
14 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16 * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
17 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
19 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
20 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
21 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
22 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
23 * SUCH DAMAGE.
24 */
25
26 #include <sys/cdefs.h>
27 __FBSDID("$FreeBSD$");
28
29 #include <sys/socket.h>
30 #include <sys/types.h>
31 #include <sys/sysctl.h>
32 #include <sys/stat.h>
33
34 #include <netinet/in.h>
35 #include <arpa/tftp.h>
36
37 #include <ctype.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 { NULL, NULL, NULL, NULL, 0 }
58 };
59
60 /* By default allow them */
61 int options_rfc_enabled = 1;
62 int options_extra_enabled = 1;
63
64 /*
65 * Rules for the option handlers:
66 * - If there is no o_request, there will be no processing.
67 *
68 * For servers
69 * - Logging is done as warnings.
70 * - The handler exit()s if there is a serious problem with the
71 * values submitted in the option.
72 *
73 * For clients
74 * - Logging is done as errors. After all, the server shouldn't
75 * return rubbish.
76 * - The handler returns if there is a serious problem with the
77 * values submitted in the option.
78 * - Sending the EBADOP packets is done by the handler.
79 */
80
81 int
option_tsize(int peer __unused,struct tftphdr * tp __unused,int mode,struct stat * stbuf)82 option_tsize(int peer __unused, struct tftphdr *tp __unused, int mode,
83 struct stat *stbuf)
84 {
85
86 if (options[OPT_TSIZE].o_request == NULL)
87 return (0);
88
89 if (mode == RRQ)
90 asprintf(&options[OPT_TSIZE].o_reply,
91 "%ju", stbuf->st_size);
92 else
93 /* XXX Allows writes of all sizes. */
94 options[OPT_TSIZE].o_reply =
95 strdup(options[OPT_TSIZE].o_request);
96 return (0);
97 }
98
99 int
option_timeout(int peer)100 option_timeout(int peer)
101 {
102 int to;
103
104 if (options[OPT_TIMEOUT].o_request == NULL)
105 return (0);
106
107 to = atoi(options[OPT_TIMEOUT].o_request);
108 if (to < TIMEOUT_MIN || to > TIMEOUT_MAX) {
109 tftp_log(acting_as_client ? LOG_ERR : LOG_WARNING,
110 "Received bad value for timeout. "
111 "Should be between %d and %d, received %d",
112 TIMEOUT_MIN, TIMEOUT_MAX, to);
113 send_error(peer, EBADOP);
114 if (acting_as_client)
115 return (1);
116 exit(1);
117 } else {
118 timeoutpacket = to;
119 options[OPT_TIMEOUT].o_reply =
120 strdup(options[OPT_TIMEOUT].o_request);
121 }
122 settimeouts(timeoutpacket, timeoutnetwork, maxtimeouts);
123
124 if (debug&DEBUG_OPTIONS)
125 tftp_log(LOG_DEBUG, "Setting timeout to '%s'",
126 options[OPT_TIMEOUT].o_reply);
127
128 return (0);
129 }
130
131 int
option_rollover(int peer)132 option_rollover(int peer)
133 {
134
135 if (options[OPT_ROLLOVER].o_request == NULL)
136 return (0);
137
138 if (strcmp(options[OPT_ROLLOVER].o_request, "0") != 0
139 && strcmp(options[OPT_ROLLOVER].o_request, "1") != 0) {
140 tftp_log(acting_as_client ? LOG_ERR : LOG_WARNING,
141 "Bad value for rollover, "
142 "should be either 0 or 1, received '%s', "
143 "ignoring request",
144 options[OPT_ROLLOVER].o_request);
145 if (acting_as_client) {
146 send_error(peer, EBADOP);
147 return (1);
148 }
149 return (0);
150 }
151 options[OPT_ROLLOVER].o_reply =
152 strdup(options[OPT_ROLLOVER].o_request);
153
154 if (debug&DEBUG_OPTIONS)
155 tftp_log(LOG_DEBUG, "Setting rollover to '%s'",
156 options[OPT_ROLLOVER].o_reply);
157
158 return (0);
159 }
160
161 int
option_blksize(int peer)162 option_blksize(int peer)
163 {
164 u_long maxdgram;
165 size_t len;
166
167 if (options[OPT_BLKSIZE].o_request == NULL)
168 return (0);
169
170 /* maximum size of an UDP packet according to the system */
171 len = sizeof(maxdgram);
172 if (sysctlbyname("net.inet.udp.maxdgram",
173 &maxdgram, &len, NULL, 0) < 0) {
174 tftp_log(LOG_ERR, "sysctl: net.inet.udp.maxdgram");
175 return (acting_as_client ? 1 : 0);
176 }
177
178 int size = atoi(options[OPT_BLKSIZE].o_request);
179 if (size < BLKSIZE_MIN || size > BLKSIZE_MAX) {
180 if (acting_as_client) {
181 tftp_log(LOG_ERR,
182 "Invalid blocksize (%d bytes), aborting",
183 size);
184 send_error(peer, EBADOP);
185 return (1);
186 } else {
187 tftp_log(LOG_WARNING,
188 "Invalid blocksize (%d bytes), ignoring request",
189 size);
190 return (0);
191 }
192 }
193
194 if (size > (int)maxdgram) {
195 if (acting_as_client) {
196 tftp_log(LOG_ERR,
197 "Invalid blocksize (%d bytes), "
198 "net.inet.udp.maxdgram sysctl limits it to "
199 "%ld bytes.\n", size, maxdgram);
200 send_error(peer, EBADOP);
201 return (1);
202 } else {
203 tftp_log(LOG_WARNING,
204 "Invalid blocksize (%d bytes), "
205 "net.inet.udp.maxdgram sysctl limits it to "
206 "%ld bytes.\n", size, maxdgram);
207 size = maxdgram;
208 /* No reason to return */
209 }
210 }
211
212 asprintf(&options[OPT_BLKSIZE].o_reply, "%d", size);
213 segsize = size;
214 pktsize = size + 4;
215 if (debug&DEBUG_OPTIONS)
216 tftp_log(LOG_DEBUG, "Setting blksize to '%s'",
217 options[OPT_BLKSIZE].o_reply);
218
219 return (0);
220 }
221
222 int
option_blksize2(int peer __unused)223 option_blksize2(int peer __unused)
224 {
225 u_long maxdgram;
226 int size, i;
227 size_t len;
228
229 int sizes[] = {
230 8, 16, 32, 64, 128, 256, 512, 1024,
231 2048, 4096, 8192, 16384, 32768, 0
232 };
233
234 if (options[OPT_BLKSIZE2].o_request == NULL)
235 return (0);
236
237 /* maximum size of an UDP packet according to the system */
238 len = sizeof(maxdgram);
239 if (sysctlbyname("net.inet.udp.maxdgram",
240 &maxdgram, &len, NULL, 0) < 0) {
241 tftp_log(LOG_ERR, "sysctl: net.inet.udp.maxdgram");
242 return (acting_as_client ? 1 : 0);
243 }
244
245 size = atoi(options[OPT_BLKSIZE2].o_request);
246 for (i = 0; sizes[i] != 0; i++) {
247 if (size == sizes[i]) break;
248 }
249 if (sizes[i] == 0) {
250 tftp_log(LOG_INFO,
251 "Invalid blocksize2 (%d bytes), ignoring request", size);
252 return (acting_as_client ? 1 : 0);
253 }
254
255 if (size > (int)maxdgram) {
256 for (i = 0; sizes[i+1] != 0; i++) {
257 if ((int)maxdgram < sizes[i+1]) break;
258 }
259 tftp_log(LOG_INFO,
260 "Invalid blocksize2 (%d bytes), net.inet.udp.maxdgram "
261 "sysctl limits it to %ld bytes.\n", size, maxdgram);
262 size = sizes[i];
263 /* No need to return */
264 }
265
266 asprintf(&options[OPT_BLKSIZE2].o_reply, "%d", size);
267 segsize = size;
268 pktsize = size + 4;
269 if (debug&DEBUG_OPTIONS)
270 tftp_log(LOG_DEBUG, "Setting blksize2 to '%s'",
271 options[OPT_BLKSIZE2].o_reply);
272
273 return (0);
274 }
275
276 /*
277 * Append the available options to the header
278 */
279 uint16_t
make_options(int peer __unused,char * buffer,uint16_t size)280 make_options(int peer __unused, char *buffer, uint16_t size) {
281 int i;
282 char *value;
283 const char *option;
284 uint16_t length;
285 uint16_t returnsize = 0;
286
287 if (!options_rfc_enabled) return (0);
288
289 for (i = 0; options[i].o_type != NULL; i++) {
290 if (options[i].rfc == 0 && !options_extra_enabled)
291 continue;
292
293 option = options[i].o_type;
294 if (acting_as_client)
295 value = options[i].o_request;
296 else
297 value = options[i].o_reply;
298 if (value == NULL)
299 continue;
300
301 length = strlen(value) + strlen(option) + 2;
302 if (size <= length) {
303 tftp_log(LOG_ERR,
304 "Running out of option space for "
305 "option '%s' with value '%s': "
306 "needed %d bytes, got %d bytes",
307 option, value, size, length);
308 continue;
309 }
310
311 sprintf(buffer, "%s%c%s%c", option, '\000', value, '\000');
312 size -= length;
313 buffer += length;
314 returnsize += length;
315 }
316
317 return (returnsize);
318 }
319
320 /*
321 * Parse the received options in the header
322 */
323 int
parse_options(int peer,char * buffer,uint16_t size)324 parse_options(int peer, char *buffer, uint16_t size)
325 {
326 int i, options_failed;
327 char *c, *cp, *option, *value;
328
329 if (!options_rfc_enabled) return (0);
330
331 /* Parse the options */
332 cp = buffer;
333 options_failed = 0;
334 while (size > 0) {
335 option = cp;
336 i = get_field(peer, cp, size);
337 cp += i;
338
339 value = cp;
340 i = get_field(peer, cp, size);
341 cp += i;
342
343 /* We are at the end */
344 if (*option == '\0') break;
345
346 if (debug&DEBUG_OPTIONS)
347 tftp_log(LOG_DEBUG,
348 "option: '%s' value: '%s'", option, value);
349
350 for (c = option; *c; c++)
351 if (isupper(*c))
352 *c = tolower(*c);
353 for (i = 0; options[i].o_type != NULL; i++) {
354 if (strcmp(option, options[i].o_type) == 0) {
355 if (!acting_as_client)
356 options[i].o_request = value;
357 if (!options_extra_enabled && !options[i].rfc) {
358 tftp_log(LOG_INFO,
359 "Option '%s' with value '%s' found "
360 "but it is not an RFC option",
361 option, value);
362 continue;
363 }
364 if (options[i].o_handler)
365 options_failed +=
366 (options[i].o_handler)(peer);
367 break;
368 }
369 }
370 if (options[i].o_type == NULL)
371 tftp_log(LOG_WARNING,
372 "Unknown option: '%s'", option);
373
374 size -= strlen(option) + strlen(value) + 2;
375 }
376
377 return (options_failed);
378 }
379
380 /*
381 * Set some default values in the options
382 */
383 void
init_options(void)384 init_options(void)
385 {
386
387 options[OPT_ROLLOVER].o_request = strdup("0");
388 }
389