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