[Toybox] [New Toy] - Add tftpd
Ashwini Sharma
ak.ashwini at gmail.com
Fri Nov 1 04:07:53 PDT 2013
Hi Felix, list,
Thanks for your comments.
I have incorporated your comments into the tftpd.c,
attached is the modified file.
regards,
Ashwini
On Sat, Oct 26, 2013 at 7:04 PM, Felix Janda <felix.janda at posteo.de> wrote:
> Ashwini Sharma wrote:
> > Hi Rob, All,
> >
> > Attached is the TFTPD implementation.
> > Add it to hg, if you find it fine.
> >
> > Let me know for any comments/improvements.
> >
> > -Ashwini
>
> Thanks for your submission. Some comments sprinkled into the code below.
>
> Felix
>
> > /* tftpd.c - TFTP server.
> > *
> > * Copyright 2013 Ranjan Kumar <ranjankumar.bth at gmail.com>
> > * Copyright 2013 Kyungwan Han <asura321 at 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"
> > #include "toynet.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 |
> > * ----------------------------------------
> > */
>
> Thanks for the useful comments.
>
> > static char g_buff[TFTPD_BLKSIZE];
> > static char g_errpkt[TFTPD_BLKSIZE];
>
> How about using toybuf and toybuf + TFTP_BLKSIZE for this?
>
Modified code as per your suggestion.
>
> > 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");
> > }
>
> This can be inlined into tftpd_main().
>
inlined the function
>
> > // 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");
> > }
>
> Why does this function not set g_errpkt[3] (the error code)? In some cases
> before send_errpkt is called it is set up, in some cases not. In the second
> type of case I guess the error code of the previous packet would be used.
> Is that intentional?
>
> On an error, the server sends the error packet and shuts down. In places
where the error code is not set,
it is intentional to set it as ZERO. which happens due the _static_ global
declaration of the __g_errpkt__ variable.
> > // 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)
>
> This is also only used once in tftpd_main() and can be inlined. That would
> however require quite some work.
>
This is not done intentionaly, as this will result into one function being
very big, of around 300 lines.
>
> > {
> > 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;
> > }
>
> How about
>
> char *message = "DATA Check failure.";
>
> if (rblockno && (rblockno < 9)) message =
> ((char **){"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"})[rblockno - 1];
> error_msg(message);
>
this is a good suggestion, modified the code with a little modification. As
for this snippet, compiler shouts of excess
initialization for 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_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);
>
> Leave out the "static".
>
Done.
>
> > 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);
> > }
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.landley.net/pipermail/toybox-landley.net/attachments/20131101/ec4f342e/attachment-0004.htm>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: tftpd.c
Type: text/x-csrc
Size: 10390 bytes
Desc: not available
URL: <http://lists.landley.net/pipermail/toybox-landley.net/attachments/20131101/ec4f342e/attachment-0005.c>
More information about the Toybox
mailing list