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