diff options
Diffstat (limited to 'networking/tftp.c')
-rw-r--r-- | networking/tftp.c | 561 |
1 files changed, 561 insertions, 0 deletions
diff --git a/networking/tftp.c b/networking/tftp.c new file mode 100644 index 000000000..64d376fa7 --- /dev/null +++ b/networking/tftp.c @@ -0,0 +1,561 @@ +/* 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 <damm@opensource.se> + * + * Parts of the code based on: + * + * atftp: Copyright (C) 2000 Jean-Pierre Lefebvre <helix@step.polymtl.ca> + * and Remi Lefebvre <remi@debian.org> + * + * utftp: Copyright (C) 1999 Uwe Ohse <uwe@ohse.de> + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * ------------------------------------------------------------------------- */ + +#include "busybox.h" + + +#define TFTP_BLOCKSIZE_DEFAULT 512 /* according to RFC 1350, don't change */ +#define TFTP_TIMEOUT 5 /* seconds */ +#define TFTP_NUM_RETRIES 5 /* number of retries */ + +static const char * const MODE_OCTET = "octet"; +#define MODE_OCTET_LEN 6 /* sizeof(MODE_OCTET)*/ + +static const char * const OPTION_BLOCKSIZE = "blksize"; +#define OPTION_BLOCKSIZE_LEN 8 /* sizeof(OPTION_BLOCKSIZE) */ + +/* opcodes we support */ +#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 *const tftp_bb_error_msg[] = { + "Undefined error", + "File not found", + "Access violation", + "Disk full or allocation error", + "Illegal TFTP operation", + "Unknown transfer ID", + "File already exists", + "No such user" +}; + +#define tftp_cmd_get ENABLE_FEATURE_TFTP_GET + +#if ENABLE_FEATURE_TFTP_PUT +# define tftp_cmd_put (tftp_cmd_get+ENABLE_FEATURE_TFTP_PUT) +#else +# define tftp_cmd_put 0 +#endif + + +#if ENABLE_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 > 65564)) { + bb_error_msg("bad blocksize"); + return 0; + } + + return blocksize; +} + +static char *tftp_option_get(char *buf, int len, const char * const 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 int tftp(const int cmd, const struct hostent *host, + const char *remotefile, const int localfd, + const unsigned short port, int tftp_bufsize) +{ + struct sockaddr_in sa; + struct sockaddr_in from; + struct timeval tv; + socklen_t fromlen; + fd_set rfds; + int socketfd; + int len; + int opcode = 0; + int finished = 0; + int timeout = TFTP_NUM_RETRIES; + unsigned short block_nr = 1; + unsigned short tmp; + char *cp; + + USE_FEATURE_TFTP_BLOCKSIZE(int want_option_ack = 0;) + + /* 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); + + if ((socketfd = socket(PF_INET, SOCK_DGRAM, 0)) < 0) { + /* need to unlink the localfile, so don't use xsocket here. */ + bb_perror_msg("socket"); + return EXIT_FAILURE; + } + + len = sizeof(sa); + + memset(&sa, 0, len); + xbind(socketfd, (struct sockaddr *)&sa, len); + + sa.sin_family = host->h_addrtype; + sa.sin_port = port; + memcpy(&sa.sin_addr, (struct in_addr *) host->h_addr, + sizeof(sa.sin_addr)); + + /* build opcode */ + if (cmd & tftp_cmd_get) { + opcode = TFTP_RRQ; + } + if (cmd & tftp_cmd_put) { + opcode = TFTP_WRQ; + } + + while (1) { + + cp = buf; + + /* first create the opcode part */ + *((unsigned short *) cp) = htons(opcode); + cp += 2; + + /* add filename and mode */ + if (((cmd & tftp_cmd_get) && (opcode == TFTP_RRQ)) || + ((cmd & tftp_cmd_put) && (opcode == TFTP_WRQ))) + { + int too_long = 0; + + /* 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 (too_long || ((&buf[tftp_bufsize - 1] - cp) < MODE_OCTET_LEN)) { + bb_error_msg("remote filename too long"); + break; + } + + /* add "mode" part of the package */ + memcpy(cp, MODE_OCTET, MODE_OCTET_LEN); + cp += MODE_OCTET_LEN; + +#if ENABLE_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("remote filename too long"); + break; + } + + /* add "blksize" + number of blocks */ + memcpy(cp, OPTION_BLOCKSIZE, OPTION_BLOCKSIZE_LEN); + cp += OPTION_BLOCKSIZE_LEN; + cp += snprintf(cp, 6, "%d", len) + 1; + + want_option_ack = 1; + } +#endif + } + + /* add ack and data */ + + if (((cmd & tftp_cmd_get) && (opcode == TFTP_ACK)) || + ((cmd & tftp_cmd_put) && (opcode == TFTP_DATA))) { + + *((unsigned short *) cp) = htons(block_nr); + + cp += 2; + + block_nr++; + + if ((cmd & tftp_cmd_put) && (opcode == TFTP_DATA)) { + len = full_read(localfd, cp, tftp_bufsize - 4); + + if (len < 0) { + bb_perror_msg(bb_msg_read_error); + break; + } + + if (len != (tftp_bufsize - 4)) { + finished++; + } + + cp += len; + } + } + + + /* send packet */ + + + timeout = TFTP_NUM_RETRIES; /* re-initialize */ + do { + + len = cp - buf; + +#if ENABLE_DEBUG_TFTP + 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) { + bb_perror_msg("send"); + len = -1; + break; + } + + + if (finished && (opcode == TFTP_ACK)) { + break; + } + + /* receive packet */ + + memset(&from, 0, sizeof(from)); + fromlen = sizeof(from); + + tv.tv_sec = TFTP_TIMEOUT; + tv.tv_usec = 0; + + FD_ZERO(&rfds); + FD_SET(socketfd, &rfds); + + switch (select(socketfd + 1, &rfds, NULL, NULL, &tv)) { + case 1: + len = recvfrom(socketfd, buf, tftp_bufsize, 0, + (struct sockaddr *) &from, &fromlen); + + if (len < 0) { + bb_perror_msg("recvfrom"); + break; + } + + timeout = 0; + + if (sa.sin_port == port) { + sa.sin_port = from.sin_port; + } + if (sa.sin_port == from.sin_port) { + break; + } + + /* fall-through for bad packets! */ + /* discard the packet - treat as timeout */ + timeout = TFTP_NUM_RETRIES; + case 0: + bb_error_msg("timeout"); + + timeout--; + if (timeout == 0) { + len = -1; + bb_error_msg("last timeout"); + } + break; + default: + bb_perror_msg("select"); + len = -1; + } + + } while (timeout && (len >= 0)); + + if ((finished) || (len < 0)) { + break; + } + + /* process received packet */ + + opcode = ntohs(*((unsigned short *) buf)); + tmp = ntohs(*((unsigned short *) &buf[2])); + +#if ENABLE_DEBUG_TFTP + 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 ENABLE_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, OPTION_BLOCKSIZE); + + if (res) { + int blksize = xatoi_u(res); + + if (tftp_blocksize_check(blksize, tftp_bufsize - 4)) { + + if (cmd & tftp_cmd_put) { + opcode = TFTP_DATA; + } else { + opcode = TFTP_ACK; + } +#if ENABLE_DEBUG_TFTP + fprintf(stderr, "using %s %u\n", OPTION_BLOCKSIZE, + 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 & tftp_cmd_get) && (opcode == TFTP_DATA)) { + + if (tmp == block_nr) { + + len = full_write(localfd, &buf[4], len - 4); + + if (len < 0) { + bb_perror_msg(bb_msg_write_error); + break; + } + + if (len != (tftp_bufsize - 4)) { + finished++; + } + + 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 ((cmd & tftp_cmd_put) && (opcode == TFTP_ACK)) { + + if (tmp == (unsigned short) (block_nr - 1)) { + if (finished) { + break; + } + + opcode = TFTP_DATA; + continue; + } + } + } + +#if ENABLE_FEATURE_CLEAN_UP + close(socketfd); + free(buf); +#endif + + return finished ? EXIT_SUCCESS : EXIT_FAILURE; +} + +int tftp_main(int argc, char **argv) +{ + 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 */ + +#if ENABLE_FEATURE_TFTP_BLOCKSIZE + char *sblocksize = NULL; + +#define BS "b:" +#define BS_ARG , &sblocksize +#else +#define BS +#define BS_ARG +#endif + +#if ENABLE_FEATURE_TFTP_GET +#define GET "g" +#define GET_COMPL ":g" +#else +#define GET +#define GET_COMPL +#endif + +#if ENABLE_FEATURE_TFTP_PUT +#define PUT "p" +#define PUT_COMPL ":p" +#else +#define PUT +#define PUT_COMPL +#endif + +#if defined(CONFIG_FEATURE_TFTP_GET) && defined(CONFIG_FEATURE_TFTP_PUT) + opt_complementary = GET_COMPL PUT_COMPL ":?g--p:p--g"; +#elif defined(CONFIG_FEATURE_TFTP_GET) || defined(CONFIG_FEATURE_TFTP_PUT) + opt_complementary = GET_COMPL PUT_COMPL; +#endif + + cmd = getopt32(argc, argv, GET PUT "l:r:" BS, &localfile, &remotefile BS_ARG); + + cmd &= (tftp_cmd_get | tftp_cmd_put); +#if ENABLE_FEATURE_TFTP_GET + if (cmd == tftp_cmd_get) + flags = O_WRONLY | O_CREAT | O_TRUNC; +#endif +#if ENABLE_FEATURE_TFTP_PUT + if (cmd == tftp_cmd_put) + flags = O_RDONLY; +#endif + +#if ENABLE_FEATURE_TFTP_BLOCKSIZE + if (sblocksize) { + blocksize = xatoi_u(sblocksize); + if (!tftp_blocksize_check(blocksize, 0)) { + return EXIT_FAILURE; + } + } +#endif + + if (localfile == NULL) + localfile = remotefile; + if (remotefile == NULL) + remotefile = localfile; + if ((localfile == NULL && remotefile == NULL) || (argv[optind] == NULL)) + bb_show_usage(); + + if (localfile == NULL || strcmp(localfile, "-") == 0) { + fd = (cmd == tftp_cmd_get) ? STDOUT_FILENO : STDIN_FILENO; + } else { + fd = open(localfile, flags, 0644); /* fail below */ + } + if (fd < 0) { + bb_perror_msg_and_die("local file"); + } + + host = xgethostbyname(argv[optind]); + port = bb_lookup_port(argv[optind + 1], "udp", 69); + +#if ENABLE_DEBUG_TFTP + 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); + + if (!(fd == STDOUT_FILENO || fd == STDIN_FILENO)) { + if (ENABLE_FEATURE_CLEAN_UP) + close(fd); + if (cmd == tftp_cmd_get && result != EXIT_SUCCESS) + unlink(localfile); + } + return result; +} |