123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607 |
- /* ------------------------------------------------------------------------- */
- /* 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> */
- /* */
- /* 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 */
- /* */
- /* ------------------------------------------------------------------------- */
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <sys/time.h>
- #include <sys/stat.h>
- #include <netdb.h>
- #include <netinet/in.h>
- #include <arpa/inet.h>
- #include <unistd.h>
- #include <fcntl.h>
- #include "busybox.h"
- //#define CONFIG_FEATURE_TFTP_DEBUG
- #define TFTP_BLOCKSIZE_DEFAULT 512 /* according to RFC 1350, don't change */
- #define TFTP_TIMEOUT 5 /* seconds */
- /* 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"
- };
- #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;
- struct sockaddr_in from;
- struct timeval tv;
- socklen_t fromlen;
- fd_set rfds;
- char *cp;
- unsigned short tmp;
- 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
- /* 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);
- tftp_bufsize += 4;
- if ((socketfd = socket(PF_INET, SOCK_DGRAM, 0)) < 0) {
- bb_perror_msg("socket");
- return EXIT_FAILURE;
- }
- len = sizeof(sa);
- memset(&sa, 0, len);
- bind(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_get) {
- opcode = TFTP_RRQ;
- }
- if (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_get && (opcode == TFTP_RRQ)) ||
- (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) < 6)) {
- bb_error_msg("too long remote-filename");
- break;
- }
- /* 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 ((cmd_get && (opcode == TFTP_ACK)) ||
- (cmd_put && (opcode == TFTP_DATA))) {
- *((unsigned short *) cp) = htons(block_nr);
- cp += 2;
- block_nr++;
- if (cmd_put && (opcode == TFTP_DATA)) {
- len = bb_full_read(localfd, cp, tftp_bufsize - 4);
- if (len < 0) {
- bb_perror_msg("read");
- break;
- }
- if (len != (tftp_bufsize - 4)) {
- finished++;
- }
- cp += len;
- }
- }
- /* send packet */
- timeout = bb_tftp_num_retries; /* re-initialize */
- do {
- len = cp - buf;
- #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) {
- 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 = bb_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]));
- #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;
- }
- #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 = bb_full_write(localfd, &buf[4], len - 4);
- if (len < 0) {
- bb_perror_msg("write");
- 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_put && (opcode == TFTP_ACK)) {
- if (tmp == (unsigned short)(block_nr - 1)) {
- if (finished) {
- break;
- }
- opcode = TFTP_DATA;
- continue;
- }
- }
- }
- #ifdef CONFIG_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 */
- #ifdef CONFIG_FEATURE_TFTP_BLOCKSIZE
- char *sblocksize = NULL;
- #define BS "b:"
- #define BS_ARG , &sblocksize
- #else
- #define BS
- #define BS_ARG
- #endif
- #ifdef CONFIG_FEATURE_TFTP_GET
- #define GET "g"
- #define GET_COMPL ":g"
- #else
- #define GET
- #define GET_COMP
- #endif
- #ifdef CONFIG_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)
- 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
- 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;
- }
- }
- #endif
- 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(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");
- }
- /* XXX: argv[optind] and/or argv[optind + 1] may be NULL! */
- host = xgethostbyname(argv[optind]);
- port = bb_lookup_port(argv[optind + 1], "udp", 69);
- #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);
- #ifdef CONFIG_FEATURE_CLEAN_UP
- if (!(fd == STDOUT_FILENO || fd == STDIN_FILENO)) {
- close(fd);
- }
- #endif
- return(result);
- }
|