master xplshn/aruu / cmd / net / tftp.c
  1/* See LICENSE file for copyright and license details. */
  2
  3
  4#include <sys/time.h>
  5#include <sys/types.h>
  6#include <sys/socket.h>
  7
  8#include <netdb.h>
  9#include <netinet/in.h>
 10
 11#include <errno.h>
 12#include <stdio.h>
 13#include <stdlib.h>
 14#include <string.h>
 15#include <unistd.h>
 16
 17#include "util.h"
 18
 19#define BLKSIZE 512
 20#define HDRSIZE 4
 21#define PKTSIZE (BLKSIZE + HDRSIZE)
 22
 23#define TIMEOUT_SEC 5
 24/* transfer will time out after NRETRIES * TIMEOUT_SEC */
 25#define NRETRIES 5
 26
 27#define RRQ  1
 28#define WWQ  2
 29#define DATA 3
 30#define ACK  4
 31#define ERR  5
 32
 33static char *errtext[] = {
 34	"Undefined",
 35	"File not found",
 36	"Access violation",
 37	"Disk full or allocation exceeded",
 38	"Illegal TFTP operation",
 39	"Unknown transfer ID",
 40	"File already exists",
 41	"No such user"
 42};
 43
 44static struct sockaddr_storage to;
 45static socklen_t tolen;
 46static int timeout;
 47static int state;
 48static int s;
 49
 50static int
 51packreq(unsigned char *buf, int op, char *path, char *mode)
 52{
 53	unsigned char *p = buf;
 54
 55	*p++ = op >> 8;
 56	*p++ = op & 0xff;
 57	if (strlen(path) + 1 > 256)
 58		eprintf("filename too long\n");
 59	memcpy(p, path, strlen(path) + 1);
 60	p += strlen(path) + 1;
 61	memcpy(p, mode, strlen(mode) + 1);
 62	p += strlen(mode) + 1;
 63	return p - buf;
 64}
 65
 66static int
 67packack(unsigned char *buf, int blkno)
 68{
 69	buf[0] = ACK >> 8;
 70	buf[1] = ACK & 0xff;
 71	buf[2] = blkno >> 8;
 72	buf[3] = blkno & 0xff;
 73	return 4;
 74}
 75
 76static int
 77packdata(unsigned char *buf, int blkno)
 78{
 79	buf[0] = DATA >> 8;
 80	buf[1] = DATA & 0xff;
 81	buf[2] = blkno >> 8;
 82	buf[3] = blkno & 0xff;
 83	return 4;
 84}
 85
 86static int
 87unpackop(unsigned char *buf)
 88{
 89	return (buf[0] << 8) | (buf[1] & 0xff);
 90}
 91
 92static int
 93unpackblkno(unsigned char *buf)
 94{
 95	return (buf[2] << 8) | (buf[3] & 0xff);
 96}
 97
 98static int
 99unpackerrc(unsigned char *buf)
100{
101	int errc;
102
103	errc = (buf[2] << 8) | (buf[3] & 0xff);
104	if (errc < 0 || (size_t)errc >= LEN(errtext))
105		eprintf("bad error code: %d\n", errc);
106	return errc;
107}
108
109static int
110writepkt(unsigned char *buf, int len)
111{
112	int n;
113
114	n = sendto(s, buf, len, 0, (struct sockaddr *)&to,
115	           tolen);
116	if (n < 0)
117		if (errno != EINTR)
118			eprintf("sendto:");
119	return n;
120}
121
122static int
123readpkt(unsigned char *buf, int len)
124{
125	int n;
126
127	n = recvfrom(s, buf, len, 0, (struct sockaddr *)&to,
128	             &tolen);
129	if (n < 0) {
130		if (errno != EINTR && errno != EWOULDBLOCK)
131			eprintf("recvfrom:");
132		timeout++;
133		if (timeout == NRETRIES)
134			eprintf("transfer timed out\n");
135	} else {
136		timeout = 0;
137	}
138	return n;
139}
140
141static void
142getfile(char *file)
143{
144	unsigned char buf[PKTSIZE];
145	int n, op, blkno, nextblkno = 1, done = 0;
146
147	state = RRQ;
148	for (;;) {
149		switch (state) {
150		case RRQ:
151			n = packreq(buf, RRQ, file, "octet");
152			writepkt(buf, n);
153			n = readpkt(buf, sizeof(buf));
154			if (n > 0) {
155				op = unpackop(buf);
156				if (op != DATA && op != ERR)
157					eprintf("bad opcode: %d\n", op);
158				state = op;
159			}
160			break;
161		case DATA:
162			n -= HDRSIZE;
163			if (n < 0)
164				eprintf("truncated packet\n");
165			blkno = unpackblkno(buf);
166			if (blkno == nextblkno) {
167				nextblkno++;
168				write(1, &buf[HDRSIZE], n);
169			}
170			if (n < BLKSIZE)
171				done = 1;
172			state = ACK;
173			break;
174		case ACK:
175			n = packack(buf, blkno);
176			writepkt(buf, n);
177			if (done)
178				return;
179			n = readpkt(buf, sizeof(buf));
180			if (n > 0) {
181				op = unpackop(buf);
182				if (op != DATA && op != ERR)
183					eprintf("bad opcode: %d\n", op);
184				state = op;
185			}
186			break;
187		case ERR:
188			eprintf("error: %s\n", errtext[unpackerrc(buf)]);
189		}
190	}
191}
192
193static void
194putfile(char *file)
195{
196	unsigned char inbuf[PKTSIZE], outbuf[PKTSIZE];
197	int inb, outb, op, blkno, nextblkno = 0, done = 0;
198
199	state = WWQ;
200	for (;;) {
201		switch (state) {
202		case WWQ:
203			outb = packreq(outbuf, WWQ, file, "octet");
204			writepkt(outbuf, outb);
205			inb = readpkt(inbuf, sizeof(inbuf));
206			if (inb > 0) {
207				op = unpackop(inbuf);
208				if (op != ACK && op != ERR)
209					eprintf("bad opcode: %d\n", op);
210				state = op;
211			}
212			break;
213		case DATA:
214			if (blkno == nextblkno) {
215				nextblkno++;
216				packdata(outbuf, nextblkno);
217				outb = read(0, &outbuf[HDRSIZE], BLKSIZE);
218				if (outb < BLKSIZE)
219					done = 1;
220			}
221			writepkt(outbuf, outb + HDRSIZE);
222			inb = readpkt(inbuf, sizeof(inbuf));
223			if (inb > 0) {
224				op = unpackop(inbuf);
225				if (op != ACK && op != ERR)
226					eprintf("bad opcode: %d\n", op);
227				state = op;
228			}
229			break;
230		case ACK:
231			if (inb < HDRSIZE)
232				eprintf("truncated packet\n");
233			blkno = unpackblkno(inbuf);
234			if (blkno == nextblkno)
235				if (done)
236					return;
237			state = DATA;
238			break;
239		case ERR:
240			eprintf("error: %s\n", errtext[unpackerrc(inbuf)]);
241		}
242	}
243}
244
245static void
246usage(void)
247{
248	eprintf("usage: %s -h host [-p port] [-x | -c] file\n", argv0);
249}
250
251// ?man tftp: tftp client
252// ?man arguments: -h host file
253// ?man transfer files to and from a remote tftp server
254int
255main(int argc, char *argv[])
256{
257	struct addrinfo hints, *res, *r;
258	struct timeval tv;
259	char *host = NULL, *port = "tftp";
260	void (*fn)(char *) = getfile;
261	int ret;
262
263	ARGBEGIN {
264	// ?man -h:str: suppress headers or print help
265	case 'h':
266		host = EARGF(usage());
267		break;
268	// ?man -p:str: preserve file attributes
269	case 'p':
270		port = EARGF(usage());
271		break;
272	// ?man -x: hex format or match whole lines
273	case 'x':
274		fn = getfile;
275		break;
276	// ?man -c: print count or perform stdout action
277	case 'c':
278		fn = putfile;
279		break;
280	default:
281		usage();
282	} ARGEND
283
284	if (!host || !argc)
285		usage();
286
287	memset(&hints, 0, sizeof(hints));
288	hints.ai_family = AF_UNSPEC;
289	hints.ai_socktype = SOCK_DGRAM;
290	hints.ai_protocol = IPPROTO_UDP;
291	ret = getaddrinfo(host, port, &hints, &res);
292	if (ret)
293		eprintf("getaddrinfo: %s\n", gai_strerror(ret));
294
295	for (r = res; r; r = r->ai_next) {
296		if (r->ai_family != AF_INET &&
297		    r->ai_family != AF_INET6)
298			continue;
299		s = socket(r->ai_family, r->ai_socktype,
300		           r->ai_protocol);
301		if (s < 0)
302			continue;
303		break;
304	}
305	if (!r)
306		eprintf("cannot create socket\n");
307	memcpy(&to, r->ai_addr, r->ai_addrlen);
308	tolen = r->ai_addrlen;
309	freeaddrinfo(res);
310
311	tv.tv_sec = TIMEOUT_SEC;
312	tv.tv_usec = 0;
313	if (setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0)
314		eprintf("setsockopt:");
315
316	fn(argv[0]);
317	return 0;
318}