xref: /openbsd-src/usr.bin/tftp/tftp.c (revision daf88648c0e349d5c02e1504293082072c981640)
1 /*	$OpenBSD: tftp.c,v 1.20 2006/07/26 09:10:03 mglocker Exp $	*/
2 /*	$NetBSD: tftp.c,v 1.5 1995/04/29 05:55:25 cgd Exp $	*/
3 
4 /*
5  * Copyright (c) 1983, 1993
6  *	The Regents of the University of California.  All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  * 3. Neither the name of the University nor the names of its contributors
17  *    may be used to endorse or promote products derived from this software
18  *    without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
21  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  */
32 
33 #ifndef lint
34 #if 0
35 static char sccsid[] = "@(#)tftp.c	8.1 (Berkeley) 6/6/93";
36 #endif
37 static const char rcsid[] =
38     "$OpenBSD: tftp.c,v 1.20 2006/07/26 09:10:03 mglocker Exp $";
39 #endif /* not lint */
40 
41 /*
42  * TFTP User Program -- Protocol Machines
43  *
44  * This version includes many modifications by Jim Guyton <guyton@rand-unix>
45  */
46 
47 #include <sys/types.h>
48 #include <sys/socket.h>
49 #include <sys/time.h>
50 #include <sys/stat.h>
51 
52 #include <netinet/in.h>
53 #include <arpa/tftp.h>
54 
55 #include <err.h>
56 #include <errno.h>
57 #include <poll.h>
58 #include <signal.h>
59 #include <stdio.h>
60 #include <stddef.h>
61 #include <stdlib.h>
62 #include <string.h>
63 #include <unistd.h>
64 
65 #include "extern.h"
66 #include "tftpsubs.h"
67 
68 static int	makerequest(int, const char *, struct tftphdr *, const char *);
69 static void	nak(int);
70 static void 	tpacket(const char *, struct tftphdr *, int);
71 static void	startclock(void);
72 static void	stopclock(void);
73 static void	printstats(const char *, unsigned long);
74 static void	printtimeout(void);
75 static void	oack(struct tftphdr *, int, int);
76 static int	oack_set(const char *, const char *);
77 
78 extern struct sockaddr_in	 peeraddr;	/* filled in by main */
79 extern int			 f;		/* the opened socket */
80 extern int			 trace;
81 extern int			 verbose;
82 extern int			 rexmtval;
83 extern int			 maxtimeout;
84 extern FILE			*file;
85 extern volatile sig_atomic_t	 intrflag;
86 extern char			*ackbuf;
87 extern int			 has_options;
88 extern int			 opt_tsize;
89 extern int			 opt_tout;
90 extern int			 opt_blksize;
91 
92 struct timeval	tstart;
93 struct timeval	tstop;
94 unsigned int	segment_size = SEGSIZE;
95 unsigned int	packet_size = SEGSIZE + 4;
96 
97 struct errmsg {
98 	int	 e_code;
99 	char	*e_msg;
100 } errmsgs[] = {
101 	{ EUNDEF,	"Undefined error code" },
102 	{ ENOTFOUND,	"File not found" },
103 	{ EACCESS,	"Access violation" },
104 	{ ENOSPACE,	"Disk full or allocation exceeded" },
105 	{ EBADOP,	"Illegal TFTP operation" },
106 	{ EBADID,	"Unknown transfer ID" },
107 	{ EEXISTS,	"File already exists" },
108 	{ ENOUSER,	"No such user" },
109 	{ EOPTNEG,	"Option negotiation failed" },
110 	{ -1,		NULL }
111 };
112 
113 struct options {
114 	const char      *o_type;
115 } options[] = {
116 	{ "tsize" },
117 	{ "timeout" },
118 	{ "blksize" },
119 	{ NULL }
120 };
121 
122 enum opt_enum {
123 	OPT_TSIZE = 0,
124 	OPT_TIMEOUT,
125 	OPT_BLKSIZE
126 };
127 
128 /*
129  * Send the requested file.
130  */
131 void
132 sendfile(int fd, char *name, char *mode)
133 {
134 	struct tftphdr		*dp, *ap; /* data and ack packets */
135 	struct sockaddr_in	 from;
136 	struct pollfd		 pfd[1];
137 	unsigned long		 amount;
138 	int			 convert; /* true if converting crlf -> lf */
139 	int			 n, nfds, error, fromlen, timeouts, block, size;
140 
141 	startclock();		/* start stat's clock */
142 	dp = r_init();		/* reset fillbuf/read-ahead code */
143 	ap = (struct tftphdr *)ackbuf;
144 	file = fdopen(fd, "r");
145 	convert = !strcmp(mode, "netascii");
146 	block = 0;
147 	amount = 0;
148 
149 	do {
150 		/* read data from file */
151 		if (!block)
152 			size = makerequest(WRQ, name, dp, mode) - 4;
153 		else {
154 			size = readit(file, &dp, convert, segment_size);
155 			if (size < 0) {
156 				nak(errno + 100);
157 				break;
158 			}
159 			dp->th_opcode = htons((u_short)DATA);
160 			dp->th_block = htons((u_short)block);
161 		}
162 
163 		/* send data to server and wait for server ACK */
164 		for (timeouts = 0, error = 0; !intrflag;) {
165 			if (timeouts >= maxtimeout) {
166 				printtimeout();
167 				goto abort;
168 			}
169 
170 			if (!error) {
171 				if (trace)
172 					tpacket("sent", dp, size + 4);
173 				if (sendto(f, dp, size + 4, 0,
174 		    		    (struct sockaddr *)&peeraddr,
175 				    sizeof(peeraddr)) != size + 4) {
176 					warn("sendto");
177 					goto abort;
178 				}
179 				if (block > 0)
180 					read_ahead(file, convert, segment_size);
181 			}
182 			error = 0;
183 
184 			pfd[0].fd = f;
185 			pfd[0].events = POLLIN;
186 			nfds = poll(pfd, 1, rexmtval * 1000);
187 			if (nfds == 0) {
188 				timeouts += rexmtval;
189 				continue;
190 			}
191 			if (nfds == -1) {
192 				error = 1;
193 				if (errno == EINTR)
194 					continue;
195 				warn("poll");
196 				goto abort;
197 			}
198 			fromlen = sizeof(from);
199 			n = recvfrom(f, ackbuf, packet_size, 0,
200 			    (struct sockaddr *)&from, &fromlen);
201 			if (n == 0) {
202 				warn("recvfrom");
203 				goto abort;
204 			}
205 			if (n == -1) {
206 				error = 1;
207 				if (errno == EINTR)
208 					continue;
209 				warn("recvfrom");
210 				goto abort;
211 			}
212 			peeraddr.sin_port = from.sin_port;	/* added */
213 			if (trace)
214 				tpacket("received", ap, n);
215 
216 			ap->th_opcode = ntohs(ap->th_opcode);
217 
218 			if (ap->th_opcode == OACK) {
219 				oack(ap, n, 0);
220 				break;
221 			}
222 
223 			ap->th_block = ntohs(ap->th_block);
224 
225 			if (ap->th_opcode == ERROR) {
226 				printf("Error code %d: %s\n",
227 				    ap->th_code, ap->th_msg);
228 				goto abort;
229 			}
230 			if (ap->th_opcode == ACK) {
231 				int j;
232 				if (ap->th_block == block)
233 					break;
234 				/* re-synchronize with other side */
235 				j = synchnet(f);
236 				if (j && trace)
237 					printf("discarded %d packets\n", j);
238 				if (ap->th_block == (block - 1))
239 					continue;
240 			}
241 			error = 1;	/* received packet does not match */
242 		}
243 
244 		if (block > 0)
245 			amount += size;
246 		block++;
247 	} while ((size == segment_size || block == 1) && !intrflag);
248 
249 abort:
250 	fclose(file);
251 	stopclock();
252 	if (amount > 0) {
253 		if (intrflag)
254 			putchar('\n');
255 		printstats("Sent", amount);
256 	}
257 }
258 
259 /*
260  * Receive a file.
261  */
262 void
263 recvfile(int fd, char *name, char *mode)
264 {
265 	struct tftphdr		*dp, *ap; /* data and ack packets */
266 	struct sockaddr_in	 from;
267 	struct pollfd		 pfd[1];
268 	unsigned long		 amount;
269 	int			 convert; /* true if converting crlf -> lf */
270 	int			 n, nfds, error, fromlen, timeouts, block, size;
271 	int			 firsttrip;
272 
273 	startclock();		/* start stat's clock */
274 	dp = w_init();		/* reset fillbuf/read-ahead code */
275 	ap = (struct tftphdr *)ackbuf;
276 	file = fdopen(fd, "w");
277 	convert = !strcmp(mode, "netascii");
278 	n = 0;
279 	block = 1;
280 	amount = 0;
281 	firsttrip = 1;
282 
283 options:
284 	do {
285 		/* create new ACK packet */
286 		if (firsttrip) {
287 			size = makerequest(RRQ, name, ap, mode);
288 			firsttrip = 0;
289 		} else {
290 			ap->th_opcode = htons((u_short)ACK);
291 			ap->th_block = htons((u_short)(block));
292 			size = 4;
293 			block++;
294 		}
295 
296 		/* send ACK to server and wait for server data */
297 		for (timeouts = 0, error = 0; !intrflag;) {
298 			if (timeouts >= maxtimeout) {
299 				printtimeout();
300 				goto abort;
301 			}
302 
303 			if (!error) {
304 				if (trace)
305 					tpacket("sent", ap, size);
306 				if (sendto(f, ackbuf, size, 0,
307 			    	    (struct sockaddr *)&peeraddr,
308 				    sizeof(peeraddr)) != size) {
309 					warn("sendto");
310 					goto abort;
311 				}
312 				write_behind(file, convert);
313 			}
314 			error = 0;
315 
316 			pfd[0].fd = f;
317 			pfd[0].events = POLLIN;
318 			nfds = poll(pfd, 1, rexmtval * 1000);
319 			if (nfds == 0) {
320 				timeouts += rexmtval;
321 				continue;
322 			}
323 			if (nfds == -1) {
324 				error = 1;
325 				if (errno == EINTR)
326 					continue;
327 				warn("poll");
328 				goto abort;
329 			}
330 			fromlen = sizeof(from);
331 			n = recvfrom(f, dp, packet_size, 0,
332 			    (struct sockaddr *)&from, &fromlen);
333 			if (n == 0) {
334 				warn("recvfrom");
335 				goto abort;
336 			}
337 			if (n == -1) {
338 				error = 1;
339 				if (errno == EINTR)
340 					continue;
341 				warn("recvfrom");
342 				goto abort;
343 			}
344 			peeraddr.sin_port = from.sin_port;	/* added */
345 			if (trace)
346 				tpacket("received", dp, n);
347 
348 			dp->th_opcode = ntohs(dp->th_opcode);
349 
350 			if (dp->th_opcode == OACK) {
351 				oack(dp, n, 0);
352 				block = 0;
353 				goto options;
354 			}
355 
356 			dp->th_block = ntohs(dp->th_block);
357 
358 			if (dp->th_opcode == ERROR) {
359 				printf("Error code %d: %s\n",
360 				    dp->th_code, dp->th_msg);
361 				goto abort;
362 			}
363 			if (dp->th_opcode == DATA) {
364 				int j;
365 				if (dp->th_block == block)
366 					break;
367 				/* re-synchronize with other side */
368 				j = synchnet(f);
369 				if (j && trace)
370 					printf("discarded %d packets\n", j);
371 				if (dp->th_block == (block - 1))
372 					continue;
373 			}
374 			error = 1;	/* received packet does not match */
375 		}
376 
377 		/* write data to file */
378 		size = writeit(file, &dp, n - 4, convert);
379 		if (size < 0) {
380 			nak(errno + 100);
381 			break;
382 		}
383 		amount += size;
384 	} while (size == segment_size && !intrflag);
385 
386 abort:
387 	/* ok to ack, since user has seen err msg */
388 	ap->th_opcode = htons((u_short)ACK);
389 	ap->th_block = htons((u_short)block);
390 	(void)sendto(f, ackbuf, 4, 0, (struct sockaddr *)&peeraddr,
391 	    sizeof(peeraddr));
392 	write_behind(file, convert);	/* flush last buffer */
393 
394 	fclose(file);
395 	stopclock();
396 	if (amount > 0) {
397 		if (intrflag)
398 			putchar('\n');
399 		printstats("Received", amount);
400 	}
401 }
402 
403 static int
404 makerequest(int request, const char *name, struct tftphdr *tp,
405     const char *mode)
406 {
407 	char		*cp;
408 	int		 len, pktlen;
409 	off_t		 fsize = 0;
410 	struct stat	 st;
411 
412 	tp->th_opcode = htons((u_short)request);
413 	cp = tp->th_stuff;
414 	pktlen = packet_size - offsetof(struct tftphdr, th_stuff);
415 	len = strlen(name) + 1;
416 	strlcpy(cp, name, pktlen);
417 	strlcpy(cp + len, mode, pktlen - len);
418 	len += strlen(mode) + 1;
419 
420 	if (opt_tsize) {
421 		if (request == WRQ) {
422 			stat(name, &st);
423 			fsize = st.st_size;
424 		}
425 		len += snprintf(cp + len, pktlen - len, "%s%c%lld%c",
426 		    options[OPT_TSIZE].o_type, 0, fsize, 0);
427 	}
428 	if (opt_tout)
429 		len += snprintf(cp + len, pktlen - len, "%s%c%d%c",
430 		    options[OPT_TIMEOUT].o_type, 0, rexmtval, 0);
431 	if (opt_blksize)
432 		len += snprintf(cp + len, pktlen - len, "%s%c%d%c",
433 		    options[OPT_BLKSIZE].o_type, 0, opt_blksize, 0);
434 
435 	return (cp + len - (char *)tp);
436 }
437 
438 /*
439  * Send a nak packet (error message).
440  * Error code passed in is one of the
441  * standard TFTP codes, or a UNIX errno
442  * offset by 100.
443  */
444 static void
445 nak(int error)
446 {
447 	struct errmsg	*pe;
448 	struct tftphdr	*tp;
449 	int		 length;
450 
451 	tp = (struct tftphdr *)ackbuf;
452 	tp->th_opcode = htons((u_short)ERROR);
453 	tp->th_code = htons((u_short)error);
454 	for (pe = errmsgs; pe->e_code >= 0; pe++)
455 		if (pe->e_code == error)
456 			break;
457 	if (pe->e_code < 0) {
458 		pe->e_msg = strerror(error - 100);
459 		tp->th_code = EUNDEF;
460 	}
461 	length = strlcpy(tp->th_msg, pe->e_msg, packet_size) + 5;
462 	if (length > packet_size)
463 		length = packet_size;
464 	if (trace)
465 		tpacket("sent", tp, length);
466 	if (sendto(f, ackbuf, length, 0, (struct sockaddr *)&peeraddr,
467 	    sizeof(peeraddr)) != length)
468 		warn("nak");
469 }
470 
471 static void
472 tpacket(const char *s, struct tftphdr *tp, int n)
473 {
474 	char		*cp, *file;
475 	static char	*opcodes[] =
476 	    { "#0", "RRQ", "WRQ", "DATA", "ACK", "ERROR", "OACK" };
477 
478 	u_short op = ntohs(tp->th_opcode);
479 
480 	if (op < RRQ || op > OACK)
481 		printf("%s opcode=%x ", s, op);
482 	else
483 		printf("%s %s ", s, opcodes[op]);
484 
485 	switch (op) {
486 	case RRQ:
487 	case WRQ:
488 		n -= 2;
489 		file = cp = tp->th_stuff;
490 		cp = strchr(cp, '\0');
491 		printf("<file=%s, mode=%s", file, cp + 1);
492 		if (has_options)
493 			oack(tp, n, 1);
494 		printf(">\n");
495 		break;
496 	case DATA:
497 		printf("<block=%d, %d bytes>\n", ntohs(tp->th_block), n - 4);
498 		break;
499 	case ACK:
500 		printf("<block=%d>\n", ntohs(tp->th_block));
501 		break;
502 	case ERROR:
503 		printf("<code=%d, msg=%s>\n", ntohs(tp->th_code), tp->th_msg);
504 		break;
505 	case OACK:
506 		printf("<");
507 		oack(tp, n, 1);
508 		printf(">\n");
509 		break;
510 	}
511 }
512 
513 static void
514 startclock(void)
515 {
516 	(void)gettimeofday(&tstart, NULL);
517 }
518 
519 static void
520 stopclock(void)
521 {
522 	(void)gettimeofday(&tstop, NULL);
523 }
524 
525 static void
526 printstats(const char *direction, unsigned long amount)
527 {
528 	double	delta;
529 
530 	/* compute delta in 1/10's second units */
531 	delta = ((tstop.tv_sec * 10.) + (tstop.tv_usec / 100000)) -
532 	    ((tstart.tv_sec * 10.) + (tstart.tv_usec / 100000));
533 	delta = delta / 10.;	/* back to seconds */
534 	printf("%s %lu bytes in %.1f seconds", direction, amount, delta);
535 	if (verbose)
536 		printf(" [%.0f bits/sec]", (amount * 8.) / delta);
537 	putchar('\n');
538 }
539 
540 static void
541 printtimeout(void)
542 {
543 	printf("Transfer timed out.\n");
544 }
545 
546 static void
547 oack(struct tftphdr *tp, int size, int trace)
548 {
549 	int	 i, len, off;
550 	char	*opt, *val;
551 
552 	u_short op = ntohs(tp->th_opcode);
553 
554 	opt = tp->th_u.tu_stuff;
555 	val = tp->th_u.tu_stuff;
556 
557 	if (op == RRQ || op == WRQ) {
558 		len = strlen(opt) + 1;
559 		opt = strchr(opt, '\0');
560 		opt++;
561 		len += strlen(opt) + 1;
562 		opt = strchr(opt, '\0');
563 		opt++;
564 		val = opt;
565 		off = len;
566 		if (trace)
567 			printf(", ");
568 	} else
569 		off = 2;
570 
571 	for (i = off, len = 0; i < size - 1; i++) {
572 		if (*val != '\0') {
573 			val++;
574 			continue;
575 		}
576 		/* got option and value */
577 		val++;
578 		if (trace)
579 			printf("%s=%s", opt, val);
580 		else
581 			if (oack_set(opt, val) == -1)
582 				break;
583 		len = strlen(val) + 1;
584 		val += len;
585 		opt = val;
586 		i += len;
587 		if (trace && i < size - 1)
588 			printf(", ");
589 	}
590 }
591 
592 int
593 oack_set(const char *option, const char *value)
594 {
595 	int		 i, n;
596 	const char	*errstr;
597 
598 	for (i = 0; options[i].o_type != NULL; i++) {
599 		if (!strcasecmp(options[i].o_type, option)) {
600 			if (i == OPT_TSIZE) {
601 				/* XXX verify OACK response */
602 			}
603 			if (i == OPT_TIMEOUT) {
604 				/* verify OACK response */
605 				n = strtonum(value, TIMEOUT_MIN, TIMEOUT_MAX,
606 				    &errstr);
607 				if (errstr || rexmtval != n ||
608 				    opt_tout == 0) {
609 					nak(EOPTNEG);
610 					intrflag = 1;
611 					return (-1);
612 				}
613 				/* OK */
614 			}
615 			if (i == OPT_BLKSIZE) {
616 				/* verify OACK response */
617 				n = strtonum(value, SEGSIZE_MIN, SEGSIZE_MAX,
618 				    &errstr);
619 				if (errstr || opt_blksize != n ||
620 				    opt_blksize == 0) {
621 					nak(EOPTNEG);
622 					intrflag = 1;
623 					return (-1);
624 				}
625 				/* OK, set option */
626 				segment_size = n;
627 				packet_size = segment_size + 4;
628 			}
629 		}
630 	}
631 
632 	return (1);
633 }
634