diff options
Diffstat (limited to 'util/sbase/tftp.c')
-rw-r--r-- | util/sbase/tftp.c | 309 |
1 files changed, 309 insertions, 0 deletions
diff --git a/util/sbase/tftp.c b/util/sbase/tftp.c new file mode 100644 index 00000000..0a099ff2 --- /dev/null +++ b/util/sbase/tftp.c @@ -0,0 +1,309 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/time.h> +#include <sys/types.h> +#include <sys/socket.h> + +#include <netdb.h> +#include <netinet/in.h> + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#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; +} |