1207614Simp/* 2207614Simp * Copyright (C) 2008 Edwin Groothuis. All rights reserved. 3207614Simp * 4207614Simp * Redistribution and use in source and binary forms, with or without 5207614Simp * modification, are permitted provided that the following conditions 6207614Simp * are met: 7207614Simp * 1. Redistributions of source code must retain the above copyright 8207614Simp * notice, this list of conditions and the following disclaimer. 9207614Simp * 2. Redistributions in binary form must reproduce the above copyright 10207614Simp * notice, this list of conditions and the following disclaimer in the 11207614Simp * documentation and/or other materials provided with the distribution. 12207614Simp * 13207614Simp * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND 14207614Simp * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15207614Simp * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16207614Simp * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE 17207614Simp * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 18207614Simp * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 19207614Simp * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 20207614Simp * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 21207614Simp * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 22207614Simp * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 23207614Simp * SUCH DAMAGE. 24207614Simp */ 25207614Simp 26207614Simp#include <sys/cdefs.h> 27207614Simp__FBSDID("$FreeBSD: releng/10.3/libexec/tftpd/tftp-options.c 246139 2013-01-31 00:02:36Z marius $"); 28207614Simp 29207614Simp#include <sys/socket.h> 30207614Simp#include <sys/types.h> 31207614Simp#include <sys/sysctl.h> 32207614Simp#include <sys/stat.h> 33207614Simp 34207614Simp#include <netinet/in.h> 35207614Simp#include <arpa/tftp.h> 36207614Simp 37207614Simp#include <ctype.h> 38207614Simp#include <stdio.h> 39207614Simp#include <stdlib.h> 40207614Simp#include <string.h> 41207614Simp#include <syslog.h> 42207614Simp 43207614Simp#include "tftp-utils.h" 44207614Simp#include "tftp-io.h" 45207614Simp#include "tftp-options.h" 46207614Simp 47207614Simp/* 48207614Simp * Option handlers 49207614Simp */ 50207614Simp 51207614Simpstruct options options[] = { 52207614Simp { "tsize", NULL, NULL, NULL /* option_tsize */, 1 }, 53207614Simp { "timeout", NULL, NULL, option_timeout, 1 }, 54207614Simp { "blksize", NULL, NULL, option_blksize, 1 }, 55207614Simp { "blksize2", NULL, NULL, option_blksize2, 0 }, 56207614Simp { "rollover", NULL, NULL, option_rollover, 0 }, 57207614Simp { NULL, NULL, NULL, NULL, 0 } 58207614Simp}; 59207614Simp 60207614Simp/* By default allow them */ 61207614Simpint options_rfc_enabled = 1; 62207614Simpint options_extra_enabled = 1; 63207614Simp 64207614Simp/* 65207614Simp * Rules for the option handlers: 66207614Simp * - If there is no o_request, there will be no processing. 67207614Simp * 68207614Simp * For servers 69207614Simp * - Logging is done as warnings. 70207614Simp * - The handler exit()s if there is a serious problem with the 71207614Simp * values submitted in the option. 72207614Simp * 73207614Simp * For clients 74207614Simp * - Logging is done as errors. After all, the server shouldn't 75207614Simp * return rubbish. 76207614Simp * - The handler returns if there is a serious problem with the 77207614Simp * values submitted in the option. 78207614Simp * - Sending the EBADOP packets is done by the handler. 79207614Simp */ 80207614Simp 81207614Simpint 82213099Smariusoption_tsize(int peer __unused, struct tftphdr *tp __unused, int mode, 83213099Smarius struct stat *stbuf) 84207614Simp{ 85207614Simp 86207614Simp if (options[OPT_TSIZE].o_request == NULL) 87207614Simp return (0); 88207614Simp 89207614Simp if (mode == RRQ) 90207614Simp asprintf(&options[OPT_TSIZE].o_reply, 91207614Simp "%ju", stbuf->st_size); 92207614Simp else 93207614Simp /* XXX Allows writes of all sizes. */ 94207614Simp options[OPT_TSIZE].o_reply = 95207614Simp strdup(options[OPT_TSIZE].o_request); 96207614Simp return (0); 97207614Simp} 98207614Simp 99207614Simpint 100207614Simpoption_timeout(int peer) 101207614Simp{ 102246139Smarius int to; 103207614Simp 104207614Simp if (options[OPT_TIMEOUT].o_request == NULL) 105207614Simp return (0); 106207614Simp 107246139Smarius to = atoi(options[OPT_TIMEOUT].o_request); 108207614Simp if (to < TIMEOUT_MIN || to > TIMEOUT_MAX) { 109207614Simp tftp_log(acting_as_client ? LOG_ERR : LOG_WARNING, 110207614Simp "Received bad value for timeout. " 111246139Smarius "Should be between %d and %d, received %d", 112246139Smarius TIMEOUT_MIN, TIMEOUT_MAX, to); 113207614Simp send_error(peer, EBADOP); 114207614Simp if (acting_as_client) 115207614Simp return (1); 116207614Simp exit(1); 117207614Simp } else { 118207614Simp timeoutpacket = to; 119207614Simp options[OPT_TIMEOUT].o_reply = 120207614Simp strdup(options[OPT_TIMEOUT].o_request); 121207614Simp } 122207614Simp settimeouts(timeoutpacket, timeoutnetwork, maxtimeouts); 123207614Simp 124207614Simp if (debug&DEBUG_OPTIONS) 125207614Simp tftp_log(LOG_DEBUG, "Setting timeout to '%s'", 126207614Simp options[OPT_TIMEOUT].o_reply); 127207614Simp 128207614Simp return (0); 129207614Simp} 130207614Simp 131207614Simpint 132207614Simpoption_rollover(int peer) 133207614Simp{ 134207614Simp 135207614Simp if (options[OPT_ROLLOVER].o_request == NULL) 136207614Simp return (0); 137207614Simp 138207614Simp if (strcmp(options[OPT_ROLLOVER].o_request, "0") != 0 139207614Simp && strcmp(options[OPT_ROLLOVER].o_request, "1") != 0) { 140207614Simp tftp_log(acting_as_client ? LOG_ERR : LOG_WARNING, 141207614Simp "Bad value for rollover, " 142207614Simp "should be either 0 or 1, received '%s', " 143207614Simp "ignoring request", 144207614Simp options[OPT_ROLLOVER].o_request); 145207614Simp if (acting_as_client) { 146207614Simp send_error(peer, EBADOP); 147207614Simp return (1); 148207614Simp } 149207614Simp return (0); 150207614Simp } 151207614Simp options[OPT_ROLLOVER].o_reply = 152207614Simp strdup(options[OPT_ROLLOVER].o_request); 153207614Simp 154207614Simp if (debug&DEBUG_OPTIONS) 155207614Simp tftp_log(LOG_DEBUG, "Setting rollover to '%s'", 156207614Simp options[OPT_ROLLOVER].o_reply); 157207614Simp 158207614Simp return (0); 159207614Simp} 160207614Simp 161207614Simpint 162207614Simpoption_blksize(int peer) 163207614Simp{ 164213099Smarius u_long maxdgram; 165207614Simp size_t len; 166207614Simp 167207614Simp if (options[OPT_BLKSIZE].o_request == NULL) 168207614Simp return (0); 169207614Simp 170207614Simp /* maximum size of an UDP packet according to the system */ 171213099Smarius len = sizeof(maxdgram); 172207614Simp if (sysctlbyname("net.inet.udp.maxdgram", 173213099Smarius &maxdgram, &len, NULL, 0) < 0) { 174207614Simp tftp_log(LOG_ERR, "sysctl: net.inet.udp.maxdgram"); 175207614Simp return (acting_as_client ? 1 : 0); 176207614Simp } 177207614Simp 178207614Simp int size = atoi(options[OPT_BLKSIZE].o_request); 179207614Simp if (size < BLKSIZE_MIN || size > BLKSIZE_MAX) { 180207614Simp if (acting_as_client) { 181207614Simp tftp_log(LOG_ERR, 182207614Simp "Invalid blocksize (%d bytes), aborting", 183207614Simp size); 184207614Simp send_error(peer, EBADOP); 185207614Simp return (1); 186207614Simp } else { 187207614Simp tftp_log(LOG_WARNING, 188207614Simp "Invalid blocksize (%d bytes), ignoring request", 189207614Simp size); 190207614Simp return (0); 191207614Simp } 192207614Simp } 193207614Simp 194213099Smarius if (size > (int)maxdgram) { 195207614Simp if (acting_as_client) { 196207614Simp tftp_log(LOG_ERR, 197207614Simp "Invalid blocksize (%d bytes), " 198207614Simp "net.inet.udp.maxdgram sysctl limits it to " 199246139Smarius "%ld bytes.\n", size, maxdgram); 200207614Simp send_error(peer, EBADOP); 201207614Simp return (1); 202207614Simp } else { 203207614Simp tftp_log(LOG_WARNING, 204207614Simp "Invalid blocksize (%d bytes), " 205207614Simp "net.inet.udp.maxdgram sysctl limits it to " 206246139Smarius "%ld bytes.\n", size, maxdgram); 207213099Smarius size = maxdgram; 208207614Simp /* No reason to return */ 209207614Simp } 210207614Simp } 211207614Simp 212207614Simp asprintf(&options[OPT_BLKSIZE].o_reply, "%d", size); 213207614Simp segsize = size; 214207614Simp pktsize = size + 4; 215207614Simp if (debug&DEBUG_OPTIONS) 216207614Simp tftp_log(LOG_DEBUG, "Setting blksize to '%s'", 217207614Simp options[OPT_BLKSIZE].o_reply); 218207614Simp 219207614Simp return (0); 220207614Simp} 221207614Simp 222207614Simpint 223213099Smariusoption_blksize2(int peer __unused) 224207614Simp{ 225213099Smarius u_long maxdgram; 226207614Simp int size, i; 227207614Simp size_t len; 228207614Simp 229207614Simp int sizes[] = { 230207614Simp 8, 16, 32, 64, 128, 256, 512, 1024, 231207614Simp 2048, 4096, 8192, 16384, 32768, 0 232207614Simp }; 233207614Simp 234207614Simp if (options[OPT_BLKSIZE2].o_request == NULL) 235207614Simp return (0); 236207614Simp 237207614Simp /* maximum size of an UDP packet according to the system */ 238213099Smarius len = sizeof(maxdgram); 239207614Simp if (sysctlbyname("net.inet.udp.maxdgram", 240213099Smarius &maxdgram, &len, NULL, 0) < 0) { 241207614Simp tftp_log(LOG_ERR, "sysctl: net.inet.udp.maxdgram"); 242207614Simp return (acting_as_client ? 1 : 0); 243207614Simp } 244207614Simp 245207614Simp size = atoi(options[OPT_BLKSIZE2].o_request); 246207614Simp for (i = 0; sizes[i] != 0; i++) { 247207614Simp if (size == sizes[i]) break; 248207614Simp } 249207614Simp if (sizes[i] == 0) { 250207614Simp tftp_log(LOG_INFO, 251207614Simp "Invalid blocksize2 (%d bytes), ignoring request", size); 252207614Simp return (acting_as_client ? 1 : 0); 253207614Simp } 254207614Simp 255213099Smarius if (size > (int)maxdgram) { 256207614Simp for (i = 0; sizes[i+1] != 0; i++) { 257213099Smarius if ((int)maxdgram < sizes[i+1]) break; 258207614Simp } 259207614Simp tftp_log(LOG_INFO, 260207614Simp "Invalid blocksize2 (%d bytes), net.inet.udp.maxdgram " 261246139Smarius "sysctl limits it to %ld bytes.\n", size, maxdgram); 262207614Simp size = sizes[i]; 263207614Simp /* No need to return */ 264207614Simp } 265207614Simp 266207614Simp asprintf(&options[OPT_BLKSIZE2].o_reply, "%d", size); 267207614Simp segsize = size; 268207614Simp pktsize = size + 4; 269207614Simp if (debug&DEBUG_OPTIONS) 270207614Simp tftp_log(LOG_DEBUG, "Setting blksize2 to '%s'", 271207614Simp options[OPT_BLKSIZE2].o_reply); 272207614Simp 273207614Simp return (0); 274207614Simp} 275207614Simp 276207614Simp/* 277207614Simp * Append the available options to the header 278207614Simp */ 279207614Simpuint16_t 280213099Smariusmake_options(int peer __unused, char *buffer, uint16_t size) { 281207614Simp int i; 282207614Simp char *value; 283207614Simp const char *option; 284207614Simp uint16_t length; 285207614Simp uint16_t returnsize = 0; 286207614Simp 287207614Simp if (!options_rfc_enabled) return (0); 288207614Simp 289207614Simp for (i = 0; options[i].o_type != NULL; i++) { 290207614Simp if (options[i].rfc == 0 && !options_extra_enabled) 291207614Simp continue; 292207614Simp 293207614Simp option = options[i].o_type; 294207614Simp if (acting_as_client) 295207614Simp value = options[i].o_request; 296207614Simp else 297207614Simp value = options[i].o_reply; 298207614Simp if (value == NULL) 299207614Simp continue; 300207614Simp 301207614Simp length = strlen(value) + strlen(option) + 2; 302207614Simp if (size <= length) { 303207614Simp tftp_log(LOG_ERR, 304207614Simp "Running out of option space for " 305207614Simp "option '%s' with value '%s': " 306207614Simp "needed %d bytes, got %d bytes", 307207614Simp option, value, size, length); 308207614Simp continue; 309207614Simp } 310207614Simp 311207614Simp sprintf(buffer, "%s%c%s%c", option, '\000', value, '\000'); 312207614Simp size -= length; 313207614Simp buffer += length; 314207614Simp returnsize += length; 315207614Simp } 316207614Simp 317207614Simp return (returnsize); 318207614Simp} 319207614Simp 320207614Simp/* 321207614Simp * Parse the received options in the header 322207614Simp */ 323207614Simpint 324207614Simpparse_options(int peer, char *buffer, uint16_t size) 325207614Simp{ 326207614Simp int i, options_failed; 327207614Simp char *c, *cp, *option, *value; 328207614Simp 329207614Simp if (!options_rfc_enabled) return (0); 330207614Simp 331207614Simp /* Parse the options */ 332207614Simp cp = buffer; 333207614Simp options_failed = 0; 334207614Simp while (size > 0) { 335207614Simp option = cp; 336207614Simp i = get_field(peer, cp, size); 337207614Simp cp += i; 338207614Simp 339207614Simp value = cp; 340207614Simp i = get_field(peer, cp, size); 341207614Simp cp += i; 342207614Simp 343207614Simp /* We are at the end */ 344207614Simp if (*option == '\0') break; 345207614Simp 346207614Simp if (debug&DEBUG_OPTIONS) 347207614Simp tftp_log(LOG_DEBUG, 348207614Simp "option: '%s' value: '%s'", option, value); 349207614Simp 350207614Simp for (c = option; *c; c++) 351207614Simp if (isupper(*c)) 352207614Simp *c = tolower(*c); 353207614Simp for (i = 0; options[i].o_type != NULL; i++) { 354207614Simp if (strcmp(option, options[i].o_type) == 0) { 355207614Simp if (!acting_as_client) 356207614Simp options[i].o_request = value; 357207614Simp if (!options_extra_enabled && !options[i].rfc) { 358207614Simp tftp_log(LOG_INFO, 359207614Simp "Option '%s' with value '%s' found " 360207614Simp "but it is not an RFC option", 361207614Simp option, value); 362207614Simp continue; 363207614Simp } 364207614Simp if (options[i].o_handler) 365207614Simp options_failed += 366207614Simp (options[i].o_handler)(peer); 367207614Simp break; 368207614Simp } 369207614Simp } 370207614Simp if (options[i].o_type == NULL) 371207614Simp tftp_log(LOG_WARNING, 372207614Simp "Unknown option: '%s'", option); 373207614Simp 374207614Simp size -= strlen(option) + strlen(value) + 2; 375207614Simp } 376207614Simp 377207614Simp return (options_failed); 378207614Simp} 379207614Simp 380207614Simp/* 381207614Simp * Set some default values in the options 382207614Simp */ 383207614Simpvoid 384207614Simpinit_options(void) 385207614Simp{ 386207614Simp 387207614Simp options[OPT_ROLLOVER].o_request = strdup("0"); 388207614Simp} 389