aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--toys/pending/tftpd.c336
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);
+}