diff --git a/libexec/tftpd/Makefile b/libexec/tftpd/Makefile index b311ac8aa09e..d94df7c27074 100644 --- a/libexec/tftpd/Makefile +++ b/libexec/tftpd/Makefile @@ -1,20 +1,19 @@ # @(#)Makefile 8.1 (Berkeley) 6/4/93 # $FreeBSD$ .include PROG= tftpd MAN= tftpd.8 SRCS= tftp-file.c tftp-io.c tftp-options.c tftp-transfer.c tftp-utils.c SRCS+= tftpd.c -WFORMAT=0 .if ${MK_TCP_WRAPPERS} != "no" CFLAGS+= -DLIBWRAP LIBADD= wrap .endif HAS_TESTS= SUBDIR.${MK_TESTS}+= tests .include diff --git a/libexec/tftpd/tftp-io.c b/libexec/tftpd/tftp-io.c index 459a81508e2b..71fd57c6a7be 100644 --- a/libexec/tftpd/tftp-io.c +++ b/libexec/tftpd/tftp-io.c @@ -1,470 +1,470 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (C) 2008 Edwin Groothuis. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); -#include #include #include +#include #include -#include #include +#include #include #include #include #include #include #include #include #include #include #include #include "tftp-file.h" #include "tftp-io.h" #include "tftp-utils.h" #include "tftp-options.h" struct sockaddr_storage peer_sock; struct sockaddr_storage me_sock; static int send_packet(int peer, uint16_t block, char *pkt, int size); static struct errmsg { int e_code; const char *e_msg; } errmsgs[] = { { EUNDEF, "Undefined error code" }, { ENOTFOUND, "File not found" }, { EACCESS, "Access violation" }, { ENOSPACE, "Disk full or allocation exceeded" }, { EBADOP, "Illegal TFTP operation" }, { EBADID, "Unknown transfer ID" }, { EEXISTS, "File already exists" }, { ENOUSER, "No such user" }, { EOPTNEG, "Option negotiation" }, { -1, NULL } }; #define DROPPACKET(s) \ if (packetdroppercentage != 0 && \ random()%100 < packetdroppercentage) { \ tftp_log(LOG_DEBUG, "Artificial packet drop in %s", s); \ return; \ } #define DROPPACKETn(s,n) \ if (packetdroppercentage != 0 && \ random()%100 < packetdroppercentage) { \ tftp_log(LOG_DEBUG, "Artificial packet drop in %s", s); \ return (n); \ } const char * errtomsg(int error) { static char ebuf[40]; struct errmsg *pe; if (error == 0) return ("success"); for (pe = errmsgs; pe->e_code >= 0; pe++) if (pe->e_code == error) return (pe->e_msg); snprintf(ebuf, sizeof(ebuf), "error %d", error); return (ebuf); } static int send_packet(int peer, uint16_t block, char *pkt, int size) { int i; int t = 1; for (i = 0; i < 12 ; i++) { DROPPACKETn("send_packet", 0); if (sendto(peer, pkt, size, 0, (struct sockaddr *)&peer_sock, peer_sock.ss_len) == size) { if (i) tftp_log(LOG_ERR, "%s block %d, attempt %d successful", packettype(ntohs(((struct tftphdr *) (pkt))->th_opcode)), block, i); return (0); } tftp_log(LOG_ERR, "%s block %d, attempt %d failed (Error %d: %s)", packettype(ntohs(((struct tftphdr *)(pkt))->th_opcode)), block, i, errno, strerror(errno)); sleep(t); if (t < 32) t <<= 1; } tftp_log(LOG_ERR, "send_packet: %s", strerror(errno)); return (1); } /* * Send an ERROR packet (error message). * Error code passed in is one of the * standard TFTP codes, or a UNIX errno * offset by 100. */ void send_error(int peer, int error) { struct tftphdr *tp; int length; struct errmsg *pe; char buf[MAXPKTSIZE]; if (debug&DEBUG_PACKETS) tftp_log(LOG_DEBUG, "Sending ERROR %d", error); DROPPACKET("send_error"); tp = (struct tftphdr *)buf; tp->th_opcode = htons((u_short)ERROR); tp->th_code = htons((u_short)error); for (pe = errmsgs; pe->e_code >= 0; pe++) if (pe->e_code == error) break; if (pe->e_code < 0) { pe->e_msg = strerror(error - 100); tp->th_code = EUNDEF; /* set 'undef' errorcode */ } strcpy(tp->th_msg, pe->e_msg); length = strlen(pe->e_msg); tp->th_msg[length] = '\0'; length += 5; if (debug&DEBUG_PACKETS) tftp_log(LOG_DEBUG, "Sending ERROR %d: %s", error, tp->th_msg); if (sendto(peer, buf, length, 0, (struct sockaddr *)&peer_sock, peer_sock.ss_len) != length) tftp_log(LOG_ERR, "send_error: %s", strerror(errno)); } /* * Send an WRQ packet (write request). */ int send_wrq(int peer, char *filename, char *mode) { int n; struct tftphdr *tp; char *bp; char buf[MAXPKTSIZE]; int size; if (debug&DEBUG_PACKETS) tftp_log(LOG_DEBUG, "Sending WRQ: filename: '%s', mode '%s'", filename, mode ); DROPPACKETn("send_wrq", 0); tp = (struct tftphdr *)buf; tp->th_opcode = htons((u_short)WRQ); size = offsetof(struct tftphdr, th_stuff); bp = tp->th_stuff; strlcpy(bp, filename, sizeof(buf) - size); bp += strlen(filename); *bp = 0; bp++; size += strlen(filename) + 1; strlcpy(bp, mode, sizeof(buf) - size); bp += strlen(mode); *bp = 0; bp++; size += strlen(mode) + 1; if (options_rfc_enabled) size += make_options(peer, bp, sizeof(buf) - size); n = sendto(peer, buf, size, 0, (struct sockaddr *)&peer_sock, peer_sock.ss_len); if (n != size) { tftp_log(LOG_ERR, "send_wrq: %s", strerror(errno)); return (1); } return (0); } /* * Send an RRQ packet (write request). */ int send_rrq(int peer, char *filename, char *mode) { int n; struct tftphdr *tp; char *bp; char buf[MAXPKTSIZE]; int size; if (debug&DEBUG_PACKETS) tftp_log(LOG_DEBUG, "Sending RRQ: filename: '%s', mode '%s'", filename, mode ); DROPPACKETn("send_rrq", 0); tp = (struct tftphdr *)buf; tp->th_opcode = htons((u_short)RRQ); size = offsetof(struct tftphdr, th_stuff); bp = tp->th_stuff; strlcpy(bp, filename, sizeof(buf) - size); bp += strlen(filename); *bp = 0; bp++; size += strlen(filename) + 1; strlcpy(bp, mode, sizeof(buf) - size); bp += strlen(mode); *bp = 0; bp++; size += strlen(mode) + 1; if (options_rfc_enabled) { options[OPT_TSIZE].o_request = strdup("0"); size += make_options(peer, bp, sizeof(buf) - size); } n = sendto(peer, buf, size, 0, (struct sockaddr *)&peer_sock, peer_sock.ss_len); if (n != size) { tftp_log(LOG_ERR, "send_rrq: %d %s", n, strerror(errno)); return (1); } return (0); } /* * Send an OACK packet (option acknowledgement). */ int send_oack(int peer) { struct tftphdr *tp; int size, i, n; char *bp; char buf[MAXPKTSIZE]; if (debug&DEBUG_PACKETS) tftp_log(LOG_DEBUG, "Sending OACK"); DROPPACKETn("send_oack", 0); /* * Send back an options acknowledgement (only the ones with * a reply for) */ tp = (struct tftphdr *)buf; bp = buf + 2; size = sizeof(buf) - 2; tp->th_opcode = htons((u_short)OACK); for (i = 0; options[i].o_type != NULL; i++) { if (options[i].o_reply != NULL) { n = snprintf(bp, size, "%s%c%s", options[i].o_type, 0, options[i].o_reply); bp += n+1; size -= n+1; if (size < 0) { tftp_log(LOG_ERR, "oack: buffer overflow"); exit(1); } } } size = bp - buf; if (sendto(peer, buf, size, 0, (struct sockaddr *)&peer_sock, peer_sock.ss_len) != size) { tftp_log(LOG_INFO, "send_oack: %s", strerror(errno)); return (1); } return (0); } /* * Send an ACK packet (acknowledgement). */ int send_ack(int fp, uint16_t block) { struct tftphdr *tp; int size; char buf[MAXPKTSIZE]; if (debug&DEBUG_PACKETS) tftp_log(LOG_DEBUG, "Sending ACK for block %d", block); DROPPACKETn("send_ack", 0); tp = (struct tftphdr *)buf; size = sizeof(buf) - 2; tp->th_opcode = htons((u_short)ACK); tp->th_block = htons((u_short)block); size = 4; if (sendto(fp, buf, size, 0, (struct sockaddr *)&peer_sock, peer_sock.ss_len) != size) { tftp_log(LOG_INFO, "send_ack: %s", strerror(errno)); return (1); } return (0); } /* * Send a DATA packet */ int send_data(int peer, uint16_t block, char *data, int size) { char buf[MAXPKTSIZE]; struct tftphdr *pkt; int n; if (debug&DEBUG_PACKETS) tftp_log(LOG_DEBUG, "Sending DATA packet %d of %d bytes", block, size); DROPPACKETn("send_data", 0); pkt = (struct tftphdr *)buf; pkt->th_opcode = htons((u_short)DATA); pkt->th_block = htons((u_short)block); memcpy(pkt->th_data, data, size); n = send_packet(peer, block, (char *)pkt, size + 4); return (n); } /* * Receive a packet */ static jmp_buf timeoutbuf; static void timeout(int sig __unused) { /* tftp_log(LOG_DEBUG, "Timeout\n"); Inside a signal handler... */ longjmp(timeoutbuf, 1); } int receive_packet(int peer, char *data, int size, struct sockaddr_storage *from, int thistimeout) { struct tftphdr *pkt; struct sockaddr_storage from_local; struct sockaddr_storage *pfrom; socklen_t fromlen; int n; static int timed_out; if (debug&DEBUG_PACKETS) tftp_log(LOG_DEBUG, "Waiting %d seconds for packet", timeoutpacket); pkt = (struct tftphdr *)data; signal(SIGALRM, timeout); timed_out = setjmp(timeoutbuf); alarm(thistimeout); if (timed_out != 0) { tftp_log(LOG_ERR, "receive_packet: timeout"); alarm(0); return (RP_TIMEOUT); } pfrom = (from == NULL) ? &from_local : from; fromlen = sizeof(*pfrom); n = recvfrom(peer, data, size, 0, (struct sockaddr *)pfrom, &fromlen); alarm(0); DROPPACKETn("receive_packet", RP_TIMEOUT); if (n < 0) { tftp_log(LOG_ERR, "receive_packet: timeout"); return (RP_TIMEOUT); } if (n < 0) { /* No idea what could have happened if it isn't a timeout */ tftp_log(LOG_ERR, "receive_packet: %s", strerror(errno)); return (RP_RECVFROM); } if (n < 4) { tftp_log(LOG_ERR, "receive_packet: packet too small (%d bytes)", n); return (RP_TOOSMALL); } pkt->th_opcode = ntohs((u_short)pkt->th_opcode); if (pkt->th_opcode == DATA || pkt->th_opcode == ACK) pkt->th_block = ntohs((u_short)pkt->th_block); if (pkt->th_opcode == DATA && n > pktsize) { tftp_log(LOG_ERR, "receive_packet: packet too big"); return (RP_TOOBIG); } if (((struct sockaddr_in *)(pfrom))->sin_addr.s_addr != ((struct sockaddr_in *)(&peer_sock))->sin_addr.s_addr) { tftp_log(LOG_ERR, "receive_packet: received packet from wrong source"); return (RP_WRONGSOURCE); } if (pkt->th_opcode == ERROR) { tftp_log(pkt->th_code == EUNDEF ? LOG_DEBUG : LOG_ERR, "Got ERROR packet: %s", pkt->th_msg); return (RP_ERROR); } if (debug&DEBUG_PACKETS) tftp_log(LOG_DEBUG, "Received %d bytes in a %s packet", n, packettype(pkt->th_opcode)); return n - 4; } diff --git a/libexec/tftpd/tftp-options.c b/libexec/tftpd/tftp-options.c index 10fb112654fd..a2dd1019f811 100644 --- a/libexec/tftpd/tftp-options.c +++ b/libexec/tftpd/tftp-options.c @@ -1,426 +1,426 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (C) 2008 Edwin Groothuis. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); -#include #include -#include +#include #include +#include #include #include #include #include #include #include #include #include "tftp-utils.h" #include "tftp-io.h" #include "tftp-options.h" /* * Option handlers */ struct options options[] = { { "tsize", NULL, NULL, NULL /* option_tsize */, 1 }, { "timeout", NULL, NULL, option_timeout, 1 }, { "blksize", NULL, NULL, option_blksize, 1 }, { "blksize2", NULL, NULL, option_blksize2, 0 }, { "rollover", NULL, NULL, option_rollover, 0 }, { "windowsize", NULL, NULL, option_windowsize, 1 }, { NULL, NULL, NULL, NULL, 0 } }; /* By default allow them */ int options_rfc_enabled = 1; int options_extra_enabled = 1; /* * Rules for the option handlers: * - If there is no o_request, there will be no processing. * * For servers * - Logging is done as warnings. * - The handler exit()s if there is a serious problem with the * values submitted in the option. * * For clients * - Logging is done as errors. After all, the server shouldn't * return rubbish. * - The handler returns if there is a serious problem with the * values submitted in the option. * - Sending the EBADOP packets is done by the handler. */ int option_tsize(int peer __unused, struct tftphdr *tp __unused, int mode, struct stat *stbuf) { if (options[OPT_TSIZE].o_request == NULL) return (0); if (mode == RRQ) asprintf(&options[OPT_TSIZE].o_reply, - "%ju", stbuf->st_size); + "%ju", (uintmax_t)stbuf->st_size); else /* XXX Allows writes of all sizes. */ options[OPT_TSIZE].o_reply = strdup(options[OPT_TSIZE].o_request); return (0); } int option_timeout(int peer) { int to; if (options[OPT_TIMEOUT].o_request == NULL) return (0); to = atoi(options[OPT_TIMEOUT].o_request); if (to < TIMEOUT_MIN || to > TIMEOUT_MAX) { tftp_log(acting_as_client ? LOG_ERR : LOG_WARNING, "Received bad value for timeout. " "Should be between %d and %d, received %d", TIMEOUT_MIN, TIMEOUT_MAX, to); send_error(peer, EBADOP); if (acting_as_client) return (1); exit(1); } else { timeoutpacket = to; options[OPT_TIMEOUT].o_reply = strdup(options[OPT_TIMEOUT].o_request); } settimeouts(timeoutpacket, timeoutnetwork, maxtimeouts); if (debug&DEBUG_OPTIONS) tftp_log(LOG_DEBUG, "Setting timeout to '%s'", options[OPT_TIMEOUT].o_reply); return (0); } int option_rollover(int peer) { if (options[OPT_ROLLOVER].o_request == NULL) return (0); if (strcmp(options[OPT_ROLLOVER].o_request, "0") != 0 && strcmp(options[OPT_ROLLOVER].o_request, "1") != 0) { tftp_log(acting_as_client ? LOG_ERR : LOG_WARNING, "Bad value for rollover, " "should be either 0 or 1, received '%s', " "ignoring request", options[OPT_ROLLOVER].o_request); if (acting_as_client) { send_error(peer, EBADOP); return (1); } return (0); } options[OPT_ROLLOVER].o_reply = strdup(options[OPT_ROLLOVER].o_request); if (debug&DEBUG_OPTIONS) tftp_log(LOG_DEBUG, "Setting rollover to '%s'", options[OPT_ROLLOVER].o_reply); return (0); } int option_blksize(int peer) { u_long maxdgram; size_t len; if (options[OPT_BLKSIZE].o_request == NULL) return (0); /* maximum size of an UDP packet according to the system */ len = sizeof(maxdgram); if (sysctlbyname("net.inet.udp.maxdgram", &maxdgram, &len, NULL, 0) < 0) { tftp_log(LOG_ERR, "sysctl: net.inet.udp.maxdgram"); return (acting_as_client ? 1 : 0); } int size = atoi(options[OPT_BLKSIZE].o_request); if (size < BLKSIZE_MIN || size > BLKSIZE_MAX) { if (acting_as_client) { tftp_log(LOG_ERR, "Invalid blocksize (%d bytes), aborting", size); send_error(peer, EBADOP); return (1); } else { tftp_log(LOG_WARNING, "Invalid blocksize (%d bytes), ignoring request", size); return (0); } } if (size > (int)maxdgram) { if (acting_as_client) { tftp_log(LOG_ERR, "Invalid blocksize (%d bytes), " "net.inet.udp.maxdgram sysctl limits it to " "%ld bytes.\n", size, maxdgram); send_error(peer, EBADOP); return (1); } else { tftp_log(LOG_WARNING, "Invalid blocksize (%d bytes), " "net.inet.udp.maxdgram sysctl limits it to " "%ld bytes.\n", size, maxdgram); size = maxdgram; /* No reason to return */ } } asprintf(&options[OPT_BLKSIZE].o_reply, "%d", size); segsize = size; pktsize = size + 4; if (debug&DEBUG_OPTIONS) tftp_log(LOG_DEBUG, "Setting blksize to '%s'", options[OPT_BLKSIZE].o_reply); return (0); } int option_blksize2(int peer __unused) { u_long maxdgram; int size, i; size_t len; int sizes[] = { 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 0 }; if (options[OPT_BLKSIZE2].o_request == NULL) return (0); /* maximum size of an UDP packet according to the system */ len = sizeof(maxdgram); if (sysctlbyname("net.inet.udp.maxdgram", &maxdgram, &len, NULL, 0) < 0) { tftp_log(LOG_ERR, "sysctl: net.inet.udp.maxdgram"); return (acting_as_client ? 1 : 0); } size = atoi(options[OPT_BLKSIZE2].o_request); for (i = 0; sizes[i] != 0; i++) { if (size == sizes[i]) break; } if (sizes[i] == 0) { tftp_log(LOG_INFO, "Invalid blocksize2 (%d bytes), ignoring request", size); return (acting_as_client ? 1 : 0); } if (size > (int)maxdgram) { for (i = 0; sizes[i+1] != 0; i++) { if ((int)maxdgram < sizes[i+1]) break; } tftp_log(LOG_INFO, "Invalid blocksize2 (%d bytes), net.inet.udp.maxdgram " "sysctl limits it to %ld bytes.\n", size, maxdgram); size = sizes[i]; /* No need to return */ } asprintf(&options[OPT_BLKSIZE2].o_reply, "%d", size); segsize = size; pktsize = size + 4; if (debug&DEBUG_OPTIONS) tftp_log(LOG_DEBUG, "Setting blksize2 to '%s'", options[OPT_BLKSIZE2].o_reply); return (0); } int option_windowsize(int peer) { int size; if (options[OPT_WINDOWSIZE].o_request == NULL) return (0); size = atoi(options[OPT_WINDOWSIZE].o_request); if (size < WINDOWSIZE_MIN || size > WINDOWSIZE_MAX) { if (acting_as_client) { tftp_log(LOG_ERR, "Invalid windowsize (%d blocks), aborting", size); send_error(peer, EBADOP); return (1); } else { tftp_log(LOG_WARNING, "Invalid windowsize (%d blocks), ignoring request", size); return (0); } } /* XXX: Should force a windowsize of 1 for non-seekable files. */ asprintf(&options[OPT_WINDOWSIZE].o_reply, "%d", size); windowsize = size; if (debug&DEBUG_OPTIONS) tftp_log(LOG_DEBUG, "Setting windowsize to '%s'", options[OPT_WINDOWSIZE].o_reply); return (0); } /* * Append the available options to the header */ uint16_t make_options(int peer __unused, char *buffer, uint16_t size) { int i; char *value; const char *option; uint16_t length; uint16_t returnsize = 0; if (!options_rfc_enabled) return (0); for (i = 0; options[i].o_type != NULL; i++) { if (options[i].rfc == 0 && !options_extra_enabled) continue; option = options[i].o_type; if (acting_as_client) value = options[i].o_request; else value = options[i].o_reply; if (value == NULL) continue; length = strlen(value) + strlen(option) + 2; if (size <= length) { tftp_log(LOG_ERR, "Running out of option space for " "option '%s' with value '%s': " "needed %d bytes, got %d bytes", option, value, size, length); continue; } sprintf(buffer, "%s%c%s%c", option, '\000', value, '\000'); size -= length; buffer += length; returnsize += length; } return (returnsize); } /* * Parse the received options in the header */ int parse_options(int peer, char *buffer, uint16_t size) { int i, options_failed; char *c, *cp, *option, *value; if (!options_rfc_enabled) return (0); /* Parse the options */ cp = buffer; options_failed = 0; while (size > 0) { option = cp; i = get_field(peer, cp, size); cp += i; value = cp; i = get_field(peer, cp, size); cp += i; /* We are at the end */ if (*option == '\0') break; if (debug&DEBUG_OPTIONS) tftp_log(LOG_DEBUG, "option: '%s' value: '%s'", option, value); for (c = option; *c; c++) if (isupper(*c)) *c = tolower(*c); for (i = 0; options[i].o_type != NULL; i++) { if (strcmp(option, options[i].o_type) == 0) { if (!acting_as_client) options[i].o_request = value; if (!options_extra_enabled && !options[i].rfc) { tftp_log(LOG_INFO, "Option '%s' with value '%s' found " "but it is not an RFC option", option, value); continue; } if (options[i].o_handler) options_failed += (options[i].o_handler)(peer); break; } } if (options[i].o_type == NULL) tftp_log(LOG_WARNING, "Unknown option: '%s'", option); size -= strlen(option) + strlen(value) + 2; } return (options_failed); } /* * Set some default values in the options */ void init_options(void) { options[OPT_ROLLOVER].o_request = strdup("0"); } diff --git a/libexec/tftpd/tftp-transfer.c b/libexec/tftpd/tftp-transfer.c index 0a0c099e4c33..5c037bc38f86 100644 --- a/libexec/tftpd/tftp-transfer.c +++ b/libexec/tftpd/tftp-transfer.c @@ -1,451 +1,451 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (C) 2008 Edwin Groothuis. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); -#include #include #include -#include #include +#include +#include #include #include #include #include #include #include #include #include "tftp-file.h" #include "tftp-io.h" #include "tftp-utils.h" #include "tftp-options.h" #include "tftp-transfer.h" struct block_data { off_t offset; uint16_t block; int size; }; /* * Send a file via the TFTP data session. */ void tftp_send(int peer, uint16_t *block, struct tftp_stats *ts) { struct tftphdr *rp; int size, n_data, n_ack, sendtry, acktry; u_int i, j; uint16_t oldblock, windowblock; char sendbuffer[MAXPKTSIZE]; char recvbuffer[MAXPKTSIZE]; struct block_data window[WINDOWSIZE_MAX]; rp = (struct tftphdr *)recvbuffer; *block = 1; ts->amount = 0; windowblock = 0; acktry = 0; do { read_block: if (debug&DEBUG_SIMPLE) tftp_log(LOG_DEBUG, "Sending block %d (window block %d)", *block, windowblock); window[windowblock].offset = tell_file(); window[windowblock].block = *block; size = read_file(sendbuffer, segsize); if (size < 0) { tftp_log(LOG_ERR, "read_file returned %d", size); send_error(peer, errno + 100); goto abort; } window[windowblock].size = size; windowblock++; for (sendtry = 0; ; sendtry++) { n_data = send_data(peer, *block, sendbuffer, size); if (n_data == 0) break; if (sendtry == maxtimeouts) { tftp_log(LOG_ERR, "Cannot send DATA packet #%d, " "giving up", *block); return; } tftp_log(LOG_ERR, "Cannot send DATA packet #%d, trying again", *block); } /* Only check for ACK for last block in window. */ if (windowblock == windowsize || size != segsize) { n_ack = receive_packet(peer, recvbuffer, MAXPKTSIZE, NULL, timeoutpacket); if (n_ack < 0) { if (n_ack == RP_TIMEOUT) { if (acktry == maxtimeouts) { tftp_log(LOG_ERR, "Timeout #%d send ACK %d " "giving up", acktry, *block); return; } tftp_log(LOG_WARNING, "Timeout #%d on ACK %d", acktry, *block); acktry++; ts->retries++; if (seek_file(window[0].offset) != 0) { tftp_log(LOG_ERR, "seek_file failed: %s", strerror(errno)); send_error(peer, errno + 100); goto abort; } *block = window[0].block; windowblock = 0; goto read_block; } /* Either read failure or ERROR packet */ if (debug&DEBUG_SIMPLE) tftp_log(LOG_ERR, "Aborting: %s", rp_strerror(n_ack)); goto abort; } if (rp->th_opcode == ACK) { /* * Look for the ACKed block in our open * window. */ for (i = 0; i < windowblock; i++) { if (rp->th_block == window[i].block) break; } if (i == windowblock) { /* Did not recognize ACK. */ if (debug&DEBUG_SIMPLE) tftp_log(LOG_DEBUG, "ACK %d out of window", rp->th_block); /* Re-synchronize with the other side */ (void) synchnet(peer); /* Resend the current window. */ ts->retries++; if (seek_file(window[0].offset) != 0) { tftp_log(LOG_ERR, "seek_file failed: %s", strerror(errno)); send_error(peer, errno + 100); goto abort; } *block = window[0].block; windowblock = 0; goto read_block; } /* ACKed at least some data. */ acktry = 0; for (j = 0; j <= i; j++) { if (debug&DEBUG_SIMPLE) tftp_log(LOG_DEBUG, "ACKed block %d", window[j].block); ts->blocks++; ts->amount += window[j].size; } /* * Partial ACK. Rewind state to first * un-ACKed block. */ if (i + 1 != windowblock) { if (debug&DEBUG_SIMPLE) tftp_log(LOG_DEBUG, "Partial ACK"); if (seek_file(window[i + 1].offset) != 0) { tftp_log(LOG_ERR, "seek_file failed: %s", strerror(errno)); send_error(peer, errno + 100); goto abort; } *block = window[i + 1].block; windowblock = 0; ts->retries++; goto read_block; } windowblock = 0; } } oldblock = *block; (*block)++; if (oldblock > *block) { if (options[OPT_ROLLOVER].o_request == NULL) { /* * "rollover" option not specified in * tftp client. Default to rolling block * counter to 0. */ *block = 0; } else { *block = atoi(options[OPT_ROLLOVER].o_request); } ts->rollovers++; } gettimeofday(&(ts->tstop), NULL); } while (size == segsize); abort: return; } /* * Receive a file via the TFTP data session. * * - It could be that the first block has already arrived while * trying to figure out if we were receiving options or not. In * that case it is passed to this function. */ void tftp_receive(int peer, uint16_t *block, struct tftp_stats *ts, struct tftphdr *firstblock, size_t fb_size) { struct tftphdr *rp; uint16_t oldblock, windowstart; int n_data, n_ack, writesize, i, retry, windowblock; char recvbuffer[MAXPKTSIZE]; ts->amount = 0; windowblock = 0; if (firstblock != NULL) { writesize = write_file(firstblock->th_data, fb_size); ts->amount += writesize; ts->blocks++; windowblock++; if (windowsize == 1 || fb_size != segsize) { for (i = 0; ; i++) { n_ack = send_ack(peer, *block); if (n_ack > 0) { if (i == maxtimeouts) { tftp_log(LOG_ERR, "Cannot send ACK packet #%d, " "giving up", *block); return; } tftp_log(LOG_ERR, "Cannot send ACK packet #%d, trying again", *block); continue; } break; } } if (fb_size != segsize) { write_close(); gettimeofday(&(ts->tstop), NULL); return; } } rp = (struct tftphdr *)recvbuffer; do { oldblock = *block; (*block)++; if (oldblock > *block) { if (options[OPT_ROLLOVER].o_request == NULL) { /* * "rollover" option not specified in * tftp client. Default to rolling block * counter to 0. */ *block = 0; } else { *block = atoi(options[OPT_ROLLOVER].o_request); } ts->rollovers++; } for (retry = 0; ; retry++) { if (debug&DEBUG_SIMPLE) tftp_log(LOG_DEBUG, "Receiving DATA block %d (window block %d)", *block, windowblock); n_data = receive_packet(peer, recvbuffer, MAXPKTSIZE, NULL, timeoutpacket); if (n_data < 0) { if (retry == maxtimeouts) { tftp_log(LOG_ERR, "Timeout #%d on DATA block %d, " "giving up", retry, *block); return; } if (n_data == RP_TIMEOUT) { tftp_log(LOG_WARNING, "Timeout #%d on DATA block %d", retry, *block); send_ack(peer, oldblock); windowblock = 0; continue; } /* Either read failure or ERROR packet */ if (debug&DEBUG_SIMPLE) tftp_log(LOG_DEBUG, "Aborting: %s", rp_strerror(n_data)); goto abort; } if (rp->th_opcode == DATA) { ts->blocks++; if (rp->th_block == *block) break; /* * Ignore duplicate blocks within the * window. * * This does not handle duplicate * blocks during a rollover as * gracefully, but that should still * recover eventually. */ if (*block > windowsize) windowstart = *block - windowsize; else windowstart = 0; if (rp->th_block > windowstart && rp->th_block < *block) { if (debug&DEBUG_SIMPLE) tftp_log(LOG_DEBUG, "Ignoring duplicate DATA block %d", rp->th_block); windowblock++; retry = 0; continue; } tftp_log(LOG_WARNING, "Expected DATA block %d, got block %d", *block, rp->th_block); /* Re-synchronize with the other side */ (void) synchnet(peer); tftp_log(LOG_INFO, "Trying to sync"); *block = oldblock; ts->retries++; goto send_ack; /* rexmit */ } else { tftp_log(LOG_WARNING, "Expected DATA block, got %s block", packettype(rp->th_opcode)); } } if (n_data > 0) { writesize = write_file(rp->th_data, n_data); ts->amount += writesize; if (writesize <= 0) { tftp_log(LOG_ERR, "write_file returned %d", writesize); if (writesize < 0) send_error(peer, errno + 100); else send_error(peer, ENOSPACE); goto abort; } } if (n_data != segsize) write_close(); windowblock++; /* Only send ACKs for the last block in the window. */ if (windowblock < windowsize && n_data == segsize) continue; send_ack: for (i = 0; ; i++) { n_ack = send_ack(peer, *block); if (n_ack > 0) { if (i == maxtimeouts) { tftp_log(LOG_ERR, "Cannot send ACK packet #%d, " "giving up", *block); return; } tftp_log(LOG_ERR, "Cannot send ACK packet #%d, trying again", *block); continue; } if (debug&DEBUG_SIMPLE) tftp_log(LOG_DEBUG, "Sent ACK for %d", *block); windowblock = 0; break; } gettimeofday(&(ts->tstop), NULL); } while (n_data == segsize); /* Don't do late packet management for the client implementation */ if (acting_as_client) return; for (i = 0; ; i++) { n_data = receive_packet(peer, (char *)rp, pktsize, NULL, timeoutpacket); if (n_data <= 0) break; if (n_data > 0 && rp->th_opcode == DATA && /* and got a data block */ *block == rp->th_block) /* then my last ack was lost */ send_ack(peer, *block); /* resend final ack */ } abort: return; } diff --git a/libexec/tftpd/tftp-utils.c b/libexec/tftpd/tftp-utils.c index d706639d51e8..24ce505ab812 100644 --- a/libexec/tftpd/tftp-utils.c +++ b/libexec/tftpd/tftp-utils.c @@ -1,325 +1,326 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (C) 2008 Edwin Groothuis. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include +#include #include #include #include #include #include #include #include #include "tftp-utils.h" #include "tftp-io.h" /* * Default values, can be changed later via the TFTP Options */ int timeoutpacket = TIMEOUT; int timeoutnetwork = MAX_TIMEOUTS * TIMEOUT; int maxtimeouts = MAX_TIMEOUTS; uint16_t segsize = SEGSIZE; uint16_t pktsize = SEGSIZE + 4; uint16_t windowsize = WINDOWSIZE; int acting_as_client; /* * Set timeout values for packet reception. The idea is that you * get 'maxtimeouts' of 5 seconds between 'timeoutpacket' (i.e. the * first timeout) to 'timeoutnetwork' (i.e. the last timeout) */ int settimeouts(int _timeoutpacket, int _timeoutnetwork, int _maxtimeouts __unused) { int i; /* We cannot do impossible things */ if (_timeoutpacket >= _timeoutnetwork) return (0); maxtimeouts = 0; i = _timeoutpacket; while (i < _timeoutnetwork || maxtimeouts < MIN_TIMEOUTS) { maxtimeouts++; i += 5; } timeoutpacket = _timeoutpacket; timeoutnetwork = i; return (1); } /* translate IPv4 mapped IPv6 address to IPv4 address */ void unmappedaddr(struct sockaddr_in6 *sin6) { struct sockaddr_in *sin4; u_int32_t addr; int port; if (sin6->sin6_family != AF_INET6 || !IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) return; sin4 = (struct sockaddr_in *)sin6; memcpy(&addr, &sin6->sin6_addr.s6_addr[12], sizeof(addr)); port = sin6->sin6_port; memset(sin4, 0, sizeof(struct sockaddr_in)); sin4->sin_addr.s_addr = addr; sin4->sin_port = port; sin4->sin_family = AF_INET; sin4->sin_len = sizeof(struct sockaddr_in); } /* Get a field from a \0 separated string */ ssize_t get_field(int peer, char *buffer, ssize_t size) { char *cp = buffer; while (cp < buffer + size) { if (*cp == '\0') break; cp++; } if (*cp != '\0') { tftp_log(LOG_ERR, "Bad option - no trailing \\0 found"); send_error(peer, EBADOP); exit(1); } return (cp - buffer + 1); } /* * Logging functions */ static int _tftp_logtostdout = 1; void tftp_openlog(const char *ident, int logopt, int facility) { _tftp_logtostdout = (ident == NULL); if (_tftp_logtostdout == 0) openlog(ident, logopt, facility); } void tftp_closelog(void) { if (_tftp_logtostdout == 0) closelog(); } void tftp_log(int priority, const char *message, ...) { va_list ap; char *s; va_start(ap, message); if (_tftp_logtostdout == 0) { vasprintf(&s, message, ap); syslog(priority, "%s", s); } else { vprintf(message, ap); printf("\n"); } va_end(ap); } /* * Packet types */ struct packettypes packettypes[] = { { RRQ, "RRQ" }, { WRQ, "WRQ" }, { DATA, "DATA" }, { ACK, "ACK" }, { ERROR, "ERROR" }, { OACK, "OACK" }, { 0, NULL }, }; const char * packettype(int type) { static char failed[100]; int i = 0; while (packettypes[i].name != NULL) { if (packettypes[i].value == type) break; i++; } if (packettypes[i].name != NULL) return packettypes[i].name; sprintf(failed, "unknown (type: %d)", type); return (failed); } /* * Debugs */ int debug = DEBUG_NONE; struct debugs debugs[] = { { DEBUG_PACKETS, "packet", "Packet debugging" }, { DEBUG_SIMPLE, "simple", "Simple debugging" }, { DEBUG_OPTIONS, "options", "Options debugging" }, { DEBUG_ACCESS, "access", "TCPd access debugging" }, { DEBUG_NONE, NULL, "No debugging" }, }; int packetdroppercentage = 0; int debug_find(char *s) { int i = 0; while (debugs[i].name != NULL) { if (strcasecmp(debugs[i].name, s) == 0) break; i++; } return (debugs[i].value); } int debug_finds(char *s) { int i = 0; char *ps = s; while (s != NULL) { ps = strchr(s, ' '); if (ps != NULL) *ps = '\0'; i += debug_find(s); if (ps != NULL) *ps = ' '; s = ps; } return (i); } const char * debug_show(int d) { static char s[100]; size_t space = sizeof(s); int i = 0; s[0] = '\0'; while (debugs[i].name != NULL) { if (d&debugs[i].value) { if (s[0] != '\0') strlcat(s, " ", space); strlcat(s, debugs[i].name, space); } i++; } if (s[0] != '\0') return (s); return ("none"); } /* * RP_ */ struct rp_errors rp_errors[] = { { RP_TIMEOUT, "Network timeout" }, { RP_TOOSMALL, "Not enough data bytes" }, { RP_WRONGSOURCE, "Invalid IP address of UDP port" }, { RP_ERROR, "Error packet" }, { RP_RECVFROM, "recvfrom() complained" }, { RP_TOOBIG, "Too many data bytes" }, { RP_NONE, NULL } }; char * rp_strerror(int error) { static char s[100]; size_t space = sizeof(s); int i = 0; while (rp_errors[i].desc != NULL) { if (rp_errors[i].error == error) { strlcpy(s, rp_errors[i].desc, space); space -= strlen(rp_errors[i].desc); } i++; } if (s[0] == '\0') sprintf(s, "unknown (error=%d)", error); return (s); } /* * Performance figures */ void stats_init(struct tftp_stats *ts) { ts->amount = 0; ts->rollovers = 0; ts->retries = 0; ts->blocks = 0; ts->amount = 0; gettimeofday(&(ts->tstart), NULL); } void printstats(const char *direction, int verbose, struct tftp_stats *ts) { double delta; /* compute delta in 1/10's second units */ delta = ((ts->tstop.tv_sec*10.)+(ts->tstop.tv_usec/100000)) - ((ts->tstart.tv_sec*10.)+(ts->tstart.tv_usec/100000)); delta = delta/10.; /* back to seconds */ printf("%s %zu bytes during %.1f seconds in %u blocks", direction, ts->amount, delta, ts->blocks); if (ts->rollovers != 0) printf(" with %d rollover%s", ts->rollovers, ts->rollovers != 1 ? "s" : ""); if (verbose) printf(" [%.0f bits/sec]", (ts->amount*8.)/delta); putchar('\n'); } diff --git a/libexec/tftpd/tftpd.c b/libexec/tftpd/tftpd.c index 02fbaaf0d371..02df60c87734 100644 --- a/libexec/tftpd/tftpd.c +++ b/libexec/tftpd/tftpd.c @@ -1,860 +1,862 @@ /*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright (c) 1983, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef lint static const char copyright[] = "@(#) Copyright (c) 1983, 1993\n\ The Regents of the University of California. All rights reserved.\n"; #endif /* not lint */ #ifndef lint #if 0 static char sccsid[] = "@(#)tftpd.c 8.1 (Berkeley) 6/4/93"; #endif #endif /* not lint */ #include __FBSDID("$FreeBSD$"); /* * Trivial file transfer protocol server. * * This version includes many modifications by Jim Guyton * . */ #include #include -#include #include +#include +#include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include #include "tftp-file.h" #include "tftp-io.h" #include "tftp-utils.h" #include "tftp-transfer.h" #include "tftp-options.h" #ifdef LIBWRAP #include #endif static void tftp_wrq(int peer, char *, ssize_t); static void tftp_rrq(int peer, char *, ssize_t); /* * Null-terminated directory prefix list for absolute pathname requests and * search list for relative pathname requests. * * MAXDIRS should be at least as large as the number of arguments that * inetd allows (currently 20). */ #define MAXDIRS 20 static struct dirlist { const char *name; int len; } dirs[MAXDIRS+1]; static int suppress_naks; static int logging; static int ipchroot; static int create_new = 0; static const char *newfile_format = "%Y%m%d"; static int increase_name = 0; static mode_t mask = S_IWGRP | S_IWOTH; struct formats; static void tftp_recvfile(int peer, const char *mode); static void tftp_xmitfile(int peer, const char *mode); static int validate_access(int peer, char **, int); static char peername[NI_MAXHOST]; static FILE *file; static struct formats { const char *f_mode; int f_convert; } formats[] = { { "netascii", 1 }, { "octet", 0 }, { NULL, 0 } }; int main(int argc, char *argv[]) { struct tftphdr *tp; int peer; socklen_t peerlen, len; ssize_t n; int ch; char *chroot_dir = NULL; struct passwd *nobody; const char *chuser = "nobody"; char recvbuffer[MAXPKTSIZE]; int allow_ro = 1, allow_wo = 1; tzset(); /* syslog in localtime */ acting_as_client = 0; tftp_openlog("tftpd", LOG_PID | LOG_NDELAY, LOG_FTP); while ((ch = getopt(argc, argv, "cCd:F:lnoOp:s:u:U:wW")) != -1) { switch (ch) { case 'c': ipchroot = 1; break; case 'C': ipchroot = 2; break; case 'd': if (atoi(optarg) != 0) debug += atoi(optarg); else debug |= debug_finds(optarg); break; case 'F': newfile_format = optarg; break; case 'l': logging = 1; break; case 'n': suppress_naks = 1; break; case 'o': options_rfc_enabled = 0; break; case 'O': options_extra_enabled = 0; break; case 'p': packetdroppercentage = atoi(optarg); tftp_log(LOG_INFO, "Randomly dropping %d out of 100 packets", packetdroppercentage); break; case 's': chroot_dir = optarg; break; case 'u': chuser = optarg; break; case 'U': mask = strtol(optarg, NULL, 0); break; case 'w': create_new = 1; break; case 'W': create_new = 1; increase_name = 1; break; default: tftp_log(LOG_WARNING, "ignoring unknown option -%c", ch); } } if (optind < argc) { struct dirlist *dirp; /* Get list of directory prefixes. Skip relative pathnames. */ for (dirp = dirs; optind < argc && dirp < &dirs[MAXDIRS]; optind++) { if (argv[optind][0] == '/') { dirp->name = argv[optind]; dirp->len = strlen(dirp->name); dirp++; } } } else if (chroot_dir) { dirs->name = "/"; dirs->len = 1; } if (ipchroot > 0 && chroot_dir == NULL) { tftp_log(LOG_ERR, "-c requires -s"); exit(1); } umask(mask); { int on = 1; if (ioctl(0, FIONBIO, &on) < 0) { tftp_log(LOG_ERR, "ioctl(FIONBIO): %s", strerror(errno)); exit(1); } } /* Find out who we are talking to and what we are going to do */ peerlen = sizeof(peer_sock); n = recvfrom(0, recvbuffer, MAXPKTSIZE, 0, (struct sockaddr *)&peer_sock, &peerlen); if (n < 0) { tftp_log(LOG_ERR, "recvfrom: %s", strerror(errno)); exit(1); } getnameinfo((struct sockaddr *)&peer_sock, peer_sock.ss_len, peername, sizeof(peername), NULL, 0, NI_NUMERICHOST); /* * Now that we have read the message out of the UDP * socket, we fork and exit. Thus, inetd will go back * to listening to the tftp port, and the next request * to come in will start up a new instance of tftpd. * * We do this so that inetd can run tftpd in "wait" mode. * The problem with tftpd running in "nowait" mode is that * inetd may get one or more successful "selects" on the * tftp port before we do our receive, so more than one * instance of tftpd may be started up. Worse, if tftpd * break before doing the above "recvfrom", inetd would * spawn endless instances, clogging the system. */ { int i, pid; for (i = 1; i < 20; i++) { pid = fork(); if (pid < 0) { sleep(i); /* * flush out to most recently sent request. * * This may drop some request, but those * will be resent by the clients when * they timeout. The positive effect of * this flush is to (try to) prevent more * than one tftpd being started up to service * a single request from a single client. */ peerlen = sizeof peer_sock; i = recvfrom(0, recvbuffer, MAXPKTSIZE, 0, (struct sockaddr *)&peer_sock, &peerlen); if (i > 0) { n = i; } } else { break; } } if (pid < 0) { tftp_log(LOG_ERR, "fork: %s", strerror(errno)); exit(1); } else if (pid != 0) { exit(0); } } #ifdef LIBWRAP /* * See if the client is allowed to talk to me. * (This needs to be done before the chroot()) */ { struct request_info req; request_init(&req, RQ_CLIENT_ADDR, peername, 0); request_set(&req, RQ_DAEMON, "tftpd", 0); if (hosts_access(&req) == 0) { if (debug&DEBUG_ACCESS) tftp_log(LOG_WARNING, "Access denied by 'tftpd' entry " "in /etc/hosts.allow"); /* * Full access might be disabled, but maybe the * client is allowed to do read-only access. */ request_set(&req, RQ_DAEMON, "tftpd-ro", 0); allow_ro = hosts_access(&req); request_set(&req, RQ_DAEMON, "tftpd-wo", 0); allow_wo = hosts_access(&req); if (allow_ro == 0 && allow_wo == 0) { tftp_log(LOG_WARNING, "Unauthorized access from %s", peername); exit(1); } if (debug&DEBUG_ACCESS) { if (allow_ro) tftp_log(LOG_WARNING, "But allowed readonly access " "via 'tftpd-ro' entry"); if (allow_wo) tftp_log(LOG_WARNING, "But allowed writeonly access " "via 'tftpd-wo' entry"); } } else if (debug&DEBUG_ACCESS) tftp_log(LOG_WARNING, "Full access allowed" "in /etc/hosts.allow"); } #endif /* * Since we exit here, we should do that only after the above * recvfrom to keep inetd from constantly forking should there * be a problem. See the above comment about system clogging. */ if (chroot_dir) { if (ipchroot > 0) { char *tempchroot; struct stat sb; int statret; struct sockaddr_storage ss; char hbuf[NI_MAXHOST]; statret = -1; memcpy(&ss, &peer_sock, peer_sock.ss_len); unmappedaddr((struct sockaddr_in6 *)&ss); getnameinfo((struct sockaddr *)&ss, ss.ss_len, hbuf, sizeof(hbuf), NULL, 0, NI_NUMERICHOST); asprintf(&tempchroot, "%s/%s", chroot_dir, hbuf); if (ipchroot == 2) statret = stat(tempchroot, &sb); if (ipchroot == 1 || (statret == 0 && (sb.st_mode & S_IFDIR))) chroot_dir = tempchroot; } /* Must get this before chroot because /etc might go away */ if ((nobody = getpwnam(chuser)) == NULL) { tftp_log(LOG_ERR, "%s: no such user", chuser); exit(1); } if (chroot(chroot_dir)) { tftp_log(LOG_ERR, "chroot: %s: %s", chroot_dir, strerror(errno)); exit(1); } if (chdir("/") != 0) { tftp_log(LOG_ERR, "chdir: %s", strerror(errno)); exit(1); } if (setgroups(1, &nobody->pw_gid) != 0) { tftp_log(LOG_ERR, "setgroups failed"); exit(1); } if (setuid(nobody->pw_uid) != 0) { tftp_log(LOG_ERR, "setuid failed"); exit(1); } } len = sizeof(me_sock); if (getsockname(0, (struct sockaddr *)&me_sock, &len) == 0) { switch (me_sock.ss_family) { case AF_INET: ((struct sockaddr_in *)&me_sock)->sin_port = 0; break; case AF_INET6: ((struct sockaddr_in6 *)&me_sock)->sin6_port = 0; break; default: /* unsupported */ break; } } else { memset(&me_sock, 0, sizeof(me_sock)); me_sock.ss_family = peer_sock.ss_family; me_sock.ss_len = peer_sock.ss_len; } close(0); close(1); peer = socket(peer_sock.ss_family, SOCK_DGRAM, 0); if (peer < 0) { tftp_log(LOG_ERR, "socket: %s", strerror(errno)); exit(1); } if (bind(peer, (struct sockaddr *)&me_sock, me_sock.ss_len) < 0) { tftp_log(LOG_ERR, "bind: %s", strerror(errno)); exit(1); } tp = (struct tftphdr *)recvbuffer; tp->th_opcode = ntohs(tp->th_opcode); if (tp->th_opcode == RRQ) { if (allow_ro) tftp_rrq(peer, tp->th_stuff, n - 1); else { tftp_log(LOG_WARNING, "%s read access denied", peername); exit(1); } } else if (tp->th_opcode == WRQ) { if (allow_wo) tftp_wrq(peer, tp->th_stuff, n - 1); else { tftp_log(LOG_WARNING, "%s write access denied", peername); exit(1); } } else send_error(peer, EBADOP); exit(1); } static void reduce_path(char *fn) { char *slash, *ptr; /* Reduce all "/+./" to "/" (just in case we've got "/./../" later */ while ((slash = strstr(fn, "/./")) != NULL) { for (ptr = slash; ptr > fn && ptr[-1] == '/'; ptr--) ; slash += 2; while (*slash) *++ptr = *++slash; } /* Now reduce all "/something/+../" to "/" */ while ((slash = strstr(fn, "/../")) != NULL) { if (slash == fn) break; for (ptr = slash; ptr > fn && ptr[-1] == '/'; ptr--) ; for (ptr--; ptr >= fn; ptr--) if (*ptr == '/') break; if (ptr < fn) break; slash += 3; while (*slash) *++ptr = *++slash; } } static char * parse_header(int peer, char *recvbuffer, ssize_t size, char **filename, char **mode) { char *cp; int i; struct formats *pf; *mode = NULL; cp = recvbuffer; i = get_field(peer, recvbuffer, size); if (i >= PATH_MAX) { tftp_log(LOG_ERR, "Bad option - filename too long"); send_error(peer, EBADOP); exit(1); } *filename = recvbuffer; tftp_log(LOG_INFO, "Filename: '%s'", *filename); cp += i; i = get_field(peer, cp, size); *mode = cp; cp += i; /* Find the file transfer mode */ for (cp = *mode; *cp; cp++) if (isupper(*cp)) *cp = tolower(*cp); for (pf = formats; pf->f_mode; pf++) if (strcmp(pf->f_mode, *mode) == 0) break; if (pf->f_mode == NULL) { tftp_log(LOG_ERR, "Bad option - Unknown transfer mode (%s)", *mode); send_error(peer, EBADOP); exit(1); } tftp_log(LOG_INFO, "Mode: '%s'", *mode); return (cp + 1); } /* * WRQ - receive a file from the client */ void tftp_wrq(int peer, char *recvbuffer, ssize_t size) { char *cp; int has_options = 0, ecode; char *filename, *mode; char fnbuf[PATH_MAX]; cp = parse_header(peer, recvbuffer, size, &filename, &mode); size -= (cp - recvbuffer) + 1; strlcpy(fnbuf, filename, sizeof(fnbuf)); reduce_path(fnbuf); filename = fnbuf; if (size > 0) { if (options_rfc_enabled) has_options = !parse_options(peer, cp, size); else tftp_log(LOG_INFO, "Options found but not enabled"); } ecode = validate_access(peer, &filename, WRQ); if (ecode == 0) { if (has_options) send_oack(peer); else send_ack(peer, 0); } if (logging) { tftp_log(LOG_INFO, "%s: write request for %s: %s", peername, filename, errtomsg(ecode)); } if (ecode) { send_error(peer, ecode); exit(1); } tftp_recvfile(peer, mode); exit(0); } /* * RRQ - send a file to the client */ void tftp_rrq(int peer, char *recvbuffer, ssize_t size) { char *cp; int has_options = 0, ecode; char *filename, *mode; char fnbuf[PATH_MAX]; cp = parse_header(peer, recvbuffer, size, &filename, &mode); size -= (cp - recvbuffer) + 1; strlcpy(fnbuf, filename, sizeof(fnbuf)); reduce_path(fnbuf); filename = fnbuf; if (size > 0) { if (options_rfc_enabled) has_options = !parse_options(peer, cp, size); else tftp_log(LOG_INFO, "Options found but not enabled"); } ecode = validate_access(peer, &filename, RRQ); if (ecode == 0) { if (has_options) { int n; char lrecvbuffer[MAXPKTSIZE]; struct tftphdr *rp = (struct tftphdr *)lrecvbuffer; send_oack(peer); n = receive_packet(peer, lrecvbuffer, MAXPKTSIZE, NULL, timeoutpacket); if (n < 0) { if (debug&DEBUG_SIMPLE) tftp_log(LOG_DEBUG, "Aborting: %s", rp_strerror(n)); return; } if (rp->th_opcode != ACK) { if (debug&DEBUG_SIMPLE) tftp_log(LOG_DEBUG, "Expected ACK, got %s on OACK", packettype(rp->th_opcode)); return; } } } if (logging) tftp_log(LOG_INFO, "%s: read request for %s: %s", peername, filename, errtomsg(ecode)); if (ecode) { /* * Avoid storms of naks to a RRQ broadcast for a relative * bootfile pathname from a diskless Sun. */ if (suppress_naks && *filename != '/' && ecode == ENOTFOUND) exit(0); send_error(peer, ecode); exit(1); } tftp_xmitfile(peer, mode); } /* * Find the next value for YYYYMMDD.nn when the file to be written should * be unique. Due to the limitations of nn, we will fail if nn reaches 100. * Besides, that is four updates per hour on a file, which is kind of * execessive anyway. */ static int find_next_name(char *filename, int *fd) { int i; time_t tval; size_t len; struct tm lt; char yyyymmdd[MAXPATHLEN]; char newname[MAXPATHLEN]; /* Create the YYYYMMDD part of the filename */ time(&tval); lt = *localtime(&tval); len = strftime(yyyymmdd, sizeof(yyyymmdd), newfile_format, <); if (len == 0) { syslog(LOG_WARNING, "Filename suffix too long (%d characters maximum)", MAXPATHLEN); return (EACCESS); } /* Make sure the new filename is not too long */ if (strlen(filename) > MAXPATHLEN - len - 5) { syslog(LOG_WARNING, "Filename too long (%zd characters, %zd maximum)", strlen(filename), MAXPATHLEN - len - 5); return (EACCESS); } /* Find the first file which doesn't exist */ for (i = 0; i < 100; i++) { sprintf(newname, "%s.%s.%02d", filename, yyyymmdd, i); *fd = open(newname, O_WRONLY | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); if (*fd > 0) return 0; } return (EEXIST); } /* * Validate file access. Since we * have no uid or gid, for now require * file to exist and be publicly * readable/writable. * If we were invoked with arguments * from inetd then the file must also be * in one of the given directory prefixes. * Note also, full path name must be * given as we have no login directory. */ int validate_access(int peer, char **filep, int mode) { struct stat stbuf; int fd; int error; struct dirlist *dirp; static char pathname[MAXPATHLEN]; char *filename = *filep; /* * Prevent tricksters from getting around the directory restrictions */ if (strstr(filename, "/../")) return (EACCESS); if (*filename == '/') { /* * Allow the request if it's in one of the approved locations. * Special case: check the null prefix ("/") by looking * for length = 1 and relying on the arg. processing that * it's a /. */ for (dirp = dirs; dirp->name != NULL; dirp++) { if (dirp->len == 1 || (!strncmp(filename, dirp->name, dirp->len) && filename[dirp->len] == '/')) break; } /* If directory list is empty, allow access to any file */ if (dirp->name == NULL && dirp != dirs) return (EACCESS); if (stat(filename, &stbuf) < 0) return (errno == ENOENT ? ENOTFOUND : EACCESS); if ((stbuf.st_mode & S_IFMT) != S_IFREG) return (ENOTFOUND); if (mode == RRQ) { if ((stbuf.st_mode & S_IROTH) == 0) return (EACCESS); } else { if ((stbuf.st_mode & S_IWOTH) == 0) return (EACCESS); } } else { int err; /* * Relative file name: search the approved locations for it. * Don't allow write requests that avoid directory * restrictions. */ if (!strncmp(filename, "../", 3)) return (EACCESS); /* * If the file exists in one of the directories and isn't * readable, continue looking. However, change the error code * to give an indication that the file exists. */ err = ENOTFOUND; for (dirp = dirs; dirp->name != NULL; dirp++) { snprintf(pathname, sizeof(pathname), "%s/%s", dirp->name, filename); if (stat(pathname, &stbuf) == 0 && (stbuf.st_mode & S_IFMT) == S_IFREG) { if (mode == RRQ) { if ((stbuf.st_mode & S_IROTH) != 0) break; } else { if ((stbuf.st_mode & S_IWOTH) != 0) break; } err = EACCESS; } } if (dirp->name != NULL) *filep = filename = pathname; else if (mode == RRQ) return (err); else if (err != ENOTFOUND || !create_new) return (err); } /* * This option is handled here because it (might) require(s) the * size of the file. */ option_tsize(peer, NULL, mode, &stbuf); if (mode == RRQ) fd = open(filename, O_RDONLY); else { if (create_new) { if (increase_name) { error = find_next_name(filename, &fd); if (error > 0) return (error + 100); } else fd = open(filename, O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH ); } else fd = open(filename, O_WRONLY | O_TRUNC); } if (fd < 0) return (errno + 100); file = fdopen(fd, (mode == RRQ)? "r":"w"); if (file == NULL) { close(fd); return (errno + 100); } return (0); } static void tftp_xmitfile(int peer, const char *mode) { uint16_t block; time_t now; struct tftp_stats ts; memset(&ts, 0, sizeof(ts)); now = time(NULL); if (debug&DEBUG_SIMPLE) tftp_log(LOG_DEBUG, "Transmitting file"); read_init(0, file, mode); block = 1; tftp_send(peer, &block, &ts); read_close(); if (debug&DEBUG_SIMPLE) tftp_log(LOG_INFO, "Sent %jd bytes in %jd seconds", (intmax_t)ts.amount, (intmax_t)time(NULL) - now); } static void tftp_recvfile(int peer, const char *mode) { uint16_t block; struct timeval now1, now2; struct tftp_stats ts; gettimeofday(&now1, NULL); if (debug&DEBUG_SIMPLE) tftp_log(LOG_DEBUG, "Receiving file"); write_init(0, file, mode); block = 0; tftp_receive(peer, &block, &ts, NULL, 0); gettimeofday(&now2, NULL); if (debug&DEBUG_SIMPLE) { double f; if (now1.tv_usec > now2.tv_usec) { now2.tv_usec += 1000000; now2.tv_sec--; } f = now2.tv_sec - now1.tv_sec + (now2.tv_usec - now1.tv_usec) / 100000.0; tftp_log(LOG_INFO, "Download of %jd bytes in %d blocks completed after %0.1f seconds\n", (intmax_t)ts.amount, block, f); } return; }