diff --git a/Makefile b/Makefile index 7194ade..d5d230b 100644 --- a/Makefile +++ b/Makefile @@ -139,6 +139,7 @@ BIN =\ tar\ tee\ test\ + tftp\ time\ touch\ tr\ diff --git a/README b/README index 330e699..5c5491d 100644 --- a/README +++ b/README @@ -78,6 +78,7 @@ The following tools are implemented: =*|x tar . =*|o tee . =*|o test . +=* x tftp . =*|o time . =*|o touch . #*|o tr . diff --git a/tftp.1 b/tftp.1 new file mode 100644 index 0000000..6224381 --- /dev/null +++ b/tftp.1 @@ -0,0 +1,29 @@ +.Dd August 14, 2015 +.Dt TFTP 1 +.Os sbase +.Sh NAME +.Nm tftp +.Nd trivial file transfer protocol client +.Sh SYNOPSIS +.Nm +.Fl h Ar host +.Op Fl p Ar port +.Op Fl x | c +.Ar file +.Sh DESCRIPTION +.Nm +is a client that implements the trivial file transfer protocol over +either IPv4 or IPv6 as specified in RFC 1350. It can be used to transfer +files to and from remote machines. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl h Ar host +Set the remote hostname. +.It Fl p Ar port +Set the remote port. It defaults to port 69. +.It Fl x +Extract a file from the server. This is the default +if no flags are specified. Output goes to stdout. +.It Fl c +Create a file on the server. Input comes from stdin. +.El diff --git a/tftp.c b/tftp.c new file mode 100644 index 0000000..4073700 --- /dev/null +++ b/tftp.c @@ -0,0 +1,308 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include "util.h" + +#define BLKSIZE 512 +#define HDRSIZE 4 +#define PKTSIZE (BLKSIZE + HDRSIZE) + +#define TIMEOUT_SEC 5 +/* transfer will time out after NRETRIES * TIMEOUT_SEC */ +#define NRETRIES 5 + +#define RRQ 1 +#define WWQ 2 +#define DATA 3 +#define ACK 4 +#define ERR 5 + +static char *errtext[] = { + "Undefined", + "File not found", + "Access violation", + "Disk full or allocation exceeded", + "Illegal TFTP operation", + "Unknown transfer ID", + "File already exists", + "No such user" +}; + +static struct sockaddr_storage to; +static socklen_t tolen; +static int timeout; +static int state; +static int s; + +static int +packreq(unsigned char *buf, int op, char *path, char *mode) +{ + unsigned char *p = buf; + + *p++ = op >> 8; + *p++ = op & 0xff; + if (strlen(path) + 1 > 256) + eprintf("filename too long\n"); + memcpy(p, path, strlen(path) + 1); + p += strlen(path) + 1; + memcpy(p, mode, strlen(mode) + 1); + p += strlen(mode) + 1; + return p - buf; +} + +static int +packack(unsigned char *buf, int blkno) +{ + buf[0] = ACK >> 8; + buf[1] = ACK & 0xff; + buf[2] = blkno >> 8; + buf[3] = blkno & 0xff; + return 4; +} + +static int +packdata(unsigned char *buf, int blkno) +{ + buf[0] = DATA >> 8; + buf[1] = DATA & 0xff; + buf[2] = blkno >> 8; + buf[3] = blkno & 0xff; + return 4; +} + +static int +unpackop(unsigned char *buf) +{ + return (buf[0] << 8) | (buf[1] & 0xff); +} + +static int +unpackblkno(unsigned char *buf) +{ + return (buf[2] << 8) | (buf[3] & 0xff); +} + +static int +unpackerrc(unsigned char *buf) +{ + int errc; + + errc = (buf[2] << 8) | (buf[3] & 0xff); + if (errc < 0 || errc >= LEN(errtext)) + eprintf("bad error code: %d\n", errc); + return errc; +} + +static int +writepkt(unsigned char *buf, int len) +{ + int n; + + n = sendto(s, buf, len, 0, (struct sockaddr *)&to, + tolen); + if (n < 0) + if (errno != EINTR) + eprintf("sendto:"); + return n; +} + +static int +readpkt(unsigned char *buf, int len) +{ + int n; + + n = recvfrom(s, buf, len, 0, (struct sockaddr *)&to, + &tolen); + if (n < 0) { + if (errno != EINTR && errno != EWOULDBLOCK) + eprintf("recvfrom:"); + timeout++; + if (timeout == NRETRIES) + eprintf("transfer timed out\n"); + } else { + timeout = 0; + } + return n; +} + +static void +getfile(char *file) +{ + unsigned char buf[PKTSIZE]; + int n, op, blkno, nextblkno = 1, done = 0; + + state = RRQ; + for (;;) { + switch (state) { + case RRQ: + n = packreq(buf, RRQ, file, "octet"); + writepkt(buf, n); + n = readpkt(buf, sizeof(buf)); + if (n > 0) { + op = unpackop(buf); + if (op != DATA && op != ERR) + eprintf("bad opcode: %d\n", op); + state = op; + } + break; + case DATA: + n -= HDRSIZE; + if (n < 0) + eprintf("truncated packet\n"); + blkno = unpackblkno(buf); + if (blkno == nextblkno) { + nextblkno++; + write(1, &buf[HDRSIZE], n); + } + if (n < BLKSIZE) + done = 1; + state = ACK; + break; + case ACK: + n = packack(buf, blkno); + writepkt(buf, n); + if (done) + return; + n = readpkt(buf, sizeof(buf)); + if (n > 0) { + op = unpackop(buf); + if (op != DATA && op != ERR) + eprintf("bad opcode: %d\n", op); + state = op; + } + break; + case ERR: + eprintf("error: %s\n", errtext[unpackerrc(buf)]); + } + } +} + +static void +putfile(char *file) +{ + unsigned char inbuf[PKTSIZE], outbuf[PKTSIZE]; + int inb, outb, op, blkno, nextblkno = 0, done = 0; + + state = WWQ; + for (;;) { + switch (state) { + case WWQ: + outb = packreq(outbuf, WWQ, file, "octet"); + writepkt(outbuf, outb); + inb = readpkt(inbuf, sizeof(inbuf)); + if (inb > 0) { + op = unpackop(inbuf); + if (op != ACK && op != ERR) + eprintf("bad opcode: %d\n", op); + state = op; + } + break; + case DATA: + if (blkno == nextblkno) { + nextblkno++; + packdata(outbuf, nextblkno); + outb = read(0, &outbuf[HDRSIZE], BLKSIZE); + if (outb < BLKSIZE) + done = 1; + } + writepkt(outbuf, outb + HDRSIZE); + inb = readpkt(inbuf, sizeof(inbuf)); + if (inb > 0) { + op = unpackop(inbuf); + if (op != ACK && op != ERR) + eprintf("bad opcode: %d\n", op); + state = op; + } + break; + case ACK: + if (inb < HDRSIZE) + eprintf("truncated packet\n"); + blkno = unpackblkno(inbuf); + if (blkno == nextblkno) + if (done) + return; + state = DATA; + break; + case ERR: + eprintf("error: %s\n", errtext[unpackerrc(inbuf)]); + } + } +} + +static void +usage(void) +{ + eprintf("usage: %s -h host [-p port] [-x | -c] file\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + struct addrinfo hints, *res, *r; + struct timeval tv; + char *host = NULL, *port = "tftp"; + void (*fn)(char *) = getfile; + int ret; + + ARGBEGIN { + case 'h': + host = EARGF(usage()); + break; + case 'p': + port = EARGF(usage()); + break; + case 'x': + fn = getfile; + break; + case 'c': + fn = putfile; + break; + default: + usage(); + } ARGEND; + + if (!host || !argc) + usage(); + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_protocol = IPPROTO_UDP; + ret = getaddrinfo(host, port, &hints, &res); + if (ret) + eprintf("getaddrinfo: %s\n", gai_strerror(ret)); + + for (r = res; r; r = r->ai_next) { + if (r->ai_family != AF_INET && + r->ai_family != AF_INET6) + continue; + s = socket(r->ai_family, r->ai_socktype, + r->ai_protocol); + if (s < 0) + continue; + break; + } + if (!r) + eprintf("cannot create socket\n"); + memcpy(&to, r->ai_addr, r->ai_addrlen); + tolen = r->ai_addrlen; + freeaddrinfo(res); + + tv.tv_sec = TIMEOUT_SEC; + tv.tv_usec = 0; + if (setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0) + eprintf("setsockopt:"); + + fn(argv[0]); + return 0; +}