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/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 234 int size = atoi(options[OPT_BLKSIZE].o_request); 235 if (size < BLKSIZE_MIN || size > BLKSIZE_MAX) { 236 if (acting_as_client) { 237 tftp_log(LOG_ERR, 238 "Invalid blocksize (%d bytes), aborting", 239 size); 240 send_error(peer, EBADOP); 241 return (1); 242 } else { 243 tftp_log(LOG_WARNING, 244 "Invalid blocksize (%d bytes), ignoring request", 245 size); 246 return (0); 247 } 248 } 249 250 if (size > (int)maxdgram) { 251 if (acting_as_client) { 252 tftp_log(LOG_ERR, 253 "Invalid blocksize (%d bytes), " 254 "net.inet.udp.maxdgram sysctl limits it to " 255 "%ld bytes.\n", size, maxdgram); 256 send_error(peer, EBADOP); 257 return (1); 258 } else { 259 tftp_log(LOG_WARNING, 260 "Invalid blocksize (%d bytes), " 261 "net.inet.udp.maxdgram sysctl limits it to " 262 "%ld bytes.\n", size, maxdgram); 263 size = maxdgram; 264 /* No reason to return */ 265 } 266 } 267 268 options_set_reply(OPT_BLKSIZE, "%d", size); 269 segsize = size; 270 pktsize = size + 4; 271 if (debug & DEBUG_OPTIONS) 272 tftp_log(LOG_DEBUG, "Setting blksize to '%s'", 273 options[OPT_BLKSIZE].o_reply); 274 275 return (0); 276 } 277 278 int 279 option_blksize2(int peer __unused) 280 { 281 u_long maxdgram; 282 int size, i; 283 size_t len; 284 285 int sizes[] = { 286 8, 16, 32, 64, 128, 256, 512, 1024, 287 2048, 4096, 8192, 16384, 32768, 0 288 }; 289 290 if (options[OPT_BLKSIZE2].o_request == NULL) 291 return (0); 292 293 /* maximum size of an UDP packet according to the system */ 294 len = sizeof(maxdgram); 295 if (sysctlbyname("net.inet.udp.maxdgram", 296 &maxdgram, &len, NULL, 0) < 0) { 297 tftp_log(LOG_ERR, "sysctl: net.inet.udp.maxdgram"); 298 return (acting_as_client ? 1 : 0); 299 } 300 301 size = atoi(options[OPT_BLKSIZE2].o_request); 302 for (i = 0; sizes[i] != 0; i++) { 303 if (size == sizes[i]) break; 304 } 305 if (sizes[i] == 0) { 306 tftp_log(LOG_INFO, 307 "Invalid blocksize2 (%d bytes), ignoring request", size); 308 return (acting_as_client ? 1 : 0); 309 } 310 311 if (size > (int)maxdgram) { 312 for (i = 0; sizes[i+1] != 0; i++) { 313 if ((int)maxdgram < sizes[i+1]) break; 314 } 315 tftp_log(LOG_INFO, 316 "Invalid blocksize2 (%d bytes), net.inet.udp.maxdgram " 317 "sysctl limits it to %ld bytes.\n", size, maxdgram); 318 size = sizes[i]; 319 /* No need to return */ 320 } 321 322 options_set_reply(OPT_BLKSIZE2, "%d", size); 323 segsize = size; 324 pktsize = size + 4; 325 if (debug & DEBUG_OPTIONS) 326 tftp_log(LOG_DEBUG, "Setting blksize2 to '%s'", 327 options[OPT_BLKSIZE2].o_reply); 328 329 return (0); 330 } 331 332 int 333 option_windowsize(int peer) 334 { 335 int size; 336 337 if (options[OPT_WINDOWSIZE].o_request == NULL) 338 return (0); 339 340 size = atoi(options[OPT_WINDOWSIZE].o_request); 341 if (size < WINDOWSIZE_MIN || size > WINDOWSIZE_MAX) { 342 if (acting_as_client) { 343 tftp_log(LOG_ERR, 344 "Invalid windowsize (%d blocks), aborting", 345 size); 346 send_error(peer, EBADOP); 347 return (1); 348 } else { 349 tftp_log(LOG_WARNING, 350 "Invalid windowsize (%d blocks), ignoring request", 351 size); 352 return (0); 353 } 354 } 355 356 /* XXX: Should force a windowsize of 1 for non-seekable files. */ 357 options_set_reply(OPT_WINDOWSIZE, "%d", size); 358 windowsize = size; 359 360 if (debug & DEBUG_OPTIONS) 361 tftp_log(LOG_DEBUG, "Setting windowsize to '%s'", 362 options[OPT_WINDOWSIZE].o_reply); 363 364 return (0); 365 } 366 367 /* 368 * Append the available options to the header 369 */ 370 uint16_t 371 make_options(int peer __unused, char *buffer, uint16_t size) { 372 int i; 373 char *value; 374 const char *option; 375 uint16_t length; 376 uint16_t returnsize = 0; 377 378 if (!options_rfc_enabled) return (0); 379 380 for (i = 0; options[i].o_type != NULL; i++) { 381 if (options[i].rfc == 0 && !options_extra_enabled) 382 continue; 383 384 option = options[i].o_type; 385 if (acting_as_client) 386 value = options[i].o_request; 387 else 388 value = options[i].o_reply; 389 if (value == NULL) 390 continue; 391 392 length = strlen(value) + strlen(option) + 2; 393 if (size <= length) { 394 tftp_log(LOG_ERR, 395 "Running out of option space for " 396 "option '%s' with value '%s': " 397 "needed %d bytes, got %d bytes", 398 option, value, size, length); 399 continue; 400 } 401 402 sprintf(buffer, "%s%c%s%c", option, '\000', value, '\000'); 403 size -= length; 404 buffer += length; 405 returnsize += length; 406 } 407 408 return (returnsize); 409 } 410 411 /* 412 * Parse the received options in the header 413 */ 414 int 415 parse_options(int peer, char *buffer, uint16_t size) 416 { 417 int i, options_failed; 418 char *c, *cp, *option, *value; 419 420 if (!options_rfc_enabled) return (0); 421 422 /* Parse the options */ 423 cp = buffer; 424 options_failed = 0; 425 while (size > 0) { 426 option = cp; 427 i = get_field(peer, cp, size); 428 cp += i; 429 430 value = cp; 431 i = get_field(peer, cp, size); 432 cp += i; 433 434 /* We are at the end */ 435 if (*option == '\0') break; 436 437 if (debug & DEBUG_OPTIONS) 438 tftp_log(LOG_DEBUG, 439 "option: '%s' value: '%s'", option, value); 440 441 for (c = option; *c; c++) 442 if (isupper(*c)) 443 *c = tolower(*c); 444 for (i = 0; options[i].o_type != NULL; i++) { 445 if (strcmp(option, options[i].o_type) == 0) { 446 if (!acting_as_client) 447 options_set_request(i, "%s", value); 448 if (!options_extra_enabled && !options[i].rfc) { 449 tftp_log(LOG_INFO, 450 "Option '%s' with value '%s' found " 451 "but it is not an RFC option", 452 option, value); 453 continue; 454 } 455 if (options[i].o_handler) 456 options_failed += 457 (options[i].o_handler)(peer); 458 break; 459 } 460 } 461 if (options[i].o_type == NULL) 462 tftp_log(LOG_WARNING, 463 "Unknown option: '%s'", option); 464 465 size -= strlen(option) + strlen(value) + 2; 466 } 467 468 return (options_failed); 469 } 470 471 /* 472 * Set some default values in the options 473 */ 474 void 475 init_options(void) 476 { 477 478 options_set_request(OPT_ROLLOVER, "0"); 479 } 480