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