tftp.c revision 1.9
1/*	$NetBSD: tftp.c,v 1.9 1998/12/19 22:41:21 christos 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.9 1998/12/19 22:41:21 christos 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#ifndef __SVR4
320	cp = tp->th_stuff;
321#else
322	cp = (void *)&tp->th_stuff;
323#endif
324	strcpy(cp, name);
325	cp += strlen(name);
326	*cp++ = '\0';
327	strcpy(cp, mode);
328	cp += strlen(mode);
329	*cp++ = '\0';
330	return (cp - (char *)tp);
331}
332
333const struct errmsg {
334	int	e_code;
335	const char *e_msg;
336} errmsgs[] = {
337	{ EUNDEF,	"Undefined error code" },
338	{ ENOTFOUND,	"File not found" },
339	{ EACCESS,	"Access violation" },
340	{ ENOSPACE,	"Disk full or allocation exceeded" },
341	{ EBADOP,	"Illegal TFTP operation" },
342	{ EBADID,	"Unknown transfer ID" },
343	{ EEXISTS,	"File already exists" },
344	{ ENOUSER,	"No such user" },
345	{ -1,		0 }
346};
347
348/*
349 * Send a nak packet (error message).
350 * Error code passed in is one of the
351 * standard TFTP codes, or a UNIX errno
352 * offset by 100.
353 */
354static void
355nak(error)
356	int error;
357{
358	const struct errmsg *pe;
359	struct tftphdr *tp;
360	int length;
361
362	tp = (struct tftphdr *)ackbuf;
363	tp->th_opcode = htons((u_short)ERROR);
364	for (pe = errmsgs; pe->e_code >= 0; pe++)
365		if (pe->e_code == error)
366			break;
367	if (pe->e_code < 0) {
368		tp->th_code = EUNDEF;
369		strcpy(tp->th_msg, strerror(error - 100));
370	} else {
371		tp->th_code = htons((u_short)error);
372		strcpy(tp->th_msg, pe->e_msg);
373	}
374	length = strlen(pe->e_msg) + 4;
375	if (trace)
376		tpacket("sent", tp, length);
377	if (sendto(f, ackbuf, length, 0, (struct sockaddr *)&peeraddr,
378	    sizeof(peeraddr)) != length)
379		warn("nak");
380}
381
382static void
383tpacket(s, tp, n)
384	const char *s;
385	struct tftphdr *tp;
386	int n;
387{
388	static char *opcodes[] =
389	   { "#0", "RRQ", "WRQ", "DATA", "ACK", "ERROR" };
390	char *cp, *file;
391	u_short op = ntohs(tp->th_opcode);
392
393	if (op < RRQ || op > ERROR)
394		printf("%s opcode=%x ", s, op);
395	else
396		printf("%s %s ", s, opcodes[op]);
397	switch (op) {
398
399	case RRQ:
400	case WRQ:
401		n -= 2;
402#ifndef __SVR4
403		cp = tp->th_stuff;
404#else
405		cp = (void *) &tp->th_stuff;
406#endif
407		file = cp;
408		cp = strchr(cp, '\0');
409		printf("<file=%s, mode=%s>\n", file, cp + 1);
410		break;
411
412	case DATA:
413		printf("<block=%d, %d bytes>\n", ntohs(tp->th_block), n - 4);
414		break;
415
416	case ACK:
417		printf("<block=%d>\n", ntohs(tp->th_block));
418		break;
419
420	case ERROR:
421		printf("<code=%d, msg=%s>\n", ntohs(tp->th_code), tp->th_msg);
422		break;
423	}
424}
425
426struct timeval tstart;
427struct timeval tstop;
428
429static void
430startclock()
431{
432
433	(void)gettimeofday(&tstart, NULL);
434}
435
436static void
437stopclock()
438{
439
440	(void)gettimeofday(&tstop, NULL);
441}
442
443static void
444printstats(direction, amount)
445	const char *direction;
446	unsigned long amount;
447{
448	double delta;
449
450	/* compute delta in 1/10's second units */
451	delta = ((tstop.tv_sec*10.)+(tstop.tv_usec/100000)) -
452		((tstart.tv_sec*10.)+(tstart.tv_usec/100000));
453	delta = delta/10.;      /* back to seconds */
454	printf("%s %ld bytes in %.1f seconds", direction, amount, delta);
455	if (verbose)
456		printf(" [%.0f bits/sec]", (amount*8.)/delta);
457	putchar('\n');
458}
459
460static void
461timer(sig)
462	int sig;
463{
464
465	timeout += rexmtval;
466	if (timeout >= maxtimeout) {
467		printf("Transfer timed out.\n");
468		longjmp(toplevel, -1);
469	}
470	longjmp(timeoutbuf, 1);
471}
472