X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=networking%2Ftftp.c;h=bdba16727bf27da7dc62c7d6330285be0f8ea3b2;hb=c44bc986b738977c5cd9741d9752a41ac022602f;hp=309df36b301d1a7b88a82cd278b1e0897060d1d1;hpb=6b2c23d847a0942e6dcc5735a5a5512ab1432d28;p=oweals%2Fbusybox.git diff --git a/networking/tftp.c b/networking/tftp.c index 309df36b3..bdba16727 100644 --- a/networking/tftp.c +++ b/networking/tftp.c @@ -1,41 +1,29 @@ -/* ------------------------------------------------------------------------- */ -/* tftp.c */ -/* */ -/* A simple tftp client for busybox. */ -/* Tries to follow RFC1350. */ -/* Only "octet" mode and 512-byte data blocks are supported. */ -/* */ -/* Copyright (C) 2001 Magnus Damm */ -/* */ -/* Parts of the code based on: */ -/* */ -/* atftp: Copyright (C) 2000 Jean-Pierre Lefebvre */ -/* and Remi Lefebvre */ -/* */ -/* utftp: Copyright (C) 1999 Uwe Ohse */ -/* */ -/* This program is free software; you can redistribute it and/or modify */ -/* it under the terms of the GNU General Public License as published by */ -/* the Free Software Foundation; either version 2 of the License, or */ -/* (at your option) any later version. */ -/* */ -/* This program is distributed in the hope that it will be useful, */ -/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ -/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU */ -/* General Public License for more details. */ -/* */ -/* You should have received a copy of the GNU General Public License */ -/* along with this program; if not, write to the Free Software */ -/* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -/* */ -/* ------------------------------------------------------------------------- */ +/* vi: set sw=4 ts=4: */ +/* ------------------------------------------------------------------------- + * tftp.c + * + * A simple tftp client for busybox. + * Tries to follow RFC1350. + * Only "octet" mode supported. + * Optional blocksize negotiation (RFC2347 + RFC2348) + * + * Copyright (C) 2001 Magnus Damm + * + * Parts of the code based on: + * + * atftp: Copyright (C) 2000 Jean-Pierre Lefebvre + * and Remi Lefebvre + * + * utftp: Copyright (C) 1999 Uwe Ohse + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * ------------------------------------------------------------------------- */ #include #include #include #include #include -#include #include #include #include @@ -45,40 +33,21 @@ #include "busybox.h" -//#define BB_FEATURE_TFTP_DEBUG +//#define CONFIG_FEATURE_TFTP_DEBUG -/* we don't need #ifdefs with these constants and optimization... */ +#define TFTP_BLOCKSIZE_DEFAULT 512 /* according to RFC 1350, don't change */ +#define TFTP_TIMEOUT 5 /* seconds */ -#ifdef BB_FEATURE_TFTP_GET -#define BB_TFTP_GET (1 << 0) -#else -#define BB_TFTP_GET 0 -#endif - -#ifdef BB_FEATURE_TFTP_PUT -#define BB_TFTP_PUT (1 << 1) -#else -#define BB_TFTP_PUT 0 -#endif - -#ifdef BB_FEATURE_TFTP_DEBUG -#define BB_TFTP_DEBUG 1 -#else -#define BB_TFTP_DEBUG 0 -#endif - -#define BB_TFTP_NO_RETRIES 5 -#define BB_TFTP_TIMEOUT 5 /* seconds */ - -#define RRQ 1 /* read request */ -#define WRQ 2 /* write request */ -#define DATA 3 /* data packet */ -#define ACK 4 /* acknowledgement */ -#define ERROR 5 /* error code */ +/* opcodes we support */ -#define BUFSIZE (512+4) +#define TFTP_RRQ 1 +#define TFTP_WRQ 2 +#define TFTP_DATA 3 +#define TFTP_ACK 4 +#define TFTP_ERROR 5 +#define TFTP_OACK 6 -static const char *tftp_error_msg[] = { +static const char * const tftp_bb_error_msg[] = { "Undefined error", "File not found", "Access violation", @@ -89,86 +58,203 @@ static const char *tftp_error_msg[] = { "No such user" }; -static inline int tftp(int cmd, struct hostent *host, - char *serverfile, int localfd, int port) +#ifdef CONFIG_FEATURE_TFTP_GET +# define tftp_cmd_get 1 +#else +# define tftp_cmd_get 0 +#endif +#ifdef CONFIG_FEATURE_TFTP_PUT +# define tftp_cmd_put (tftp_cmd_get+1) +#else +# define tftp_cmd_put 0 +#endif + + +#ifdef CONFIG_FEATURE_TFTP_BLOCKSIZE + +static int tftp_blocksize_check(int blocksize, int bufsize) +{ + /* Check if the blocksize is valid: + * RFC2348 says between 8 and 65464, + * but our implementation makes it impossible + * to use blocksizes smaller than 22 octets. + */ + + if ((bufsize && (blocksize > bufsize)) || + (blocksize < 8) || (blocksize > 65464)) { + bb_error_msg("bad blocksize"); + return 0; + } + + return blocksize; +} + +static char *tftp_option_get(char *buf, int len, char *option) +{ + int opt_val = 0; + int opt_found = 0; + int k; + + while (len > 0) { + + /* Make sure the options are terminated correctly */ + + for (k = 0; k < len; k++) { + if (buf[k] == '\0') { + break; + } + } + + if (k >= len) { + break; + } + + if (opt_val == 0) { + if (strcasecmp(buf, option) == 0) { + opt_found = 1; + } + } + else { + if (opt_found) { + return buf; + } + } + + k++; + + buf += k; + len -= k; + + opt_val ^= 1; + } + + return NULL; +} + +#endif + +static inline int tftp(const int cmd, const struct hostent *host, + const char *remotefile, int localfd, const unsigned short port, int tftp_bufsize) { + const int cmd_get = cmd & tftp_cmd_get; + const int cmd_put = cmd & tftp_cmd_put; + const int bb_tftp_num_retries = 5; + struct sockaddr_in sa; - int socketfd; - struct timeval tv; - fd_set rfds; struct sockaddr_in from; + struct timeval tv; socklen_t fromlen; + fd_set rfds; char *cp; unsigned short tmp; - int len, opcode, finished; - int timeout, block_nr; + int socketfd; + int len; + int opcode = 0; + int finished = 0; + int timeout = bb_tftp_num_retries; + unsigned short block_nr = 1; + +#ifdef CONFIG_FEATURE_TFTP_BLOCKSIZE + int want_option_ack = 0; +#endif - RESERVE_BB_BUFFER(buf, BUFSIZE); + /* Can't use RESERVE_CONFIG_BUFFER here since the allocation + * size varies meaning BUFFERS_GO_ON_STACK would fail */ + char *buf=xmalloc(tftp_bufsize + 4); - opcode = finished = timeout = 0; - block_nr = 1; + tftp_bufsize += 4; - if ((socketfd = socket(PF_INET, SOCK_DGRAM, 0)) < 0) { - perror_msg("socket"); + if ((socketfd = socket(PF_INET, SOCK_DGRAM, 0)) < 0) { /* bb_xsocket? */ + bb_perror_msg("socket"); return EXIT_FAILURE; } len = sizeof(sa); memset(&sa, 0, len); - bind(socketfd, &sa, len); + bind(socketfd, (struct sockaddr *)&sa, len); sa.sin_family = host->h_addrtype; - sa.sin_port = htons(port); + sa.sin_port = port; memcpy(&sa.sin_addr, (struct in_addr *) host->h_addr, sizeof(sa.sin_addr)); /* build opcode */ - if (cmd & BB_TFTP_GET) { - opcode = RRQ; + if (cmd_get) { + opcode = TFTP_RRQ; } - if (cmd & BB_TFTP_PUT) { - opcode = WRQ; + if (cmd_put) { + opcode = TFTP_WRQ; } while (1) { - - /* build packet of type "opcode" */ - - cp = buf; + /* first create the opcode part */ + *((unsigned short *) cp) = htons(opcode); cp += 2; /* add filename and mode */ - if ((BB_TFTP_GET && (opcode == RRQ)) || - (BB_TFTP_PUT && (opcode == WRQ))) { + if ((cmd_get && (opcode == TFTP_RRQ)) || + (cmd_put && (opcode == TFTP_WRQ))) { + int too_long = 0; - while (cp != &buf[BUFSIZE - 1]) { - if ((*cp = *serverfile++) == '\0') - break; - cp++; + /* see if the filename fits into buf */ + /* and fill in packet */ + + len = strlen(remotefile) + 1; + + if ((cp + len) >= &buf[tftp_bufsize - 1]) { + too_long = 1; + } + else { + safe_strncpy(cp, remotefile, len); + cp += len; } - if ((*cp != '\0') || (&buf[BUFSIZE - 1] - cp) < 7) { - error_msg("too long server-filename"); + if (too_long || ((&buf[tftp_bufsize - 1] - cp) < 6)) { + bb_error_msg("too long remote-filename"); break; } - memcpy(cp + 1, "octet", 6); - cp += 7; + /* add "mode" part of the package */ + + memcpy(cp, "octet", 6); + cp += 6; + +#ifdef CONFIG_FEATURE_TFTP_BLOCKSIZE + + len = tftp_bufsize - 4; /* data block size */ + + if (len != TFTP_BLOCKSIZE_DEFAULT) { + + if ((&buf[tftp_bufsize - 1] - cp) < 15) { + bb_error_msg("too long remote-filename"); + break; + } + + /* add "blksize" + number of blocks */ + + memcpy(cp, "blksize", 8); + cp += 8; + + cp += snprintf(cp, 6, "%d", len) + 1; + + want_option_ack = 1; + } +#endif } /* add ack and data */ - if ((BB_TFTP_GET && (opcode == ACK)) || - (BB_TFTP_PUT && (opcode == DATA))) { + if ((cmd_get && (opcode == TFTP_ACK)) || + (cmd_put && (opcode == TFTP_DATA))) { *((unsigned short *) cp) = htons(block_nr); @@ -176,21 +262,19 @@ static inline int tftp(int cmd, struct hostent *host, block_nr++; - if (BB_TFTP_PUT && (opcode == DATA)) { - len = read(localfd, cp, BUFSIZE - 4); + if (cmd_put && (opcode == TFTP_DATA)) { + len = bb_full_read(localfd, cp, tftp_bufsize - 4); if (len < 0) { - perror_msg("read"); + bb_perror_msg("read"); break; } - if (len != (BUFSIZE - 4)) { + if (len != (tftp_bufsize - 4)) { finished++; } cp += len; - } else if (finished) { - break; } } @@ -198,86 +282,81 @@ static inline int tftp(int cmd, struct hostent *host, /* send packet */ + timeout = bb_tftp_num_retries; /* re-initialize */ do { len = cp - buf; - if (BB_TFTP_DEBUG) { - printf("sending %u bytes\n", len); - - for (cp = buf; cp < &buf[len]; cp++) - printf("%02x ", *cp); - printf("\n"); - } - +#ifdef CONFIG_FEATURE_TFTP_DEBUG + fprintf(stderr, "sending %u bytes\n", len); + for (cp = buf; cp < &buf[len]; cp++) + fprintf(stderr, "%02x ", (unsigned char)*cp); + fprintf(stderr, "\n"); +#endif if (sendto(socketfd, buf, len, 0, - (struct sockaddr *) &sa, sizeof(sa)) < 0) { - perror_msg("send"); + (struct sockaddr *) &sa, sizeof(sa)) < 0) { + bb_perror_msg("send"); len = -1; break; } - /* receive packet */ + if (finished && (opcode == TFTP_ACK)) { + break; + } + /* receive packet */ memset(&from, 0, sizeof(from)); fromlen = sizeof(from); - tv.tv_sec = BB_TFTP_TIMEOUT; + tv.tv_sec = TFTP_TIMEOUT; tv.tv_usec = 0; FD_ZERO(&rfds); FD_SET(socketfd, &rfds); - switch (select(FD_SETSIZE, &rfds, NULL, NULL, &tv)) { + switch (select(socketfd + 1, &rfds, NULL, NULL, &tv)) { case 1: - len = recvfrom(socketfd, buf, - BUFSIZE, 0, - (struct sockaddr *) &from, &fromlen); + len = recvfrom(socketfd, buf, tftp_bufsize, 0, + (struct sockaddr *) &from, &fromlen); if (len < 0) { - perror_msg("recvfrom"); + bb_perror_msg("recvfrom"); break; } timeout = 0; - if (sa.sin_port == htons(port)) { + if (sa.sin_port == port) { sa.sin_port = from.sin_port; - break; } - if (sa.sin_port == from.sin_port) { break; } /* fall-through for bad packets! */ /* discard the packet - treat as timeout */ + timeout = bb_tftp_num_retries; case 0: - error_msg("timeout"); + bb_error_msg("timeout"); - if (!timeout) { - timeout = BB_TFTP_NO_RETRIES; - } else { - timeout--; - } - - if (!timeout) { + timeout--; + if (timeout == 0) { len = -1; - error_msg("last timeout"); + bb_error_msg("last timeout"); } break; default: - perror_msg("select"); + bb_perror_msg("select"); len = -1; } } while (timeout && (len >= 0)); - if (len < 0) { + if ((finished) || (len < 0)) { break; } @@ -287,133 +366,229 @@ static inline int tftp(int cmd, struct hostent *host, opcode = ntohs(*((unsigned short *) buf)); tmp = ntohs(*((unsigned short *) &buf[2])); - if (BB_TFTP_DEBUG) { - printf("received %d bytes: %04x %04x\n", len, opcode, tmp); +#ifdef CONFIG_FEATURE_TFTP_DEBUG + fprintf(stderr, "received %d bytes: %04x %04x\n", len, opcode, tmp); +#endif + + if (opcode == TFTP_ERROR) { + const char *msg = NULL; + + if (buf[4] != '\0') { + msg = &buf[4]; + buf[tftp_bufsize - 1] = '\0'; + } else if (tmp < (sizeof(tftp_bb_error_msg) + / sizeof(char *))) { + + msg = tftp_bb_error_msg[tmp]; + } + + if (msg) { + bb_error_msg("server says: %s", msg); + } + + break; } - if (BB_TFTP_GET && (opcode == DATA)) { +#ifdef CONFIG_FEATURE_TFTP_BLOCKSIZE + if (want_option_ack) { + + want_option_ack = 0; + + if (opcode == TFTP_OACK) { + + /* server seems to support options */ + + char *res; + + res = tftp_option_get(&buf[2], len-2, + "blksize"); + + if (res) { + int blksize = atoi(res); + + if (tftp_blocksize_check(blksize, + tftp_bufsize - 4)) { + + if (cmd_put) { + opcode = TFTP_DATA; + } + else { + opcode = TFTP_ACK; + } +#ifdef CONFIG_FEATURE_TFTP_DEBUG + fprintf(stderr, "using blksize %u\n", blksize); +#endif + tftp_bufsize = blksize + 4; + block_nr = 0; + continue; + } + } + /* FIXME: + * we should send ERROR 8 */ + bb_error_msg("bad server option"); + break; + } + + bb_error_msg("warning: blksize not supported by server" + " - reverting to 512"); + + tftp_bufsize = TFTP_BLOCKSIZE_DEFAULT + 4; + } +#endif + + if (cmd_get && (opcode == TFTP_DATA)) { if (tmp == block_nr) { - len = write(localfd, &buf[4], len - 4); + + len = bb_full_write(localfd, &buf[4], len - 4); if (len < 0) { - perror_msg("write"); + bb_perror_msg("write"); break; } - if (len != (BUFSIZE - 4)) { + if (len != (tftp_bufsize - 4)) { finished++; } - opcode = ACK; + opcode = TFTP_ACK; + continue; + } + /* in case the last ack disappeared into the ether */ + if ( tmp == (block_nr - 1) ) { + --block_nr; + opcode = TFTP_ACK; + continue; + } else if (tmp + 1 == block_nr) { + /* Server lost our TFTP_ACK. Resend it */ + block_nr = tmp; + opcode = TFTP_ACK; continue; } } - if (BB_TFTP_PUT && (opcode == ACK)) { + if (cmd_put && (opcode == TFTP_ACK)) { - if (tmp == (block_nr - 1)) { + if (tmp == (unsigned short)(block_nr - 1)) { if (finished) { break; } - opcode = DATA; + opcode = TFTP_DATA; continue; } } - - if (opcode == ERROR) { - char *msg = NULL; - - if (buf[4] != '\0') { - msg = &buf[4]; - buf[BUFSIZE - 1] = '\0'; - } else if (tmp < (sizeof(tftp_error_msg) / sizeof(char *))) { - msg = (char *) tftp_error_msg[tmp]; - } - - if (msg) { - error_msg("server says: %s", msg); - } - - break; - } } +#ifdef CONFIG_FEATURE_CLEAN_UP close(socketfd); + free(buf); +#endif + return finished ? EXIT_SUCCESS : EXIT_FAILURE; } int tftp_main(int argc, char **argv) { - char *cp, *s; - char *serverstr; - struct hostent *host; - char *serverfile; - char *localfile; - int cmd, flags, fd, bad; - - host = (void *) serverstr = serverfile = localfile = NULL; - flags = cmd = 0; - bad = 1; - - if (argc > 3) { - if (BB_TFTP_GET && (strcmp(argv[1], "get") == 0)) { - cmd = BB_TFTP_GET; - flags = O_WRONLY | O_CREAT; - serverstr = argv[2]; - localfile = argv[3]; - } - - if (BB_TFTP_PUT && (strcmp(argv[1], "put") == 0)) { - cmd = BB_TFTP_PUT; - flags = O_RDONLY; - localfile = argv[2]; - serverstr = argv[3]; - } - - } - - if (!(cmd & (BB_TFTP_GET | BB_TFTP_PUT))) { - show_usage(); - } + struct hostent *host = NULL; + const char *localfile = NULL; + const char *remotefile = NULL; + int port; + int cmd = 0; + int fd = -1; + int flags = 0; + int result; + int blocksize = TFTP_BLOCKSIZE_DEFAULT; + + /* figure out what to pass to getopt */ + +#ifdef CONFIG_FEATURE_TFTP_BLOCKSIZE + char *sblocksize = NULL; +#define BS "b:" +#define BS_ARG , &sblocksize +#else +#define BS +#define BS_ARG +#endif - for (cp = serverstr; *cp != '\0'; cp++) - if (*cp == ':') - break; +#ifdef CONFIG_FEATURE_TFTP_GET +#define GET "g" +#define GET_COMPL ":g" +#else +#define GET +#define GET_COMPL +#endif - if (*cp == ':') { +#ifdef CONFIG_FEATURE_TFTP_PUT +#define PUT "p" +#define PUT_COMPL ":p" +#else +#define PUT +#define PUT_COMPL +#endif - serverfile = cp + 1; +#if defined(CONFIG_FEATURE_TFTP_GET) && defined(CONFIG_FEATURE_TFTP_PUT) + bb_opt_complementally = GET_COMPL PUT_COMPL ":?g--p:p--g"; +#elif defined(CONFIG_FEATURE_TFTP_GET) || defined(CONFIG_FEATURE_TFTP_PUT) + bb_opt_complementally = GET_COMPL PUT_COMPL; +#else + /* XXX: may be should #error ? */ +#endif - s = xstrdup(serverstr); - s[cp - serverstr] = '\0'; - if ((host = gethostbyname(s))) { - bad = 0; + cmd = bb_getopt_ulflags(argc, argv, GET PUT "l:r:" BS, + &localfile, &remotefile BS_ARG); +#ifdef CONFIG_FEATURE_TFTP_BLOCKSIZE + if(sblocksize) { + blocksize = atoi(sblocksize); + if (!tftp_blocksize_check(blocksize, 0)) { + return EXIT_FAILURE; } - - free(s); - } - if (bad) { - error_msg_and_die("bad \"server:file\" combination"); } +#endif - if (BB_TFTP_DEBUG) { - printf("using server \"%s\", serverfile \"%s\"," - "localfile \"%s\".\n", - inet_ntoa(*((struct in_addr *) host->h_addr)), - serverfile, localfile); - } + cmd &= (tftp_cmd_get | tftp_cmd_put); +#ifdef CONFIG_FEATURE_TFTP_GET + if(cmd == tftp_cmd_get) + flags = O_WRONLY | O_CREAT | O_TRUNC; +#endif +#ifdef CONFIG_FEATURE_TFTP_PUT + if(cmd == tftp_cmd_put) + flags = O_RDONLY; +#endif - if ((fd = open(localfile, flags, 0644)) < 0) { - perror_msg_and_die("local file"); + if(localfile == NULL) + localfile = remotefile; + if(remotefile == NULL) + remotefile = localfile; + /* XXX: I corrected this, but may be wrong too. vodz */ + if(localfile==NULL || strcmp(localfile, "-") == 0) { + fd = fileno((cmd==tftp_cmd_get)? stdout : stdin); + } else if (fd==-1) { + fd = open(localfile, flags, 0644); + } + if (fd < 0) { + bb_perror_msg_and_die("local file"); } - flags = tftp(cmd, host, serverfile, fd, 69); + /* XXX: argv[optind] and/or argv[optind + 1] may be NULL! */ + host = xgethostbyname(argv[optind]); + port = bb_lookup_port(argv[optind + 1], "udp", 69); - close(fd); +#ifdef CONFIG_FEATURE_TFTP_DEBUG + fprintf(stderr, "using server \"%s\", remotefile \"%s\", " + "localfile \"%s\".\n", + inet_ntoa(*((struct in_addr *) host->h_addr)), + remotefile, localfile); +#endif + + result = tftp(cmd, host, remotefile, fd, port, blocksize); - return flags; +#ifdef CONFIG_FEATURE_CLEAN_UP + if (!(fd == STDOUT_FILENO || fd == STDIN_FILENO)) { + close(fd); + } +#endif + return(result); }