diff options
-rw-r--r-- | toys/pending/tftpd.c | 336 |
1 files changed, 336 insertions, 0 deletions
diff --git a/toys/pending/tftpd.c b/toys/pending/tftpd.c new file mode 100644 index 00000000..b2e72ee6 --- /dev/null +++ b/toys/pending/tftpd.c @@ -0,0 +1,336 @@ +/* tftpd.c - TFTP server. + * + * Copyright 2013 Ranjan Kumar <ranjankumar.bth@gmail.com> + * Copyright 2013 Kyungwan Han <asura321@gmail.com> + * + * No Standard. + +USE_TFTPD(NEWTOY(tftpd, "rcu:", TOYFLAG_BIN)) + +config TFTPD + bool "tftpd" + default y + help + usage: tftpd [-cr] [-u USER] [DIR] + + Transfer file from/to tftp server. + + -r Prohibit upload + -c Allow file creation via upload + -u Access files as USER +*/ +#define FOR_tftpd +#include "toys.h" + +GLOBALS( + char *user; + long sfd; + struct passwd *pw; +) + +#define TFTPD_BLKSIZE 512 // as per RFC 1350. + +// opcodes +#define TFTPD_OP_RRQ 1 // Read Request RFC 1350, RFC 2090 +#define TFTPD_OP_WRQ 2 // Write Request RFC 1350 +#define TFTPD_OP_DATA 3 // Data chunk RFC 1350 +#define TFTPD_OP_ACK 4 // Acknowledgement RFC 1350 +#define TFTPD_OP_ERR 5 // Error Message RFC 1350 +#define TFTPD_OP_OACK 6 // Option acknowledgment RFC 2347 + +// Error Codes: +#define TFTPD_ER_NOSUCHFILE 1 // File not found +#define TFTPD_ER_ACCESS 2 // Access violation +#define TFTPD_ER_FULL 3 // Disk full or allocation exceeded +#define TFTPD_ER_ILLEGALOP 4 // Illegal TFTP operation +#define TFTPD_ER_UNKID 5 // Unknown transfer ID +#define TFTPD_ER_EXISTS 6 // File already exists +#define TFTPD_ER_UNKUSER 7 // No such user +#define TFTPD_ER_NEGOTIATE 8 // Terminate transfer due to option negotiation + +/* TFTP Packet Formats + * Type Op # Format without header + * 2 bytes string 1 byte string 1 byte + * ----------------------------------------------- + * RRQ/ | 01/02 | Filename | 0 | Mode | 0 | + * WRQ ----------------------------------------------- + * 2 bytes 2 bytes n bytes + * --------------------------------- + * DATA | 03 | Block # | Data | + * --------------------------------- + * 2 bytes 2 bytes + * ------------------- + * ACK | 04 | Block # | + * -------------------- + * 2 bytes 2 bytes string 1 byte + * ---------------------------------------- + * ERROR | 05 | ErrorCode | ErrMsg | 0 | + * ---------------------------------------- + */ + +static char g_buff[TFTPD_BLKSIZE]; +static char g_errpkt[TFTPD_BLKSIZE]; + +static void bind_and_connect(struct sockaddr *srcaddr, + struct sockaddr *dstaddr, socklen_t socklen) +{ + int set = 1; + + TT.sfd = xsocket(dstaddr->sa_family, SOCK_DGRAM, 0); + if (setsockopt(TT.sfd, SOL_SOCKET, SO_REUSEADDR, (const void *)&set, + sizeof(set)) < 0) perror_exit("setsockopt failed"); + if (bind(TT.sfd, srcaddr, socklen)) perror_exit("bind"); + if (connect(TT.sfd, dstaddr, socklen) < 0) + perror_exit("can't connect to remote host"); +} + +// Create and send error packet. +static void send_errpkt(struct sockaddr *dstaddr, + socklen_t socklen, char *errmsg) +{ + error_msg(errmsg); + g_errpkt[1] = TFTPD_OP_ERR; + strcpy(g_errpkt + 4, errmsg); + if (sendto(TT.sfd, g_errpkt, strlen(errmsg)+5, 0, dstaddr, socklen) < 0) + perror_exit("sendto failed"); +} + +// Used to send / receive packets. +static void do_action(struct sockaddr *srcaddr, struct sockaddr *dstaddr, + socklen_t socklen, char *file, int opcode, int tsize, int blksize) +{ + int fd, done = 0, retry_count = 12, timeout = 100, len; + uint16_t blockno = 1, pktopcode, rblockno; + char *ptr, *spkt, *rpkt; + struct pollfd pollfds[1]; + + spkt = xzalloc(blksize + 4); + rpkt = xzalloc(blksize + 4); + ptr = spkt+2; //point after opcode. + + pollfds[0].fd = TT.sfd; + // initialize groups, setgid and setuid + if (TT.pw) { + if (change_identity(TT.pw)) perror_exit("Failed to change identity"); + endgrent(); + } + + if (opcode == TFTPD_OP_RRQ) fd = open(file, O_RDONLY, 0666); + else fd = open(file, ((toys.optflags & FLAG_c) ? + (O_WRONLY|O_TRUNC|O_CREAT) : (O_WRONLY|O_TRUNC)) , 0666); + if (fd < 0) { + g_errpkt[3] = TFTPD_ER_NOSUCHFILE; + send_errpkt(dstaddr, socklen, "can't open file"); + goto CLEAN_APP; + } + // For download -> blockno will be 1. + // 1st ACK will be from dst,which will have blockno-=1 + // Create and send ACK packet. + if (blksize != TFTPD_BLKSIZE || tsize) { + pktopcode = TFTPD_OP_OACK; + // add "blksize\000blksize_val\000" in send buffer. + if (blksize != TFTPD_BLKSIZE) { + strcpy(ptr, "blksize"); + ptr += strlen("blksize") + 1; + ptr += snprintf(ptr, 6, "%d", blksize) + 1; + } + if (tsize) {// add "tsize\000tsize_val\000" in send buffer. + struct stat sb; + + sb.st_size = 0; + fstat(fd, &sb); + strcpy(ptr, "tsize"); + ptr += strlen("tsize") + 1; + ptr += sprintf(ptr, "%lu", (unsigned long)sb.st_size)+1; + } + goto SEND_PKT; + } + // upload -> ACK 1st packet with filename, as it has blockno 0. + if (opcode == TFTPD_OP_WRQ) blockno = 0; + + // Prepare DATA and/or ACK pkt and send it. + for (;;) { + int poll_ret; + + retry_count = 12, timeout = 100, pktopcode = TFTPD_OP_ACK; + ptr = spkt+2; + *((uint16_t*)ptr) = htons(blockno); + blockno++; + ptr += 2; + if (opcode == TFTPD_OP_RRQ) { + pktopcode = TFTPD_OP_DATA; + len = readall(fd, ptr, blksize); + if (len < 0) { + send_errpkt(dstaddr, socklen, "read-error"); + break; + } + if (len != blksize) done = 1; //last pkt. + ptr += len; + } +SEND_PKT: + // 1st ACK will be from dst, which will have blockno-=1 + *((uint16_t*)spkt) = htons(pktopcode); //append send pkt's opcode. +RETRY_SEND: + if (sendto(TT.sfd, spkt, (ptr - spkt), 0, dstaddr, socklen) <0) + perror_exit("sendto failed"); + // if "block size < 512", send ACK and exit. + if ((pktopcode == TFTPD_OP_ACK) && done) break; + +POLL_IN: + pollfds[0].events = POLLIN; + pollfds[0].fd = TT.sfd; + poll_ret = poll(pollfds, 1, timeout); + if (poll_ret < 0 && (errno == EINTR || errno == ENOMEM)) goto POLL_IN; + if (!poll_ret) { + if (!--retry_count) { + error_msg("timeout"); + break; + } + timeout += 150; + goto RETRY_SEND; + } else if (poll_ret == 1) { + len = read(pollfds[0].fd, rpkt, blksize + 4); + if (len < 0) { + send_errpkt(dstaddr, socklen, "read-error"); + break; + } + if (len < 4) goto POLL_IN; + } else { + perror_msg("poll"); + break; + } + // Validate receive packet. + pktopcode = ntohs(((uint16_t*)rpkt)[0]); + rblockno = ntohs(((uint16_t*)rpkt)[1]); + if (pktopcode == TFTPD_OP_ERR) { + switch(rblockno) { + case TFTPD_ER_NOSUCHFILE: error_msg("File not found"); break; + case TFTPD_ER_ACCESS: error_msg("Access violation"); break; + case TFTPD_ER_FULL: error_msg("Disk full or allocation exceeded"); + break; + case TFTPD_ER_ILLEGALOP: error_msg("Illegal TFTP operation"); break; + case TFTPD_ER_UNKID: error_msg("Unknown transfer ID"); break; + case TFTPD_ER_EXISTS: error_msg("File already exists"); break; + case TFTPD_ER_UNKUSER: error_msg("No such user"); break; + case TFTPD_ER_NEGOTIATE: + error_msg("Terminate transfer due to option negotiation"); break; + default: error_msg("DATA Check failure."); break; + } + break; // Break the for loop. + } + + // if download requested by client, + // server will send data pkt and will receive ACK pkt from client. + if ((opcode == TFTPD_OP_RRQ) && (pktopcode == TFTPD_OP_ACK)) { + if (rblockno == (uint16_t) (blockno - 1)) { + if (!done) continue; // Send next chunk of data. + break; + } + } + + // server will receive DATA pkt and write the data. + if ((opcode == TFTPD_OP_WRQ) && (pktopcode == TFTPD_OP_DATA)) { + if (rblockno == blockno) { + int nw = writeall(fd, &rpkt[4], len-4); + if (nw != len-4) { + g_errpkt[3] = TFTPD_ER_FULL; + send_errpkt(dstaddr, socklen, "write error"); + break; + } + + if (nw != blksize) done = 1; + } + continue; + } + goto POLL_IN; + } // end of loop + +CLEAN_APP: + if (CFG_TOYBOX_FREE) { + free(spkt); + free(rpkt); + close(fd); + } +} + +void tftpd_main(void) +{ + int recvmsg_len, rbuflen, opcode, blksize = TFTPD_BLKSIZE, tsize = 0; + struct sockaddr_storage srcaddr, dstaddr; + static socklen_t socklen = sizeof(struct sockaddr_storage); + char *buf = g_buff; + + TT.pw = NULL; + memset(&srcaddr, 0, sizeof(srcaddr)); + if (getsockname(STDIN_FILENO, (struct sockaddr*)&srcaddr, &socklen)) { + toys.exithelp = 1; + error_exit(NULL); + } + + if (toys.optflags & FLAG_u) { + struct passwd *pw = getpwnam(TT.user); + if (!pw) error_exit("unknown user %s", TT.user); + TT.pw = pw; + } + if (*toys.optargs) { + if (chroot(*toys.optargs)) + perror_exit("can't change root directory to '%s'", *toys.optargs); + if (chdir("/")) perror_exit("can't change directory to '/'"); + } + + recvmsg_len = recvfrom(STDIN_FILENO, g_buff, TFTPD_BLKSIZE, 0, + (struct sockaddr*)&dstaddr, &socklen); + bind_and_connect((struct sockaddr*)&srcaddr, (struct sockaddr*)&dstaddr, socklen); + // Error condition. + if (recvmsg_len < 4 || recvmsg_len > TFTPD_BLKSIZE + || g_buff[recvmsg_len-1] != '\0') { + send_errpkt((struct sockaddr*)&dstaddr, socklen, "packet format error"); + return; + } + + // request is either upload or Download. + opcode = ntohs(*(uint16_t*)buf); + if (((opcode != TFTPD_OP_RRQ) && (opcode != TFTPD_OP_WRQ)) + || ((opcode == TFTPD_OP_WRQ) && (toys.optflags & FLAG_r))) { + send_errpkt((struct sockaddr*)&dstaddr, socklen, + ((opcode == TFTPD_OP_WRQ) ? "write error" : "packet format error")); + return; + } + + buf += 2; + if (*buf == '.' || strstr(buf, "/.")) { + send_errpkt((struct sockaddr*)&dstaddr, socklen, "dot in filename"); + return; + } + + buf += strlen(buf) + 1; //1 '\0'. + // As per RFC 1350, mode is case in-sensitive. + if ((buf >= (g_buff + recvmsg_len)) || (strcasecmp(buf, "octet"))) { + send_errpkt((struct sockaddr*)&dstaddr, socklen, "packet format error"); + return; + } + + //RFC2348. e.g. of size type: "ttype1\0ttype1_val\0...ttypeN\0ttypeN_val\0" + buf += strlen(buf) + 1; + rbuflen = g_buff + recvmsg_len - buf; + if (rbuflen) { + int jump = 0, bflag = 0; + + for (; rbuflen; rbuflen -= jump, buf += jump) { + if (!bflag && !strcasecmp(buf, "blksize")) { //get blksize + errno = 0; + blksize = strtoul(buf, NULL, 10); + if (errno || blksize > 65564 || blksize < 8) blksize = TFTPD_BLKSIZE; + bflag ^= 1; + } else if (!tsize && !strcasecmp(buf, "tsize")) tsize ^= 1; + + jump += strlen(buf) + 1; + } + tsize &= (opcode == TFTPD_OP_RRQ); + } + + //do send / receive file. + do_action((struct sockaddr*)&srcaddr, (struct sockaddr*)&dstaddr, + socklen, g_buff + 2, opcode, tsize, blksize); + if (CFG_TOYBOX_FREE) close(STDIN_FILENO); +} |