123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462 |
- /* tftp.c - TFTP client.
- *
- * Copyright 2012 Madhur Verma <mad.flexi@gmail.com>
- * Copyright 2015 Sameer Prakash Pradhan <sameer.p.pradhan@gmail.com>
- *
- * No Standard.
- USE_TFTP(NEWTOY(tftp, "<1b#<8>65464r:l:g|p|[!gp]", TOYFLAG_USR|TOYFLAG_BIN))
- config TFTP
- bool "tftp"
- default n
- help
- usage: tftp [OPTIONS] HOST [PORT]
- Transfer file from/to tftp server.
- -l FILE Local FILE
- -r FILE Remote FILE
- -g Get file
- -p Put file
- -b SIZE Transfer blocks of SIZE octets(8 <= SIZE <= 65464)
- */
- #define FOR_tftp
- #include "toys.h"
- GLOBALS(
- char *local_file;
- char *remote_file;
- long block_size;
- struct sockaddr_storage inaddr;
- int af;
- )
- #define TFTP_BLKSIZE 512
- #define TFTP_RETRIES 3
- #define TFTP_DATAHEADERSIZE 4
- #define TFTP_MAXPACKETSIZE (TFTP_DATAHEADERSIZE + TFTP_BLKSIZE)
- #define TFTP_PACKETSIZE TFTP_MAXPACKETSIZE
- #define TFTP_DATASIZE (TFTP_PACKETSIZE-TFTP_DATAHEADERSIZE)
- #define TFTP_IOBUFSIZE (TFTP_PACKETSIZE+8)
- #define TFTP_OP_RRQ 1 /* Read Request RFC 1350, RFC 2090 */
- #define TFTP_OP_WRQ 2 /* Write Request RFC 1350 */
- #define TFTP_OP_DATA 3 /* Data chunk RFC 1350 */
- #define TFTP_OP_ACK 4 /* Acknowledgement RFC 1350 */
- #define TFTP_OP_ERR 5 /* Error Message RFC 1350 */
- #define TFTP_OP_OACK 6 /* Option acknowledgment RFC 2347 */
- #define TFTP_ER_ILLEGALOP 4 /* Illegal TFTP operation */
- #define TFTP_ER_UNKID 5 /* Unknown transfer ID */
- #define TFTP_ES_NOSUCHFILE "File not found"
- #define TFTP_ES_ACCESS "Access violation"
- #define TFTP_ES_FULL "Disk full or allocation exceeded"
- #define TFTP_ES_ILLEGALOP "Illegal TFTP operation"
- #define TFTP_ES_UNKID "Unknown transfer ID"
- #define TFTP_ES_EXISTS "File already exists"
- #define TFTP_ES_UNKUSER "No such user"
- #define TFTP_ES_NEGOTIATE "Terminate transfer due to option negotiation"
- // Initializes SERVER with ADDR and returns socket.
- static int init_tftp(struct sockaddr_storage *server)
- {
- struct timeval to = { .tv_sec = 10, //Time out
- .tv_usec = 0 };
- const int set = 1;
- int port = 69, sd = xsocket(TT.af, SOCK_DGRAM, IPPROTO_UDP);
- xsetsockopt(sd, SOL_SOCKET, SO_RCVTIMEO, (void *)&to, sizeof(struct timeval));
- xsetsockopt(sd, SOL_SOCKET, SO_REUSEADDR, (void *)&set, sizeof(set));
- if(toys.optc == 2) port = atolx_range(toys.optargs[1], 1, 65535);
- memset(server, 0, sizeof(struct sockaddr_storage));
- if (TT.af == AF_INET6) {
- ((struct sockaddr_in6 *)server)->sin6_family = AF_INET6;
- ((struct sockaddr_in6 *)server)->sin6_addr =
- ((struct sockaddr_in6 *)&TT.inaddr)->sin6_addr;
- ((struct sockaddr_in6 *)server)->sin6_port = htons(port);
- }
- else {
- ((struct sockaddr_in *)server)->sin_family = AF_INET;
- ((struct sockaddr_in *)server)->sin_addr.s_addr =
- ((struct sockaddr_in *)&TT.inaddr)->sin_addr.s_addr;
- ((struct sockaddr_in *)server)->sin_port = htons(port);
- }
- return sd;
- }
- /*
- * Makes a request packet in BUFFER with OPCODE and file PATH of MODE
- * and returns length of packet.
- */
- static int mkpkt_request(uint8_t *buffer, int opcode, char *path, int mode)
- {
- buffer[0] = opcode >> 8;
- buffer[1] = opcode & 0xff;
- if(strlen(path) > TFTP_BLKSIZE) error_exit("path too long");
- return sprintf((char*) &buffer[2], "%s%c%s", path, 0,
- (mode ? "octet" : "netascii")) + 3;
- }
- /*
- * Makes an acknowledgement packet in BUFFER of BLOCNO
- * and returns packet length.
- */
- static int mkpkt_ack(uint8_t *buffer, uint16_t blockno)
- {
- buffer[0] = TFTP_OP_ACK >> 8;
- buffer[1] = TFTP_OP_ACK & 0xff;
- buffer[2] = blockno >> 8;
- buffer[3] = blockno & 0xff;
- return 4;
- }
- /*
- * Makes an error packet in BUFFER with ERRORCODE and ERRORMSG.
- * and returns packet length.
- */
- static int mkpkt_err(uint8_t *buffer, uint16_t errorcode, char *errormsg)
- {
- buffer[0] = TFTP_OP_ERR >> 8;
- buffer[1] = TFTP_OP_ERR & 0xff;
- buffer[2] = errorcode >> 8;
- buffer[3] = errorcode & 0xff;
- strcpy((char*) &buffer[4], errormsg);
- return strlen(errormsg) + 5;
- }
- /*
- * Recieves data from server in BUFF with socket SD and updates FROM
- * and returns read length.
- */
- static int read_server(int sd, void *buf, int len,
- struct sockaddr_storage *from)
- {
- socklen_t alen;
- ssize_t nb;
-
- for (;;) {
- memset(buf, 0, len);
- alen = sizeof(struct sockaddr_storage);
- nb = recvfrom(sd, buf, len, 0, (struct sockaddr *) from, &alen);
- if (nb < 0) {
- if (errno == EAGAIN) {
- perror_msg("server read timed out");
- return nb;
- }else if (errno != EINTR) {
- perror_msg("server read failed");
- return nb;
- }
- }else return nb;
- }
- return nb;
- }
- /*
- * sends data to server TO from BUFF of length LEN through socket SD
- * and returns successfully send bytes number.
- */
- static ssize_t write_server(int sd, void *buf, size_t len,
- struct sockaddr_storage *to)
- {
- ssize_t nb;
-
- for (;;) {
- nb = sendto(sd, buf, len, 0, (struct sockaddr *)to,
- sizeof(struct sockaddr_storage));
- if (nb < 0) {
- if (errno != EINTR) {
- perror_msg("server write failed");
- return nb;
- }
- } else return nb;
- }
- return nb;
- }
- // checks packet for data and updates block no
- static inline int check_data( uint8_t *packet, uint16_t *opcode,
- uint16_t *blockno)
- {
- *opcode = (uint16_t) packet[0] << 8 | (uint16_t) packet[1];
- if (*opcode == TFTP_OP_DATA) {
- *blockno = (uint16_t) packet[2] << 8 | (uint16_t) packet[3];
- return 0;
- }
- return -1;
- }
- // Makes data packet through FD from file OFFSET in buffer PACKET of BLOCKNO
- static int mkpkt_data(int fd, off_t offset, uint8_t *packet, uint16_t blockno)
- {
- off_t tmp;
- int nbytesread;
- packet[0] = TFTP_OP_DATA >> 8;
- packet[1] = TFTP_OP_DATA & 0xff;
- packet[2] = blockno >> 8;
- packet[3] = blockno & 0xff;
- tmp = lseek(fd, offset, SEEK_SET);
- if (tmp == (off_t) -1) {
- perror_msg("lseek failed");
- return -1;
- }
- nbytesread = readall(fd, &packet[TFTP_DATAHEADERSIZE], TFTP_DATASIZE);
- if (nbytesread < 0) return -1;
- return nbytesread + TFTP_DATAHEADERSIZE;
- }
- // Receives ACK responses from server and updates blockno
- static int read_ack(int sd, uint8_t *packet, struct sockaddr_storage *server,
- uint16_t *port, uint16_t *blockno)
- {
- struct sockaddr_storage from;
- int nbytes;
- uint16_t opcode, rblockno;
- int packetlen, retry;
- for (retry = 0; retry < TFTP_RETRIES; retry++) {
- for (;;) {
- nbytes = read_server(sd, packet, TFTP_IOBUFSIZE, &from);
- if (nbytes < 4) { // Ack headersize = 4
- if (nbytes == 0) error_msg("Connection lost.");
- else if (nbytes > 0) error_msg("Short packet: %d bytes", nbytes);
- else error_msg("Server read ACK failure.");
- break;
- } else {
- if (!*port) {
- *port = ((struct sockaddr_in *)&from)->sin_port;
- ((struct sockaddr_in *)server)->sin_port =
- ((struct sockaddr_in *)&from)->sin_port;
- }
- if (((struct sockaddr_in *)server)->sin_addr.s_addr !=
- ((struct sockaddr_in *)&from)->sin_addr.s_addr) {
- error_msg("Invalid address in DATA.");
- continue;
- }
- if (*port != ((struct sockaddr_in *)server)->sin_port) {
- error_msg("Invalid port in DATA.");
- packetlen = mkpkt_err(packet, TFTP_ER_UNKID, TFTP_ES_UNKID);
- (void) write_server(sd, packet, packetlen, server);
- continue;
- }
- opcode = (uint16_t) packet[0] << 8 | (uint16_t) packet[1];
- rblockno = (uint16_t) packet[2] << 8 | (uint16_t) packet[3];
- if (opcode != TFTP_OP_ACK) {
- error_msg("Bad opcode.");
- if (opcode > 5) {
- packetlen = mkpkt_err(packet, TFTP_ER_ILLEGALOP, TFTP_ES_ILLEGALOP);
- (void) write_server(sd, packet, packetlen, server);
- }
- break;
- }
- if (blockno) *blockno = rblockno;
- return 0;
- }
- }
- }
- error_msg("Timeout, Waiting for ACK.");
- return -1;
- }
- // receives file from server.
- static int file_get(void)
- {
- struct sockaddr_storage server, from;
- uint8_t *packet;
- uint16_t blockno = 0, opcode, rblockno = 0;
- int len, sd, fd, retry, nbytesrecvd = 0, ndatabytes, ret, result = -1;
- sd = init_tftp(&server);
- packet = (uint8_t*) xzalloc(TFTP_IOBUFSIZE);
- fd = xcreate(TT.local_file, O_WRONLY | O_CREAT | O_TRUNC, 0666);
- len = mkpkt_request(packet, TFTP_OP_RRQ, TT.remote_file, 1);
- ret = write_server(sd, packet, len, &server);
- if (ret != len){
- unlink(TT.local_file);
- goto errout_with_sd;
- }
- if (TT.af == AF_INET6) ((struct sockaddr_in6 *)&server)->sin6_port = 0;
- else ((struct sockaddr_in *)&server)->sin_port = 0;
- do {
- blockno++;
- for (retry = 0 ; retry < TFTP_RETRIES; retry++) {
- nbytesrecvd = read_server(sd, packet, TFTP_IOBUFSIZE, &from);
- if (nbytesrecvd > 0) {
- if ( ((TT.af == AF_INET) &&
- memcmp(&((struct sockaddr_in *)&server)->sin_addr,
- &((struct sockaddr_in *)&from)->sin_addr,
- sizeof(struct in_addr))) ||
- ((TT.af == AF_INET6) &&
- memcmp(&((struct sockaddr_in6 *)&server)->sin6_addr,
- &((struct sockaddr_in6 *)&from)->sin6_addr,
- sizeof(struct in6_addr)))) {
- error_msg("Invalid address in DATA.");
- retry--;
- continue;
- }
- if ( ((TT.af == AF_INET) && ((struct sockaddr_in *)&server)->sin_port
- && (((struct sockaddr_in *)&server)->sin_port !=
- ((struct sockaddr_in *)&from)->sin_port)) ||
- ((TT.af == AF_INET6) && ((struct sockaddr_in6 *)&server)->sin6_port
- && (((struct sockaddr_in6 *)&server)->sin6_port !=
- ((struct sockaddr_in6 *)&from)->sin6_port))) {
- error_msg("Invalid port in DATA.");
- len = mkpkt_err(packet, TFTP_ER_UNKID, TFTP_ES_UNKID);
- ret = write_server(sd, packet, len, &from);
- retry--;
- continue;
- }
- if (nbytesrecvd < TFTP_DATAHEADERSIZE) {
- error_msg("Tiny data packet ignored.");
- continue;
- }
- if (check_data(packet, &opcode, &rblockno) != 0
- || blockno != rblockno) {
- if (opcode == TFTP_OP_ERR) {
- char *message = "DATA Check failure.";
- char *arr[] = {TFTP_ES_NOSUCHFILE, TFTP_ES_ACCESS,
- TFTP_ES_FULL, TFTP_ES_ILLEGALOP,
- TFTP_ES_UNKID, TFTP_ES_EXISTS,
- TFTP_ES_UNKUSER, TFTP_ES_NEGOTIATE};
- if (rblockno && (rblockno < 9)) message = arr[rblockno - 1];
- error_msg_raw(message);
- }
- else if (blockno == 1 && opcode == TFTP_OP_OACK) {
- len = mkpkt_ack(packet, 0);
- ret = write_server(sd, packet, len, &from);
- if (ret != len){
- unlink(TT.local_file);
- goto errout_with_sd;
- }
- }
- else if (opcode > 5) {
- len = mkpkt_err(packet, TFTP_ER_ILLEGALOP, TFTP_ES_ILLEGALOP);
- ret = write_server(sd, packet, len, &from);
- }
- continue;
- }
- if ((TT.af == AF_INET6) && !((struct sockaddr_in6 *)&server)->sin6_port)
- ((struct sockaddr_in6 *)&server)->sin6_port =
- ((struct sockaddr_in6 *)&from)->sin6_port;
- else if ((TT.af == AF_INET) && !((struct sockaddr_in *)&server)->sin_port)
- ((struct sockaddr_in *)&server)->sin_port =
- ((struct sockaddr_in *)&from)->sin_port;
- break;
- }
- }
- if (retry == TFTP_RETRIES) {
- error_msg("Retry limit exceeded.");
- unlink(TT.local_file);
- goto errout_with_sd;
- }
- ndatabytes = nbytesrecvd - TFTP_DATAHEADERSIZE;
- if (writeall(fd, packet + TFTP_DATAHEADERSIZE, ndatabytes) < 0){
- unlink(TT.local_file);
- goto errout_with_sd;
- }
- len = mkpkt_ack(packet, blockno);
- ret = write_server(sd, packet, len, &server);
- if (ret != len){
- unlink(TT.local_file);
- goto errout_with_sd;
- }
- } while (ndatabytes >= TFTP_DATASIZE);
- result = 0;
- errout_with_sd: xclose(sd);
- free(packet);
- return result;
- }
- // Sends file to server.
- int file_put(void)
- {
- struct sockaddr_storage server;
- uint8_t *packet;
- off_t offset = 0;
- uint16_t blockno = 1, rblockno, port = 0;
- int packetlen, sd, fd, retry = 0, ret, result = -1;
- sd = init_tftp(&server);
- packet = (uint8_t*)xzalloc(TFTP_IOBUFSIZE);
- fd = xopenro(TT.local_file);
- for (;;) { //first loop for request send and confirmation from server.
- packetlen = mkpkt_request(packet, TFTP_OP_WRQ, TT.remote_file, 1);
- ret = write_server(sd, packet, packetlen, &server);
- if (ret != packetlen) goto errout_with_sd;
- if (read_ack(sd, packet, &server, &port, NULL) == 0) break;
- if (++retry > TFTP_RETRIES) {
- error_msg("Retry count exceeded.");
- goto errout_with_sd;
- }
- }
- for (;;) { // loop for data sending and receving ack from server.
- packetlen = mkpkt_data(fd, offset, packet, blockno);
- if (packetlen < 0) goto errout_with_sd;
- ret = write_server(sd, packet, packetlen, &server);
- if (ret != packetlen) goto errout_with_sd;
- if (read_ack(sd, packet, &server, &port, &rblockno) == 0) {
- if (rblockno == blockno) {
- if (packetlen < TFTP_PACKETSIZE) break;
- blockno++;
- offset += TFTP_DATASIZE;
- retry = 0;
- continue;
- }
- }
- if (++retry > TFTP_RETRIES) {
- error_msg("Retry count exceeded.");
- goto errout_with_sd;
- }
- }
- result = 0;
- errout_with_sd: close(sd);
- free(packet);
- return result;
- }
- void tftp_main(void)
- {
- struct addrinfo *info, rp, *res=0;
- int ret;
- if (FLAG(r)) {
- if (!FLAG(l)) {
- char *slash = strrchr(TT.remote_file, '/');
- TT.local_file = (slash) ? slash + 1 : TT.remote_file;
- }
- } else if (FLAG(l)) TT.remote_file = TT.local_file;
- else error_exit("Please provide some files.");
- memset(&rp, 0, sizeof(rp));
- rp.ai_family = AF_UNSPEC;
- rp.ai_socktype = SOCK_STREAM;
- ret = getaddrinfo(toys.optargs[0], toys.optargs[1], &rp, &info);
- if (!ret) {
- for (res = info; res; res = res->ai_next)
- if ( (res->ai_family == AF_INET) || (res->ai_family == AF_INET6)) break;
- }
- if (!res)
- error_exit("bad address '%s' : %s", toys.optargs[0], gai_strerror(ret));
- TT.af = info->ai_family;
- memcpy((void *)&TT.inaddr, info->ai_addr, info->ai_addrlen);
- freeaddrinfo(info);
- if (FLAG(g)) file_get();
- if (FLAG(p)) file_put();
- }
|