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