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