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