You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
309 lines
9.5 KiB
309 lines
9.5 KiB
/* 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:l", TOYFLAG_BIN))
|
|
|
|
config TFTPD
|
|
bool "tftpd"
|
|
default n
|
|
help
|
|
usage: tftpd [-cr] [-u USER] [DIR]
|
|
|
|
Transfer file from/to tftp server.
|
|
|
|
-r read only
|
|
-c Allow file creation via upload
|
|
-u run as USER
|
|
-l Log to syslog (inetd mode requires this)
|
|
*/
|
|
|
|
#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_errpkt = toybuf + TFTPD_BLKSIZE;
|
|
|
|
// Create and send error packet.
|
|
static void send_errpkt(struct sockaddr *dstaddr,
|
|
socklen_t socklen, char *errmsg)
|
|
{
|
|
error_msg_raw(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) xsetuser(TT.pw);
|
|
|
|
if (opcode == TFTPD_OP_RRQ) fd = open(file, O_RDONLY, 0666);
|
|
else fd = open(file,
|
|
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_INPUT:
|
|
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_INPUT;
|
|
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_INPUT;
|
|
} 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) {
|
|
char *message = "DATA Check failure.";
|
|
char *arr[] = {"File not found", "Access violation",
|
|
"Disk full or allocation exceeded", "Illegal TFTP operation",
|
|
"Unknown transfer ID", "File already exists",
|
|
"No such user", "Terminate transfer due to option negotiation"};
|
|
|
|
if (rblockno && (rblockno < 9)) message = arr[rblockno - 1];
|
|
error_msg_raw(message);
|
|
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_INPUT;
|
|
} // end of loop
|
|
|
|
CLEAN_APP:
|
|
if (CFG_TOYBOX_FREE) {
|
|
free(spkt);
|
|
free(rpkt);
|
|
close(fd);
|
|
}
|
|
}
|
|
|
|
void tftpd_main(void)
|
|
{
|
|
int fd = 0, recvmsg_len, rbuflen, opcode, blksize = TFTPD_BLKSIZE, tsize = 0, set =1;
|
|
struct sockaddr_storage srcaddr, dstaddr;
|
|
socklen_t socklen = sizeof(struct sockaddr_storage);
|
|
char *buf = toybuf;
|
|
|
|
memset(&srcaddr, 0, sizeof(srcaddr));
|
|
if (getsockname(0, (struct sockaddr *)&srcaddr, &socklen)) help_exit(0);
|
|
|
|
if (TT.user) TT.pw = xgetpwnam(TT.user);
|
|
if (*toys.optargs) xchroot(*toys.optargs);
|
|
|
|
recvmsg_len = recvfrom(fd, toybuf, blksize, 0, (void *)&dstaddr, &socklen);
|
|
|
|
TT.sfd = xsocket(dstaddr.ss_family, SOCK_DGRAM, 0);
|
|
if (setsockopt(TT.sfd, SOL_SOCKET, SO_REUSEADDR, (const void *)&set,
|
|
sizeof(set)) < 0) perror_exit("setsockopt failed");
|
|
xbind(TT.sfd, (void *)&srcaddr, socklen);
|
|
xconnect(TT.sfd, (void *)&dstaddr, socklen);
|
|
// Error condition.
|
|
if (recvmsg_len<4 || recvmsg_len>TFTPD_BLKSIZE || toybuf[recvmsg_len-1]) {
|
|
send_errpkt((struct sockaddr*)&dstaddr, socklen, "packet format error");
|
|
return;
|
|
}
|
|
|
|
// request is either upload or Download.
|
|
opcode = buf[1];
|
|
if (((opcode != TFTPD_OP_RRQ) && (opcode != TFTPD_OP_WRQ))
|
|
|| ((opcode == TFTPD_OP_WRQ) && 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 >= toybuf+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 = toybuf + 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, toybuf + 2, opcode, tsize, blksize);
|
|
if (CFG_TOYBOX_FREE) close(0);
|
|
}
|