]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - libexec/tftpd/tftp-options.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.git] / libexec / tftpd / tftp-options.c
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
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
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
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
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
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
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
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
384 init_options(void)
385 {
386
387         options[OPT_ROLLOVER].o_request = strdup("0");
388 }