1 /* 2 * Copyright (C) 2008 Edwin Groothuis. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND 14 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE 17 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 18 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 19 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 20 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 21 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 22 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 23 * SUCH DAMAGE. 24 */ 25 26 #include <sys/cdefs.h> 27 __FBSDID("$FreeBSD$"); 28 29 #include <sys/socket.h> 30 #include <sys/types.h> 31 #include <sys/sysctl.h> 32 #include <sys/stat.h> 33 34 #include <netinet/in.h> 35 #include <arpa/tftp.h> 36 37 #include <ctype.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 { NULL, NULL, NULL, NULL, 0 } 58 }; 59 60 /* By default allow them */ 61 int options_rfc_enabled = 1; 62 int options_extra_enabled = 1; 63 64 /* 65 * Rules for the option handlers: 66 * - If there is no o_request, there will be no processing. 67 * 68 * For servers 69 * - Logging is done as warnings. 70 * - The handler exit()s if there is a serious problem with the 71 * values submitted in the option. 72 * 73 * For clients 74 * - Logging is done as errors. After all, the server shouldn't 75 * return rubbish. 76 * - The handler returns if there is a serious problem with the 77 * values submitted in the option. 78 * - Sending the EBADOP packets is done by the handler. 79 */ 80 81 int 82 option_tsize(int peer __unused, struct tftphdr *tp __unused, int mode, 83 struct stat *stbuf) 84 { 85 86 if (options[OPT_TSIZE].o_request == NULL) 87 return (0); 88 89 if (mode == RRQ) 90 asprintf(&options[OPT_TSIZE].o_reply, 91 "%ju", stbuf->st_size); 92 else 93 /* XXX Allows writes of all sizes. */ 94 options[OPT_TSIZE].o_reply = 95 strdup(options[OPT_TSIZE].o_request); 96 return (0); 97 } 98 99 int 100 option_timeout(int peer) 101 { 102 int to; 103 104 if (options[OPT_TIMEOUT].o_request == NULL) 105 return (0); 106 107 to = atoi(options[OPT_TIMEOUT].o_request); 108 if (to < TIMEOUT_MIN || to > TIMEOUT_MAX) { 109 tftp_log(acting_as_client ? LOG_ERR : LOG_WARNING, 110 "Received bad value for timeout. " 111 "Should be between %d and %d, received %d", 112 TIMEOUT_MIN, TIMEOUT_MAX, to); 113 send_error(peer, EBADOP); 114 if (acting_as_client) 115 return (1); 116 exit(1); 117 } else { 118 timeoutpacket = to; 119 options[OPT_TIMEOUT].o_reply = 120 strdup(options[OPT_TIMEOUT].o_request); 121 } 122 settimeouts(timeoutpacket, timeoutnetwork, maxtimeouts); 123 124 if (debug&DEBUG_OPTIONS) 125 tftp_log(LOG_DEBUG, "Setting timeout to '%s'", 126 options[OPT_TIMEOUT].o_reply); 127 128 return (0); 129 } 130 131 int 132 option_rollover(int peer) 133 { 134 135 if (options[OPT_ROLLOVER].o_request == NULL) 136 return (0); 137 138 if (strcmp(options[OPT_ROLLOVER].o_request, "0") != 0 139 && strcmp(options[OPT_ROLLOVER].o_request, "1") != 0) { 140 tftp_log(acting_as_client ? LOG_ERR : LOG_WARNING, 141 "Bad value for rollover, " 142 "should be either 0 or 1, received '%s', " 143 "ignoring request", 144 options[OPT_ROLLOVER].o_request); 145 if (acting_as_client) { 146 send_error(peer, EBADOP); 147 return (1); 148 } 149 return (0); 150 } 151 options[OPT_ROLLOVER].o_reply = 152 strdup(options[OPT_ROLLOVER].o_request); 153 154 if (debug&DEBUG_OPTIONS) 155 tftp_log(LOG_DEBUG, "Setting rollover to '%s'", 156 options[OPT_ROLLOVER].o_reply); 157 158 return (0); 159 } 160 161 int 162 option_blksize(int peer) 163 { 164 u_long maxdgram; 165 size_t len; 166 167 if (options[OPT_BLKSIZE].o_request == NULL) 168 return (0); 169 170 /* maximum size of an UDP packet according to the system */ 171 len = sizeof(maxdgram); 172 if (sysctlbyname("net.inet.udp.maxdgram", 173 &maxdgram, &len, NULL, 0) < 0) { 174 tftp_log(LOG_ERR, "sysctl: net.inet.udp.maxdgram"); 175 return (acting_as_client ? 1 : 0); 176 } 177 178 int size = atoi(options[OPT_BLKSIZE].o_request); 179 if (size < BLKSIZE_MIN || size > BLKSIZE_MAX) { 180 if (acting_as_client) { 181 tftp_log(LOG_ERR, 182 "Invalid blocksize (%d bytes), aborting", 183 size); 184 send_error(peer, EBADOP); 185 return (1); 186 } else { 187 tftp_log(LOG_WARNING, 188 "Invalid blocksize (%d bytes), ignoring request", 189 size); 190 return (0); 191 } 192 } 193 194 if (size > (int)maxdgram) { 195 if (acting_as_client) { 196 tftp_log(LOG_ERR, 197 "Invalid blocksize (%d bytes), " 198 "net.inet.udp.maxdgram sysctl limits it to " 199 "%ld bytes.\n", size, maxdgram); 200 send_error(peer, EBADOP); 201 return (1); 202 } else { 203 tftp_log(LOG_WARNING, 204 "Invalid blocksize (%d bytes), " 205 "net.inet.udp.maxdgram sysctl limits it to " 206 "%ld bytes.\n", size, maxdgram); 207 size = maxdgram; 208 /* No reason to return */ 209 } 210 } 211 212 asprintf(&options[OPT_BLKSIZE].o_reply, "%d", size); 213 segsize = size; 214 pktsize = size + 4; 215 if (debug&DEBUG_OPTIONS) 216 tftp_log(LOG_DEBUG, "Setting blksize to '%s'", 217 options[OPT_BLKSIZE].o_reply); 218 219 return (0); 220 } 221 222 int 223 option_blksize2(int peer __unused) 224 { 225 u_long maxdgram; 226 int size, i; 227 size_t len; 228 229 int sizes[] = { 230 8, 16, 32, 64, 128, 256, 512, 1024, 231 2048, 4096, 8192, 16384, 32768, 0 232 }; 233 234 if (options[OPT_BLKSIZE2].o_request == NULL) 235 return (0); 236 237 /* maximum size of an UDP packet according to the system */ 238 len = sizeof(maxdgram); 239 if (sysctlbyname("net.inet.udp.maxdgram", 240 &maxdgram, &len, NULL, 0) < 0) { 241 tftp_log(LOG_ERR, "sysctl: net.inet.udp.maxdgram"); 242 return (acting_as_client ? 1 : 0); 243 } 244 245 size = atoi(options[OPT_BLKSIZE2].o_request); 246 for (i = 0; sizes[i] != 0; i++) { 247 if (size == sizes[i]) break; 248 } 249 if (sizes[i] == 0) { 250 tftp_log(LOG_INFO, 251 "Invalid blocksize2 (%d bytes), ignoring request", size); 252 return (acting_as_client ? 1 : 0); 253 } 254 255 if (size > (int)maxdgram) { 256 for (i = 0; sizes[i+1] != 0; i++) { 257 if ((int)maxdgram < sizes[i+1]) break; 258 } 259 tftp_log(LOG_INFO, 260 "Invalid blocksize2 (%d bytes), net.inet.udp.maxdgram " 261 "sysctl limits it to %ld bytes.\n", size, maxdgram); 262 size = sizes[i]; 263 /* No need to return */ 264 } 265 266 asprintf(&options[OPT_BLKSIZE2].o_reply, "%d", size); 267 segsize = size; 268 pktsize = size + 4; 269 if (debug&DEBUG_OPTIONS) 270 tftp_log(LOG_DEBUG, "Setting blksize2 to '%s'", 271 options[OPT_BLKSIZE2].o_reply); 272 273 return (0); 274 } 275 276 /* 277 * Append the available options to the header 278 */ 279 uint16_t 280 make_options(int peer __unused, char *buffer, uint16_t size) { 281 int i; 282 char *value; 283 const char *option; 284 uint16_t length; 285 uint16_t returnsize = 0; 286 287 if (!options_rfc_enabled) return (0); 288 289 for (i = 0; options[i].o_type != NULL; i++) { 290 if (options[i].rfc == 0 && !options_extra_enabled) 291 continue; 292 293 option = options[i].o_type; 294 if (acting_as_client) 295 value = options[i].o_request; 296 else 297 value = options[i].o_reply; 298 if (value == NULL) 299 continue; 300 301 length = strlen(value) + strlen(option) + 2; 302 if (size <= length) { 303 tftp_log(LOG_ERR, 304 "Running out of option space for " 305 "option '%s' with value '%s': " 306 "needed %d bytes, got %d bytes", 307 option, value, size, length); 308 continue; 309 } 310 311 sprintf(buffer, "%s%c%s%c", option, '\000', value, '\000'); 312 size -= length; 313 buffer += length; 314 returnsize += length; 315 } 316 317 return (returnsize); 318 } 319 320 /* 321 * Parse the received options in the header 322 */ 323 int 324 parse_options(int peer, char *buffer, uint16_t size) 325 { 326 int i, options_failed; 327 char *c, *cp, *option, *value; 328 329 if (!options_rfc_enabled) return (0); 330 331 /* Parse the options */ 332 cp = buffer; 333 options_failed = 0; 334 while (size > 0) { 335 option = cp; 336 i = get_field(peer, cp, size); 337 cp += i; 338 339 value = cp; 340 i = get_field(peer, cp, size); 341 cp += i; 342 343 /* We are at the end */ 344 if (*option == '\0') break; 345 346 if (debug&DEBUG_OPTIONS) 347 tftp_log(LOG_DEBUG, 348 "option: '%s' value: '%s'", option, value); 349 350 for (c = option; *c; c++) 351 if (isupper(*c)) 352 *c = tolower(*c); 353 for (i = 0; options[i].o_type != NULL; i++) { 354 if (strcmp(option, options[i].o_type) == 0) { 355 if (!acting_as_client) 356 options[i].o_request = value; 357 if (!options_extra_enabled && !options[i].rfc) { 358 tftp_log(LOG_INFO, 359 "Option '%s' with value '%s' found " 360 "but it is not an RFC option", 361 option, value); 362 continue; 363 } 364 if (options[i].o_handler) 365 options_failed += 366 (options[i].o_handler)(peer); 367 break; 368 } 369 } 370 if (options[i].o_type == NULL) 371 tftp_log(LOG_WARNING, 372 "Unknown option: '%s'", option); 373 374 size -= strlen(option) + strlen(value) + 2; 375 } 376 377 return (options_failed); 378 } 379 380 /* 381 * Set some default values in the options 382 */ 383 void 384 init_options(void) 385 { 386 387 options[OPT_ROLLOVER].o_request = strdup("0"); 388 } 389