/* 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 ssize_t read_server(int sd, void *buf, size_t 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; ssize_t 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(message); } 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 (toys.optflags & FLAG_r) { if (!(toys.optflags & FLAG_l)) { char *slash = strrchr(TT.remote_file, '/'); TT.local_file = (slash) ? slash + 1 : TT.remote_file; } } else if (toys.optflags & 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 (toys.optflags & FLAG_g) file_get(); if (toys.optflags & FLAG_p) file_put(); }