tftp-options.c revision 246139
1327Sjkh/* 2228990Suqs * Copyright (C) 2008 Edwin Groothuis. All rights reserved. 3327Sjkh * 4327Sjkh * Redistribution and use in source and binary forms, with or without 5327Sjkh * modification, are permitted provided that the following conditions 6327Sjkh * are met: 7327Sjkh * 1. Redistributions of source code must retain the above copyright 8327Sjkh * notice, this list of conditions and the following disclaimer. 9327Sjkh * 2. Redistributions in binary form must reproduce the above copyright 10327Sjkh * notice, this list of conditions and the following disclaimer in the 11327Sjkh * documentation and/or other materials provided with the distribution. 12327Sjkh * 13327Sjkh * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND 14327Sjkh * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15327Sjkh * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16327Sjkh * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE 17327Sjkh * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 18327Sjkh * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 19327Sjkh * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 20327Sjkh * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 2193520Sobrien * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 2293520Sobrien * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 2393520Sobrien * SUCH DAMAGE. 24222035Sflz */ 25327Sjkh 26327Sjkh#include <sys/cdefs.h> 2730221Scharnier__FBSDID("$FreeBSD: head/libexec/tftpd/tftp-options.c 246139 2013-01-31 00:02:36Z marius $"); 2883663Ssobomax 29327Sjkh#include <sys/socket.h> 3089458Ssobomax#include <sys/types.h> 31179352Skeramida#include <sys/sysctl.h> 32179352Skeramida#include <sys/stat.h> 338051Sjkh 3416549Sjkh#include <netinet/in.h> 3513946Sjdp#include <arpa/tftp.h> 36327Sjkh 37327Sjkh#include <ctype.h> 3884745Ssobomax#include <stdio.h> 39147043Ssobomax#include <stdlib.h> 40147043Ssobomax#include <string.h> 41327Sjkh#include <syslog.h> 42327Sjkh 43327Sjkh#include "tftp-utils.h" 44327Sjkh#include "tftp-io.h" 45194497Sbrian#include "tftp-options.h" 46327Sjkh 4711780Sjkh/* 48327Sjkh * Option handlers 49327Sjkh */ 5038933Sjkh 5184745Ssobomaxstruct options options[] = { 52327Sjkh { "tsize", NULL, NULL, NULL /* option_tsize */, 1 }, 53327Sjkh { "timeout", NULL, NULL, option_timeout, 1 }, 5484670Ssobomax { "blksize", NULL, NULL, option_blksize, 1 }, 5584670Ssobomax { "blksize2", NULL, NULL, option_blksize2, 0 }, 567986Sjkh { "rollover", NULL, NULL, option_rollover, 0 }, 57327Sjkh { NULL, NULL, NULL, NULL, 0 } 58327Sjkh}; 5941530Sasami 6038933Sjkh/* By default allow them */ 6149637Sbillfint options_rfc_enabled = 1; 62102384Sobrienint options_extra_enabled = 1; 63102384Sobrien 64102384Sobrien/* 65102384Sobrien * Rules for the option handlers: 66102384Sobrien * - If there is no o_request, there will be no processing. 6795161Sobrien * 6841530Sasami * For servers 6941530Sasami * - Logging is done as warnings. 70213718Sflz * - The handler exit()s if there is a serious problem with the 71213718Sflz * values submitted in the option. 72213718Sflz * 73213718Sflz * For clients 7441530Sasami * - Logging is done as errors. After all, the server shouldn't 7595161Sobrien * return rubbish. 7641530Sasami * - The handler returns if there is a serious problem with the 7741530Sasami * values submitted in the option. 7849637Sbillf * - Sending the EBADOP packets is done by the handler. 7995161Sobrien */ 80101302Sobrien 81102384Sobrienint 8295161Sobrienoption_tsize(int peer __unused, struct tftphdr *tp __unused, int mode, 8341530Sasami struct stat *stbuf) 8489458Ssobomax{ 85213718Sflz 86213718Sflz if (options[OPT_TSIZE].o_request == NULL) 8789458Ssobomax return (0); 8841530Sasami 89327Sjkh if (mode == RRQ) 90147043Ssobomax asprintf(&options[OPT_TSIZE].o_reply, 91152210Skrion "%ju", stbuf->st_size); 92152210Skrion else 93152210Skrion /* XXX Allows writes of all sizes. */ 94152210Skrion options[OPT_TSIZE].o_reply = 95152210Skrion strdup(options[OPT_TSIZE].o_request); 96152210Skrion return (0); 97152210Skrion} 98152210Skrion 99152210Skrionint 100152210Skrionoption_timeout(int peer) 101152210Skrion{ 102152210Skrion int to; 103152210Skrion 104152210Skrion if (options[OPT_TIMEOUT].o_request == NULL) 105152210Skrion return (0); 106152210Skrion 107152210Skrion to = atoi(options[OPT_TIMEOUT].o_request); 108152210Skrion if (to < TIMEOUT_MIN || to > TIMEOUT_MAX) { 109152210Skrion tftp_log(acting_as_client ? LOG_ERR : LOG_WARNING, 110152210Skrion "Received bad value for timeout. " 111152210Skrion "Should be between %d and %d, received %d", 112152210Skrion TIMEOUT_MIN, TIMEOUT_MAX, to); 113152210Skrion send_error(peer, EBADOP); 114152210Skrion if (acting_as_client) 115152210Skrion return (1); 116152210Skrion exit(1); 117152210Skrion } else { 118152210Skrion timeoutpacket = to; 119152210Skrion options[OPT_TIMEOUT].o_reply = 120152210Skrion strdup(options[OPT_TIMEOUT].o_request); 121152210Skrion } 122152210Skrion settimeouts(timeoutpacket, timeoutnetwork, maxtimeouts); 123152210Skrion 124152210Skrion if (debug&DEBUG_OPTIONS) 125147043Ssobomax tftp_log(LOG_DEBUG, "Setting timeout to '%s'", 12684670Ssobomax options[OPT_TIMEOUT].o_reply); 12784670Ssobomax 12884670Ssobomax return (0); 12984670Ssobomax} 13084670Ssobomax 13184670Ssobomaxint 13284670Ssobomaxoption_rollover(int peer) 13384670Ssobomax{ 13484670Ssobomax 13596388Salfred if (options[OPT_ROLLOVER].o_request == NULL) 13696392Salfred return (0); 13784670Ssobomax 13884670Ssobomax if (strcmp(options[OPT_ROLLOVER].o_request, "0") != 0 13984670Ssobomax && strcmp(options[OPT_ROLLOVER].o_request, "1") != 0) { 14084670Ssobomax tftp_log(acting_as_client ? LOG_ERR : LOG_WARNING, 1417713Sjkh "Bad value for rollover, " 1427733Sjkh "should be either 0 or 1, received '%s', " 14396076Ssobomax "ignoring request", 14474295Ssobomax options[OPT_ROLLOVER].o_request); 14574295Ssobomax if (acting_as_client) { 14674295Ssobomax send_error(peer, EBADOP); 1477991Sjkh return (1); 1487733Sjkh } 14974295Ssobomax return (0); 15074295Ssobomax } 15174295Ssobomax options[OPT_ROLLOVER].o_reply = 15274295Ssobomax strdup(options[OPT_ROLLOVER].o_request); 15374295Ssobomax 15474295Ssobomax if (debug&DEBUG_OPTIONS) 15574295Ssobomax tftp_log(LOG_DEBUG, "Setting rollover to '%s'", 15674295Ssobomax options[OPT_ROLLOVER].o_reply); 15774295Ssobomax 15874295Ssobomax return (0); 15974295Ssobomax} 16096392Salfred 16174295Ssobomaxint 16274295Ssobomaxoption_blksize(int peer) 16390985Ssobomax{ 16474295Ssobomax u_long maxdgram; 16590985Ssobomax size_t len; 16674295Ssobomax 16790985Ssobomax if (options[OPT_BLKSIZE].o_request == NULL) 16890985Ssobomax return (0); 16974295Ssobomax 17090985Ssobomax /* maximum size of an UDP packet according to the system */ 17174295Ssobomax len = sizeof(maxdgram); 17274295Ssobomax if (sysctlbyname("net.inet.udp.maxdgram", 17374295Ssobomax &maxdgram, &len, NULL, 0) < 0) { 17474295Ssobomax tftp_log(LOG_ERR, "sysctl: net.inet.udp.maxdgram"); 17596076Ssobomax return (acting_as_client ? 1 : 0); 17696076Ssobomax } 17796076Ssobomax 17896076Ssobomax int size = atoi(options[OPT_BLKSIZE].o_request); 17996076Ssobomax if (size < BLKSIZE_MIN || size > BLKSIZE_MAX) { 18074295Ssobomax if (acting_as_client) { 1817992Sjkh tftp_log(LOG_ERR, 18274295Ssobomax "Invalid blocksize (%d bytes), aborting", 1837733Sjkh size); 1847713Sjkh send_error(peer, EBADOP); 18574295Ssobomax return (1); 1867991Sjkh } else { 1877733Sjkh tftp_log(LOG_WARNING, 1887713Sjkh "Invalid blocksize (%d bytes), ignoring request", 18926473Sjkh size); 190113594Skris return (0); 191113594Skris } 192113594Skris } 193113594Skris 194113594Skris if (size > (int)maxdgram) { 195113594Skris if (acting_as_client) { 196113594Skris tftp_log(LOG_ERR, 197113594Skris "Invalid blocksize (%d bytes), " 198113594Skris "net.inet.udp.maxdgram sysctl limits it to " 199113594Skris "%ld bytes.\n", size, maxdgram); 200113594Skris send_error(peer, EBADOP); 201113594Skris return (1); 202113594Skris } else { 203113594Skris tftp_log(LOG_WARNING, 204113594Skris "Invalid blocksize (%d bytes), " 205113594Skris "net.inet.udp.maxdgram sysctl limits it to " 20626473Sjkh "%ld bytes.\n", size, maxdgram); 20726473Sjkh size = maxdgram; 20826473Sjkh /* No reason to return */ 20926473Sjkh } 21026473Sjkh } 21126473Sjkh 21226473Sjkh asprintf(&options[OPT_BLKSIZE].o_reply, "%d", size); 213327Sjkh segsize = size; 214327Sjkh pktsize = size + 4; 215327Sjkh if (debug&DEBUG_OPTIONS) 21631166Sjkh tftp_log(LOG_DEBUG, "Setting blksize to '%s'", 217231300Seadler options[OPT_BLKSIZE].o_reply); 218240682Sbapt 219240682Sbapt return (0); 220240682Sbapt} 221240682Sbapt 222240682Sbaptint 223240682Sbaptoption_blksize2(int peer __unused) 224240682Sbapt{ 225240682Sbapt u_long maxdgram; 226231300Seadler int size, i; 22796066Ssobomax size_t len; 22896066Ssobomax 22996066Ssobomax int sizes[] = { 23096066Ssobomax 8, 16, 32, 64, 128, 256, 512, 1024, 23196066Ssobomax 2048, 4096, 8192, 16384, 32768, 0 232379Sjkh }; 233379Sjkh 234379Sjkh if (options[OPT_BLKSIZE2].o_request == NULL) 235379Sjkh return (0); 2361546Sjkh 23781571Sobrien /* maximum size of an UDP packet according to the system */ 238379Sjkh len = sizeof(maxdgram); 23984750Ssobomax if (sysctlbyname("net.inet.udp.maxdgram", 24084750Ssobomax &maxdgram, &len, NULL, 0) < 0) { 24196392Salfred tftp_log(LOG_ERR, "sysctl: net.inet.udp.maxdgram"); 24284750Ssobomax return (acting_as_client ? 1 : 0); 24384750Ssobomax } 24484750Ssobomax 24584750Ssobomax size = atoi(options[OPT_BLKSIZE2].o_request); 2467986Sjkh for (i = 0; sizes[i] != 0; i++) { 2477986Sjkh if (size == sizes[i]) break; 2487986Sjkh } 2497986Sjkh if (sizes[i] == 0) { 2507986Sjkh tftp_log(LOG_INFO, 25127192Sjkh "Invalid blocksize2 (%d bytes), ignoring request", size); 2527986Sjkh return (acting_as_client ? 1 : 0); 2537986Sjkh } 2547986Sjkh 2557986Sjkh if (size > (int)maxdgram) { 256327Sjkh for (i = 0; sizes[i+1] != 0; i++) { 2573577Sjkh if ((int)maxdgram < sizes[i+1]) break; 258327Sjkh } 259327Sjkh tftp_log(LOG_INFO, 260327Sjkh "Invalid blocksize2 (%d bytes), net.inet.udp.maxdgram " 261327Sjkh "sysctl limits it to %ld bytes.\n", size, maxdgram); 262327Sjkh size = sizes[i]; 26376739Ssobomax /* No need to return */ 26476739Ssobomax } 26576739Ssobomax 26676739Ssobomax asprintf(&options[OPT_BLKSIZE2].o_reply, "%d", size); 2678000Sjkh segsize = size; 2688000Sjkh pktsize = size + 4; 269327Sjkh if (debug&DEBUG_OPTIONS) 270327Sjkh tftp_log(LOG_DEBUG, "Setting blksize2 to '%s'", 271231300Seadler options[OPT_BLKSIZE2].o_reply); 272231300Seadler 273231300Seadler return (0); 274327Sjkh} 275327Sjkh 276327Sjkh/* 277131277Seik * Append the available options to the header 278327Sjkh */ 279327Sjkhuint16_t 280327Sjkhmake_options(int peer __unused, char *buffer, uint16_t size) { 281131277Seik int i; 282327Sjkh char *value; 283327Sjkh const char *option; 284327Sjkh uint16_t length; 285327Sjkh uint16_t returnsize = 0; 286327Sjkh 287131277Seik if (!options_rfc_enabled) return (0); 288327Sjkh 28941866Sjkh for (i = 0; options[i].o_type != NULL; i++) { 29041866Sjkh if (options[i].rfc == 0 && !options_extra_enabled) 29141866Sjkh continue; 29241866Sjkh 293131277Seik option = options[i].o_type; 29441866Sjkh if (acting_as_client) 295327Sjkh value = options[i].o_request; 296327Sjkh else 297327Sjkh value = options[i].o_reply; 298327Sjkh if (value == NULL) 299131277Seik continue; 300327Sjkh 30141866Sjkh length = strlen(value) + strlen(option) + 2; 30241866Sjkh if (size <= length) { 30341866Sjkh tftp_log(LOG_ERR, 30441866Sjkh "Running out of option space for " 305131277Seik "option '%s' with value '%s': " 30641866Sjkh "needed %d bytes, got %d bytes", 307327Sjkh option, value, size, length); 308327Sjkh continue; 309327Sjkh } 310327Sjkh 311131277Seik sprintf(buffer, "%s%c%s%c", option, '\000', value, '\000'); 312327Sjkh size -= length; 3134996Sjkh buffer += length; 3144996Sjkh returnsize += length; 3154996Sjkh } 3164996Sjkh 317131277Seik return (returnsize); 3184996Sjkh} 3194996Sjkh 3204996Sjkh/* 3214996Sjkh * Parse the received options in the header 3224996Sjkh */ 3234996Sjkhint 324131277Seikparse_options(int peer, char *buffer, uint16_t size) 3254996Sjkh{ 3264996Sjkh int i, options_failed; 327327Sjkh char *c, *cp, *option, *value; 328327Sjkh 329327Sjkh if (!options_rfc_enabled) return (0); 33039068Sjkh 33139068Sjkh /* Parse the options */ 33296388Salfred cp = buffer; 33396392Salfred options_failed = 0; 33439068Sjkh while (size > 0) { 335327Sjkh option = cp; 33639068Sjkh i = get_field(peer, cp, size); 33739068Sjkh cp += i; 33896388Salfred 33996392Salfred value = cp; 34039068Sjkh i = get_field(peer, cp, size); 341327Sjkh cp += i; 342327Sjkh 34341530Sasami /* We are at the end */ 344327Sjkh if (*option == '\0') break; 345327Sjkh 346327Sjkh if (debug&DEBUG_OPTIONS) 347327Sjkh tftp_log(LOG_DEBUG, 348327Sjkh "option: '%s' value: '%s'", option, value); 34933427Sjkh 350327Sjkh for (c = option; *c; c++) 351327Sjkh if (isupper(*c)) 352327Sjkh *c = tolower(*c); 353327Sjkh for (i = 0; options[i].o_type != NULL; i++) { 35484745Ssobomax if (strcmp(option, options[i].o_type) == 0) { 355327Sjkh if (!acting_as_client) 356179352Skeramida options[i].o_request = value; 357327Sjkh if (!options_extra_enabled && !options[i].rfc) { 3588419Sjkh tftp_log(LOG_INFO, 35916549Sjkh "Option '%s' with value '%s' found " 36084745Ssobomax "but it is not an RFC option", 3618419Sjkh option, value); 36213946Sjdp continue; 36313946Sjdp } 36413946Sjdp if (options[i].o_handler) 36584745Ssobomax options_failed += 366154102Skrion (options[i].o_handler)(peer); 367327Sjkh break; 368154102Skrion } 3698419Sjkh } 3708419Sjkh if (options[i].o_type == NULL) 3712389Sadam tftp_log(LOG_WARNING, 37284745Ssobomax "Unknown option: '%s'", option); 3732389Sadam 37484745Ssobomax size -= strlen(option) + strlen(value) + 2; 3758419Sjkh } 376179352Skeramida 377179352Skeramida return (options_failed); 378179352Skeramida} 379179352Skeramida 380179352Skeramida/* 381179352Skeramida * Set some default values in the options 382179352Skeramida */ 383179352Skeramidavoid 384179352Skeramidainit_options(void) 385179352Skeramida{ 3868419Sjkh 3878419Sjkh options[OPT_ROLLOVER].o_request = strdup("0"); 3888419Sjkh} 38984745Ssobomax