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