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 103 if (options[OPT_TIMEOUT].o_request == NULL) 104 return (0); 105 106 int to = atoi(options[OPT_TIMEOUT].o_request); 107 if (to < TIMEOUT_MIN || to > TIMEOUT_MAX) { 108 tftp_log(acting_as_client ? LOG_ERR : LOG_WARNING, 109 "Received bad value for timeout. " 110 "Should be between %d and %d, received %s", 111 TIMEOUT_MIN, TIMEOUT_MAX); 112 send_error(peer, EBADOP); 113 if (acting_as_client) 114 return (1); 115 exit(1); 116 } else { 117 timeoutpacket = to; 118 options[OPT_TIMEOUT].o_reply = 119 strdup(options[OPT_TIMEOUT].o_request); 120 } 121 settimeouts(timeoutpacket, timeoutnetwork, maxtimeouts); 122 123 if (debug&DEBUG_OPTIONS) 124 tftp_log(LOG_DEBUG, "Setting timeout to '%s'", 125 options[OPT_TIMEOUT].o_reply); 126 127 return (0); 128 } 129 130 int 131 option_rollover(int peer) 132 { 133 134 if (options[OPT_ROLLOVER].o_request == NULL) 135 return (0); 136 137 if (strcmp(options[OPT_ROLLOVER].o_request, "0") != 0 138 && strcmp(options[OPT_ROLLOVER].o_request, "1") != 0) { 139 tftp_log(acting_as_client ? LOG_ERR : LOG_WARNING, 140 "Bad value for rollover, " 141 "should be either 0 or 1, received '%s', " 142 "ignoring request", 143 options[OPT_ROLLOVER].o_request); 144 if (acting_as_client) { 145 send_error(peer, EBADOP); 146 return (1); 147 } 148 return (0); 149 } 150 options[OPT_ROLLOVER].o_reply = 151 strdup(options[OPT_ROLLOVER].o_request); 152 153 if (debug&DEBUG_OPTIONS) 154 tftp_log(LOG_DEBUG, "Setting rollover to '%s'", 155 options[OPT_ROLLOVER].o_reply); 156 157 return (0); 158 } 159 160 int 161 option_blksize(int peer) 162 { 163 u_long maxdgram; 164 size_t len; 165 166 if (options[OPT_BLKSIZE].o_request == NULL) 167 return (0); 168 169 /* maximum size of an UDP packet according to the system */ 170 len = sizeof(maxdgram); 171 if (sysctlbyname("net.inet.udp.maxdgram", 172 &maxdgram, &len, NULL, 0) < 0) { 173 tftp_log(LOG_ERR, "sysctl: net.inet.udp.maxdgram"); 174 return (acting_as_client ? 1 : 0); 175 } 176 177 int size = atoi(options[OPT_BLKSIZE].o_request); 178 if (size < BLKSIZE_MIN || size > BLKSIZE_MAX) { 179 if (acting_as_client) { 180 tftp_log(LOG_ERR, 181 "Invalid blocksize (%d bytes), aborting", 182 size); 183 send_error(peer, EBADOP); 184 return (1); 185 } else { 186 tftp_log(LOG_WARNING, 187 "Invalid blocksize (%d bytes), ignoring request", 188 size); 189 return (0); 190 } 191 } 192 193 if (size > (int)maxdgram) { 194 if (acting_as_client) { 195 tftp_log(LOG_ERR, 196 "Invalid blocksize (%d bytes), " 197 "net.inet.udp.maxdgram sysctl limits it to " 198 "%d bytes.\n", size, maxdgram); 199 send_error(peer, EBADOP); 200 return (1); 201 } else { 202 tftp_log(LOG_WARNING, 203 "Invalid blocksize (%d bytes), " 204 "net.inet.udp.maxdgram sysctl limits it to " 205 "%d bytes.\n", size, maxdgram); 206 size = maxdgram; 207 /* No reason to return */ 208 } 209 } 210 211 asprintf(&options[OPT_BLKSIZE].o_reply, "%d", size); 212 segsize = size; 213 pktsize = size + 4; 214 if (debug&DEBUG_OPTIONS) 215 tftp_log(LOG_DEBUG, "Setting blksize to '%s'", 216 options[OPT_BLKSIZE].o_reply); 217 218 return (0); 219 } 220 221 int 222 option_blksize2(int peer __unused) 223 { 224 u_long maxdgram; 225 int size, i; 226 size_t len; 227 228 int sizes[] = { 229 8, 16, 32, 64, 128, 256, 512, 1024, 230 2048, 4096, 8192, 16384, 32768, 0 231 }; 232 233 if (options[OPT_BLKSIZE2].o_request == NULL) 234 return (0); 235 236 /* maximum size of an UDP packet according to the system */ 237 len = sizeof(maxdgram); 238 if (sysctlbyname("net.inet.udp.maxdgram", 239 &maxdgram, &len, NULL, 0) < 0) { 240 tftp_log(LOG_ERR, "sysctl: net.inet.udp.maxdgram"); 241 return (acting_as_client ? 1 : 0); 242 } 243 244 size = atoi(options[OPT_BLKSIZE2].o_request); 245 for (i = 0; sizes[i] != 0; i++) { 246 if (size == sizes[i]) break; 247 } 248 if (sizes[i] == 0) { 249 tftp_log(LOG_INFO, 250 "Invalid blocksize2 (%d bytes), ignoring request", size); 251 return (acting_as_client ? 1 : 0); 252 } 253 254 if (size > (int)maxdgram) { 255 for (i = 0; sizes[i+1] != 0; i++) { 256 if ((int)maxdgram < sizes[i+1]) break; 257 } 258 tftp_log(LOG_INFO, 259 "Invalid blocksize2 (%d bytes), net.inet.udp.maxdgram " 260 "sysctl limits it to %d bytes.\n", size, maxdgram); 261 size = sizes[i]; 262 /* No need to return */ 263 } 264 265 asprintf(&options[OPT_BLKSIZE2].o_reply, "%d", size); 266 segsize = size; 267 pktsize = size + 4; 268 if (debug&DEBUG_OPTIONS) 269 tftp_log(LOG_DEBUG, "Setting blksize2 to '%s'", 270 options[OPT_BLKSIZE2].o_reply); 271 272 return (0); 273 } 274 275 /* 276 * Append the available options to the header 277 */ 278 uint16_t 279 make_options(int peer __unused, char *buffer, uint16_t size) { 280 int i; 281 char *value; 282 const char *option; 283 uint16_t length; 284 uint16_t returnsize = 0; 285 286 if (!options_rfc_enabled) return (0); 287 288 for (i = 0; options[i].o_type != NULL; i++) { 289 if (options[i].rfc == 0 && !options_extra_enabled) 290 continue; 291 292 option = options[i].o_type; 293 if (acting_as_client) 294 value = options[i].o_request; 295 else 296 value = options[i].o_reply; 297 if (value == NULL) 298 continue; 299 300 length = strlen(value) + strlen(option) + 2; 301 if (size <= length) { 302 tftp_log(LOG_ERR, 303 "Running out of option space for " 304 "option '%s' with value '%s': " 305 "needed %d bytes, got %d bytes", 306 option, value, size, length); 307 continue; 308 } 309 310 sprintf(buffer, "%s%c%s%c", option, '\000', value, '\000'); 311 size -= length; 312 buffer += length; 313 returnsize += length; 314 } 315 316 return (returnsize); 317 } 318 319 /* 320 * Parse the received options in the header 321 */ 322 int 323 parse_options(int peer, char *buffer, uint16_t size) 324 { 325 int i, options_failed; 326 char *c, *cp, *option, *value; 327 328 if (!options_rfc_enabled) return (0); 329 330 /* Parse the options */ 331 cp = buffer; 332 options_failed = 0; 333 while (size > 0) { 334 option = cp; 335 i = get_field(peer, cp, size); 336 cp += i; 337 338 value = cp; 339 i = get_field(peer, cp, size); 340 cp += i; 341 342 /* We are at the end */ 343 if (*option == '\0') break; 344 345 if (debug&DEBUG_OPTIONS) 346 tftp_log(LOG_DEBUG, 347 "option: '%s' value: '%s'", option, value); 348 349 for (c = option; *c; c++) 350 if (isupper(*c)) 351 *c = tolower(*c); 352 for (i = 0; options[i].o_type != NULL; i++) { 353 if (strcmp(option, options[i].o_type) == 0) { 354 if (!acting_as_client) 355 options[i].o_request = value; 356 if (!options_extra_enabled && !options[i].rfc) { 357 tftp_log(LOG_INFO, 358 "Option '%s' with value '%s' found " 359 "but it is not an RFC option", 360 option, value); 361 continue; 362 } 363 if (options[i].o_handler) 364 options_failed += 365 (options[i].o_handler)(peer); 366 break; 367 } 368 } 369 if (options[i].o_type == NULL) 370 tftp_log(LOG_WARNING, 371 "Unknown option: '%s'", option); 372 373 size -= strlen(option) + strlen(value) + 2; 374 } 375 376 return (options_failed); 377 } 378 379 /* 380 * Set some default values in the options 381 */ 382 void 383 init_options(void) 384 { 385 386 options[OPT_ROLLOVER].o_request = strdup("0"); 387 } 388