tftp.c revision 1.8
1/*	$NetBSD: tftp.c,v 1.8 1998/07/26 15:26:18 mycroft Exp $	*/
2
3/*
4 * Copyright (c) 1983, 1993
5 *	The Regents of the University of California.  All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 * 3. All advertising materials mentioning features or use of this software
16 *    must display the following acknowledgement:
17 *	This product includes software developed by the University of
18 *	California, Berkeley and its contributors.
19 * 4. Neither the name of the University nor the names of its contributors
20 *    may be used to endorse or promote products derived from this software
21 *    without specific prior written permission.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
34 */
35
36#include <sys/cdefs.h>
37#ifndef lint
38#if 0
39static char sccsid[] = "@(#)tftp.c	8.1 (Berkeley) 6/6/93";
40#else
41__RCSID("$NetBSD: tftp.c,v 1.8 1998/07/26 15:26:18 mycroft Exp $");
42#endif
43#endif /* not lint */
44
45/* Many bug fixes are from Jim Guyton <guyton@rand-unix> */
46
47/*
48 * TFTP User Program -- Protocol Machines
49 */
50#include <sys/types.h>
51#include <sys/socket.h>
52#include <sys/time.h>
53
54#include <netinet/in.h>
55
56#include <arpa/tftp.h>
57
58#include <err.h>
59#include <errno.h>
60#include <setjmp.h>
61#include <signal.h>
62#include <stdio.h>
63#include <string.h>
64#include <unistd.h>
65
66#include "extern.h"
67#include "tftpsubs.h"
68
69extern	int errno;
70
71extern  struct sockaddr_in peeraddr;	/* filled in by main */
72extern  int     f;			/* the opened socket */
73extern  int     trace;
74extern  int     verbose;
75extern  int     rexmtval;
76extern  int     maxtimeout;
77
78#define PKTSIZE    SEGSIZE+4
79char    ackbuf[PKTSIZE];
80int	timeout;
81jmp_buf	toplevel;
82jmp_buf	timeoutbuf;
83
84static void nak __P((int));
85static int makerequest __P((int, const char *, struct tftphdr *, const char *));
86static void printstats __P((const char *, unsigned long));
87static void startclock __P((void));
88static void stopclock __P((void));
89static void timer __P((int));
90static void tpacket __P((const char *, struct tftphdr *, int));
91
92/*
93 * Send the requested file.
94 */
95void
96sendfile(fd, name, mode)
97	int fd;
98	char *name;
99	char *mode;
100{
101	struct tftphdr *ap;	   /* data and ack packets */
102	struct tftphdr *dp;
103	int n;
104	volatile int block, size, convert;
105	volatile unsigned long amount;
106	struct sockaddr_in from;
107	int fromlen;
108	FILE *file;
109
110	startclock();		/* start stat's clock */
111	dp = r_init();		/* reset fillbuf/read-ahead code */
112	ap = (struct tftphdr *)ackbuf;
113	file = fdopen(fd, "r");
114	convert = !strcmp(mode, "netascii");
115	block = 0;
116	amount = 0;
117
118	signal(SIGALRM, timer);
119	do {
120		if (block == 0)
121			size = makerequest(WRQ, name, dp, mode) - 4;
122		else {
123		/*	size = read(fd, dp->th_data, SEGSIZE);	 */
124			size = readit(file, &dp, convert);
125			if (size < 0) {
126				nak(errno + 100);
127				break;
128			}
129			dp->th_opcode = htons((u_short)DATA);
130			dp->th_block = htons((u_short)block);
131		}
132		timeout = 0;
133		(void) setjmp(timeoutbuf);
134send_data:
135		if (trace)
136			tpacket("sent", dp, size + 4);
137		n = sendto(f, dp, size + 4, 0,
138		    (struct sockaddr *)&peeraddr, sizeof(peeraddr));
139		if (n != size + 4) {
140			warn("sendto");
141			goto abort;
142		}
143		read_ahead(file, convert);
144		for ( ; ; ) {
145			alarm(rexmtval);
146			do {
147				fromlen = sizeof(from);
148				n = recvfrom(f, ackbuf, sizeof(ackbuf), 0,
149				    (struct sockaddr *)&from, &fromlen);
150			} while (n <= 0);
151			alarm(0);
152			if (n < 0) {
153				warn("recvfrom");
154				goto abort;
155			}
156			peeraddr.sin_port = from.sin_port;	/* added */
157			if (trace)
158				tpacket("received", ap, n);
159			/* should verify packet came from server */
160			ap->th_opcode = ntohs(ap->th_opcode);
161			ap->th_block = ntohs(ap->th_block);
162			if (ap->th_opcode == ERROR) {
163				printf("Error code %d: %s\n", ap->th_code,
164					ap->th_msg);
165				goto abort;
166			}
167			if (ap->th_opcode == ACK) {
168				int j;
169
170				if (ap->th_block == block) {
171					break;
172				}
173				/* On an error, try to synchronize
174				 * both sides.
175				 */
176				j = synchnet(f);
177				if (j && trace) {
178					printf("discarded %d packets\n",
179							j);
180				}
181				if (ap->th_block == (block-1)) {
182					goto send_data;
183				}
184			}
185		}
186		if (block > 0)
187			amount += size;
188		block++;
189	} while (size == SEGSIZE || block == 1);
190abort:
191	fclose(file);
192	stopclock();
193	if (amount > 0)
194		printstats("Sent", amount);
195}
196
197/*
198 * Receive a file.
199 */
200void
201recvfile(fd, name, mode)
202	int fd;
203	char *name;
204	char *mode;
205{
206	struct tftphdr *ap;
207	struct tftphdr *dp;
208	int n;
209	volatile int block, size, firsttrip;
210	volatile unsigned long amount;
211	struct sockaddr_in from;
212	int fromlen;
213	FILE *file;
214	volatile int convert;		/* true if converting crlf -> lf */
215
216	startclock();
217	dp = w_init();
218	ap = (struct tftphdr *)ackbuf;
219	file = fdopen(fd, "w");
220	convert = !strcmp(mode, "netascii");
221	block = 1;
222	firsttrip = 1;
223	amount = 0;
224
225	signal(SIGALRM, timer);
226	do {
227		if (firsttrip) {
228			size = makerequest(RRQ, name, ap, mode);
229			firsttrip = 0;
230		} else {
231			ap->th_opcode = htons((u_short)ACK);
232			ap->th_block = htons((u_short)(block));
233			size = 4;
234			block++;
235		}
236		timeout = 0;
237		(void) setjmp(timeoutbuf);
238send_ack:
239		if (trace)
240			tpacket("sent", ap, size);
241		if (sendto(f, ackbuf, size, 0, (struct sockaddr *)&peeraddr,
242		    sizeof(peeraddr)) != size) {
243			alarm(0);
244			warn("sendto");
245			goto abort;
246		}
247		write_behind(file, convert);
248		for ( ; ; ) {
249			alarm(rexmtval);
250			do  {
251				fromlen = sizeof(from);
252				n = recvfrom(f, dp, PKTSIZE, 0,
253				    (struct sockaddr *)&from, &fromlen);
254			} while (n <= 0);
255			alarm(0);
256			if (n < 0) {
257				warn("recvfrom");
258				goto abort;
259			}
260			peeraddr.sin_port = from.sin_port;	/* added */
261			if (trace)
262				tpacket("received", dp, n);
263			/* should verify client address */
264			dp->th_opcode = ntohs(dp->th_opcode);
265			dp->th_block = ntohs(dp->th_block);
266			if (dp->th_opcode == ERROR) {
267				printf("Error code %d: %s\n", dp->th_code,
268					dp->th_msg);
269				goto abort;
270			}
271			if (dp->th_opcode == DATA) {
272				int j;
273
274				if (dp->th_block == block) {
275					break;		/* have next packet */
276				}
277				/* On an error, try to synchronize
278				 * both sides.
279				 */
280				j = synchnet(f);
281				if (j && trace) {
282					printf("discarded %d packets\n", j);
283				}
284				if (dp->th_block == (block-1)) {
285					goto send_ack;	/* resend ack */
286				}
287			}
288		}
289	/*	size = write(fd, dp->th_data, n - 4); */
290		size = writeit(file, &dp, n - 4, convert);
291		if (size < 0) {
292			nak(errno + 100);
293			break;
294		}
295		amount += size;
296	} while (size == SEGSIZE);
297abort:						/* ok to ack, since user */
298	ap->th_opcode = htons((u_short)ACK);	/* has seen err msg */
299	ap->th_block = htons((u_short)block);
300	(void) sendto(f, ackbuf, 4, 0, (struct sockaddr *)&peeraddr,
301	    sizeof(peeraddr));
302	write_behind(file, convert);		/* flush last buffer */
303	fclose(file);
304	stopclock();
305	if (amount > 0)
306		printstats("Received", amount);
307}
308
309static int
310makerequest(request, name, tp, mode)
311	int request;
312	const char *name;
313	struct tftphdr *tp;
314	const char *mode;
315{
316	char *cp;
317
318	tp->th_opcode = htons((u_short)request);
319	cp = tp->th_stuff;
320	strcpy(cp, name);
321	cp += strlen(name);
322	*cp++ = '\0';
323	strcpy(cp, mode);
324	cp += strlen(mode);
325	*cp++ = '\0';
326	return (cp - (char *)tp);
327}
328
329const struct errmsg {
330	int	e_code;
331	const char *e_msg;
332} errmsgs[] = {
333	{ EUNDEF,	"Undefined error code" },
334	{ ENOTFOUND,	"File not found" },
335	{ EACCESS,	"Access violation" },
336	{ ENOSPACE,	"Disk full or allocation exceeded" },
337	{ EBADOP,	"Illegal TFTP operation" },
338	{ EBADID,	"Unknown transfer ID" },
339	{ EEXISTS,	"File already exists" },
340	{ ENOUSER,	"No such user" },
341	{ -1,		0 }
342};
343
344/*
345 * Send a nak packet (error message).
346 * Error code passed in is one of the
347 * standard TFTP codes, or a UNIX errno
348 * offset by 100.
349 */
350static void
351nak(error)
352	int error;
353{
354	const struct errmsg *pe;
355	struct tftphdr *tp;
356	int length;
357
358	tp = (struct tftphdr *)ackbuf;
359	tp->th_opcode = htons((u_short)ERROR);
360	for (pe = errmsgs; pe->e_code >= 0; pe++)
361		if (pe->e_code == error)
362			break;
363	if (pe->e_code < 0) {
364		tp->th_code = EUNDEF;
365		strcpy(tp->th_msg, strerror(error - 100));
366	} else {
367		tp->th_code = htons((u_short)error);
368		strcpy(tp->th_msg, pe->e_msg);
369	}
370	length = strlen(pe->e_msg) + 4;
371	if (trace)
372		tpacket("sent", tp, length);
373	if (sendto(f, ackbuf, length, 0, (struct sockaddr *)&peeraddr,
374	    sizeof(peeraddr)) != length)
375		warn("nak");
376}
377
378static void
379tpacket(s, tp, n)
380	const char *s;
381	struct tftphdr *tp;
382	int n;
383{
384	static char *opcodes[] =
385	   { "#0", "RRQ", "WRQ", "DATA", "ACK", "ERROR" };
386	char *cp, *file;
387	u_short op = ntohs(tp->th_opcode);
388
389	if (op < RRQ || op > ERROR)
390		printf("%s opcode=%x ", s, op);
391	else
392		printf("%s %s ", s, opcodes[op]);
393	switch (op) {
394
395	case RRQ:
396	case WRQ:
397		n -= 2;
398		file = cp = tp->th_stuff;
399		cp = strchr(cp, '\0');
400		printf("<file=%s, mode=%s>\n", file, cp + 1);
401		break;
402
403	case DATA:
404		printf("<block=%d, %d bytes>\n", ntohs(tp->th_block), n - 4);
405		break;
406
407	case ACK:
408		printf("<block=%d>\n", ntohs(tp->th_block));
409		break;
410
411	case ERROR:
412		printf("<code=%d, msg=%s>\n", ntohs(tp->th_code), tp->th_msg);
413		break;
414	}
415}
416
417struct timeval tstart;
418struct timeval tstop;
419
420static void
421startclock()
422{
423
424	(void)gettimeofday(&tstart, NULL);
425}
426
427static void
428stopclock()
429{
430
431	(void)gettimeofday(&tstop, NULL);
432}
433
434static void
435printstats(direction, amount)
436	const char *direction;
437	unsigned long amount;
438{
439	double delta;
440
441	/* compute delta in 1/10's second units */
442	delta = ((tstop.tv_sec*10.)+(tstop.tv_usec/100000)) -
443		((tstart.tv_sec*10.)+(tstart.tv_usec/100000));
444	delta = delta/10.;      /* back to seconds */
445	printf("%s %ld bytes in %.1f seconds", direction, amount, delta);
446	if (verbose)
447		printf(" [%.0f bits/sec]", (amount*8.)/delta);
448	putchar('\n');
449}
450
451static void
452timer(sig)
453	int sig;
454{
455
456	timeout += rexmtval;
457	if (timeout >= maxtimeout) {
458		printf("Transfer timed out.\n");
459		longjmp(toplevel, -1);
460	}
461	longjmp(timeoutbuf, 1);
462}
463