]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - libexec/tftpd/tftp-options.c
libarchive: merge security fix from vendor branch
[FreeBSD/FreeBSD.git] / libexec / tftpd / tftp-options.c
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
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
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
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
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
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
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
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
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
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
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
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
474 init_options(void)
475 {
476
477         options_set_request(OPT_ROLLOVER, "0");
478 }