| /* 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; |
| } |