xref: /onnv-gate/usr/src/cmd/cmd-inet/usr.bin/tftp/tftp.c (revision 1926:d83c7a9aec2d)
10Sstevel@tonic-gate /*
20Sstevel@tonic-gate  * CDDL HEADER START
30Sstevel@tonic-gate  *
40Sstevel@tonic-gate  * The contents of this file are subject to the terms of the
5*1926Sas198278  * Common Development and Distribution License (the "License").
6*1926Sas198278  * You may not use this file except in compliance with the License.
70Sstevel@tonic-gate  *
80Sstevel@tonic-gate  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
90Sstevel@tonic-gate  * or http://www.opensolaris.org/os/licensing.
100Sstevel@tonic-gate  * See the License for the specific language governing permissions
110Sstevel@tonic-gate  * and limitations under the License.
120Sstevel@tonic-gate  *
130Sstevel@tonic-gate  * When distributing Covered Code, include this CDDL HEADER in each
140Sstevel@tonic-gate  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
150Sstevel@tonic-gate  * If applicable, add the following below this CDDL HEADER, with the
160Sstevel@tonic-gate  * fields enclosed by brackets "[]" replaced with your own identifying
170Sstevel@tonic-gate  * information: Portions Copyright [yyyy] [name of copyright owner]
180Sstevel@tonic-gate  *
190Sstevel@tonic-gate  * CDDL HEADER END
200Sstevel@tonic-gate  */
210Sstevel@tonic-gate /*
22*1926Sas198278  * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
230Sstevel@tonic-gate  * Use is subject to license terms.
240Sstevel@tonic-gate  */
250Sstevel@tonic-gate 
260Sstevel@tonic-gate /*	Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T	*/
270Sstevel@tonic-gate /*	  All Rights Reserved  	*/
280Sstevel@tonic-gate 
290Sstevel@tonic-gate /*
300Sstevel@tonic-gate  * University Copyright- Copyright (c) 1982, 1986, 1988
310Sstevel@tonic-gate  * The Regents of the University of California
320Sstevel@tonic-gate  * All Rights Reserved
330Sstevel@tonic-gate  *
340Sstevel@tonic-gate  * University Acknowledgment- Portions of this document are derived from
350Sstevel@tonic-gate  * software developed by the University of California, Berkeley, and its
360Sstevel@tonic-gate  * contributors.
370Sstevel@tonic-gate  */
380Sstevel@tonic-gate 
390Sstevel@tonic-gate #pragma ident	"%Z%%M%	%I%	%E% SMI"
400Sstevel@tonic-gate 
410Sstevel@tonic-gate /*
420Sstevel@tonic-gate  * TFTP User Program -- Protocol Machines
430Sstevel@tonic-gate  */
440Sstevel@tonic-gate #include <sys/types.h>
450Sstevel@tonic-gate #include <sys/socket.h>
460Sstevel@tonic-gate #include <sys/time.h>
470Sstevel@tonic-gate #include <sys/stat.h>
480Sstevel@tonic-gate 
490Sstevel@tonic-gate #include <signal.h>
500Sstevel@tonic-gate #include <stdio.h>
510Sstevel@tonic-gate #include <stdlib.h>
520Sstevel@tonic-gate #include <unistd.h>
530Sstevel@tonic-gate #include <errno.h>
540Sstevel@tonic-gate #include <string.h>
550Sstevel@tonic-gate #include <stddef.h>
560Sstevel@tonic-gate #include <inttypes.h>
570Sstevel@tonic-gate 
580Sstevel@tonic-gate #include "tftpcommon.h"
590Sstevel@tonic-gate #include "tftpprivate.h"
600Sstevel@tonic-gate 
610Sstevel@tonic-gate static char	*blksize_str(void);
620Sstevel@tonic-gate static char	*timeout_str(void);
630Sstevel@tonic-gate static char	*tsize_str(void);
640Sstevel@tonic-gate static int	blksize_handler(char *);
650Sstevel@tonic-gate static int	timeout_handler(char *);
660Sstevel@tonic-gate static int	tsize_handler(char *);
670Sstevel@tonic-gate static int	add_options(char *, char *);
680Sstevel@tonic-gate static int	process_oack(tftpbuf *, int);
690Sstevel@tonic-gate static void	nak(int);
700Sstevel@tonic-gate static void	startclock(void);
710Sstevel@tonic-gate static void	stopclock(void);
720Sstevel@tonic-gate static void	printstats(char *, off_t);
730Sstevel@tonic-gate static int	makerequest(int, char *, struct tftphdr *, char *);
740Sstevel@tonic-gate static void	tpacket(char *, struct tftphdr *, int);
750Sstevel@tonic-gate 
760Sstevel@tonic-gate static struct options {
770Sstevel@tonic-gate 	char	*opt_name;
780Sstevel@tonic-gate 	char	*(*opt_str)(void);
790Sstevel@tonic-gate 	int	(*opt_handler)(char *);
800Sstevel@tonic-gate } options[] = {
810Sstevel@tonic-gate 	{ "blksize",	blksize_str, blksize_handler },
820Sstevel@tonic-gate 	{ "timeout",	timeout_str, timeout_handler },
830Sstevel@tonic-gate 	{ "tsize",	tsize_str, tsize_handler },
840Sstevel@tonic-gate 	{ NULL }
850Sstevel@tonic-gate };
860Sstevel@tonic-gate 
870Sstevel@tonic-gate static char		optbuf[MAX_OPTVAL_LEN];
880Sstevel@tonic-gate static boolean_t	tsize_set;
890Sstevel@tonic-gate 
900Sstevel@tonic-gate static tftpbuf	ackbuf;
910Sstevel@tonic-gate static int	timeout;
920Sstevel@tonic-gate static off_t	tsize;
930Sstevel@tonic-gate static jmp_buf	timeoutbuf;
940Sstevel@tonic-gate 
950Sstevel@tonic-gate int	blocksize = SEGSIZE;	/* Number of data bytes in a DATA packet */
960Sstevel@tonic-gate 
970Sstevel@tonic-gate /*ARGSUSED*/
980Sstevel@tonic-gate static void
timer(int signum)990Sstevel@tonic-gate timer(int signum)
1000Sstevel@tonic-gate {
1010Sstevel@tonic-gate 	timeout += rexmtval;
1020Sstevel@tonic-gate 	if (timeout >= maxtimeout) {
1030Sstevel@tonic-gate 		(void) fputs("Transfer timed out.\n", stderr);
1040Sstevel@tonic-gate 		longjmp(toplevel, -1);
1050Sstevel@tonic-gate 	}
1060Sstevel@tonic-gate 	(void) signal(SIGALRM, timer);
1070Sstevel@tonic-gate 	longjmp(timeoutbuf, 1);
1080Sstevel@tonic-gate }
1090Sstevel@tonic-gate 
1100Sstevel@tonic-gate /*
1110Sstevel@tonic-gate  * Send the requested file.
1120Sstevel@tonic-gate  */
1130Sstevel@tonic-gate void
tftp_sendfile(int fd,char * name,char * mode)1140Sstevel@tonic-gate tftp_sendfile(int fd, char *name, char *mode)
1150Sstevel@tonic-gate {
1160Sstevel@tonic-gate 	struct tftphdr *ap;	/* data and ack packets */
1170Sstevel@tonic-gate 	struct tftphdr *dp;
118*1926Sas198278 	int count = 0, size, n;
119*1926Sas198278 	ushort_t block = 0;
1200Sstevel@tonic-gate 	off_t amount = 0;
1210Sstevel@tonic-gate 	struct sockaddr_in6 from;
1220Sstevel@tonic-gate 	socklen_t fromlen;
1230Sstevel@tonic-gate 	int convert;	/* true if doing nl->crlf conversion */
1240Sstevel@tonic-gate 	FILE *file;
1250Sstevel@tonic-gate 	struct stat statb;
1260Sstevel@tonic-gate 	int errcode;
1270Sstevel@tonic-gate 
1280Sstevel@tonic-gate 	startclock();	/* start stat's clock */
1290Sstevel@tonic-gate 	dp = r_init();	/* reset fillbuf/read-ahead code */
1300Sstevel@tonic-gate 	ap = &ackbuf.tb_hdr;
1310Sstevel@tonic-gate 	file = fdopen(fd, "r");
1320Sstevel@tonic-gate 	convert = (strcmp(mode, "netascii") == 0);
1330Sstevel@tonic-gate 
1340Sstevel@tonic-gate 	tsize_set = ((tsize_opt != 0) && !convert && (fstat(fd, &statb) == 0));
1350Sstevel@tonic-gate 	if (tsize_set)
1360Sstevel@tonic-gate 		tsize = statb.st_size;
1370Sstevel@tonic-gate 
1380Sstevel@tonic-gate 	do {
1390Sstevel@tonic-gate 		(void) signal(SIGALRM, timer);
140*1926Sas198278 		if (count == 0) {
1410Sstevel@tonic-gate 			if ((size = makerequest(WRQ, name, dp, mode)) == -1) {
1420Sstevel@tonic-gate 				(void) fprintf(stderr,
1430Sstevel@tonic-gate 				    "tftp: Error: Write request packet too "
1440Sstevel@tonic-gate 				    "big\n");
1450Sstevel@tonic-gate 				(void) fclose(file);
1460Sstevel@tonic-gate 				return;
1470Sstevel@tonic-gate 			}
1480Sstevel@tonic-gate 			size -= 4;
1490Sstevel@tonic-gate 		} else {
1500Sstevel@tonic-gate 			size = readit(file, &dp, convert);
1510Sstevel@tonic-gate 			if (size < 0) {
1520Sstevel@tonic-gate 				nak(errno + 100);
1530Sstevel@tonic-gate 				break;
1540Sstevel@tonic-gate 			}
1550Sstevel@tonic-gate 			dp->th_opcode = htons((ushort_t)DATA);
1560Sstevel@tonic-gate 			dp->th_block = htons((ushort_t)block);
1570Sstevel@tonic-gate 		}
1580Sstevel@tonic-gate 		timeout = 0;
1590Sstevel@tonic-gate 		(void) setjmp(timeoutbuf);
1600Sstevel@tonic-gate 		if (trace)
1610Sstevel@tonic-gate 			tpacket("sent", dp, size + 4);
1620Sstevel@tonic-gate 		n = sendto(f, dp, size + 4, 0,
1630Sstevel@tonic-gate 		    (struct sockaddr *)&sin6, sizeof (sin6));
1640Sstevel@tonic-gate 		if (n != size + 4) {
1650Sstevel@tonic-gate 			perror("tftp: sendto");
1660Sstevel@tonic-gate 			goto abort;
1670Sstevel@tonic-gate 		}
1680Sstevel@tonic-gate 		/* Can't read-ahead first block as OACK may change blocksize */
169*1926Sas198278 		if (count != 0)
1700Sstevel@tonic-gate 			read_ahead(file, convert);
1710Sstevel@tonic-gate 		(void) alarm(rexmtval);
1720Sstevel@tonic-gate 		for (; ; ) {
1730Sstevel@tonic-gate 			(void) sigrelse(SIGALRM);
1740Sstevel@tonic-gate 			do {
1750Sstevel@tonic-gate 				fromlen = (socklen_t)sizeof (from);
1760Sstevel@tonic-gate 				n = recvfrom(f, ackbuf.tb_data,
1770Sstevel@tonic-gate 				    sizeof (ackbuf.tb_data), 0,
1780Sstevel@tonic-gate 				    (struct sockaddr *)&from, &fromlen);
1790Sstevel@tonic-gate 				if (n < 0) {
1800Sstevel@tonic-gate 					perror("tftp: recvfrom");
1810Sstevel@tonic-gate 					goto abort;
1820Sstevel@tonic-gate 				}
1830Sstevel@tonic-gate 			} while (n < offsetof(struct tftphdr, th_data));
1840Sstevel@tonic-gate 			(void) sighold(SIGALRM);
1850Sstevel@tonic-gate 			sin6.sin6_port = from.sin6_port;   /* added */
1860Sstevel@tonic-gate 			if (trace)
1870Sstevel@tonic-gate 				tpacket("received", ap, n);
1880Sstevel@tonic-gate 			/* should verify packet came from server */
1890Sstevel@tonic-gate 			ap->th_opcode = ntohs(ap->th_opcode);
1900Sstevel@tonic-gate 			if (ap->th_opcode == ERROR) {
1910Sstevel@tonic-gate 				ap->th_code = ntohs(ap->th_code);
1920Sstevel@tonic-gate 				(void) fprintf(stderr,
1930Sstevel@tonic-gate 					"Error code %d", ap->th_code);
1940Sstevel@tonic-gate 				if (n > offsetof(struct tftphdr, th_data))
1950Sstevel@tonic-gate 					(void) fprintf(stderr, ": %.*s", n -
1960Sstevel@tonic-gate 					    offsetof(struct tftphdr, th_data),
1970Sstevel@tonic-gate 					    ap->th_msg);
1980Sstevel@tonic-gate 				(void) fputc('\n', stderr);
1990Sstevel@tonic-gate 				goto abort;
2000Sstevel@tonic-gate 			}
201*1926Sas198278 			if ((count == 0) && (ap->th_opcode == OACK)) {
2020Sstevel@tonic-gate 				errcode = process_oack(&ackbuf, n);
2030Sstevel@tonic-gate 				if (errcode >= 0) {
2040Sstevel@tonic-gate 					nak(errcode);
2050Sstevel@tonic-gate 					(void) fputs("Rejected OACK\n",
2060Sstevel@tonic-gate 					    stderr);
2070Sstevel@tonic-gate 					goto abort;
2080Sstevel@tonic-gate 				}
2090Sstevel@tonic-gate 				break;
2100Sstevel@tonic-gate 			}
2110Sstevel@tonic-gate 			if (ap->th_opcode == ACK) {
2120Sstevel@tonic-gate 				ap->th_block = ntohs(ap->th_block);
2130Sstevel@tonic-gate 				if (ap->th_block == block) {
2140Sstevel@tonic-gate 					break;
2150Sstevel@tonic-gate 				}
2160Sstevel@tonic-gate 				/*
2170Sstevel@tonic-gate 				 * Never resend the current DATA packet on
2180Sstevel@tonic-gate 				 * receipt of a duplicate ACK, doing so would
2190Sstevel@tonic-gate 				 * cause the "Sorcerer's Apprentice Syndrome".
2200Sstevel@tonic-gate 				 */
2210Sstevel@tonic-gate 			}
2220Sstevel@tonic-gate 		}
2230Sstevel@tonic-gate 		cancel_alarm();
224*1926Sas198278 		if (count > 0)
2250Sstevel@tonic-gate 			amount += size;
2260Sstevel@tonic-gate 		block++;
227*1926Sas198278 		count++;
228*1926Sas198278 	} while (size == blocksize || count == 1);
2290Sstevel@tonic-gate abort:
2300Sstevel@tonic-gate 	cancel_alarm();
2310Sstevel@tonic-gate 	(void) fclose(file);
2320Sstevel@tonic-gate 	stopclock();
2330Sstevel@tonic-gate 	if (amount > 0)
2340Sstevel@tonic-gate 		printstats("Sent", amount);
2350Sstevel@tonic-gate }
2360Sstevel@tonic-gate 
2370Sstevel@tonic-gate /*
2380Sstevel@tonic-gate  * Receive a file.
2390Sstevel@tonic-gate  */
2400Sstevel@tonic-gate void
tftp_recvfile(int fd,char * name,char * mode)2410Sstevel@tonic-gate tftp_recvfile(int fd, char *name, char *mode)
2420Sstevel@tonic-gate {
2430Sstevel@tonic-gate 	struct tftphdr *ap;
2440Sstevel@tonic-gate 	struct tftphdr *dp;
245*1926Sas198278 	ushort_t block = 1;
246*1926Sas198278 	int n, size;
2470Sstevel@tonic-gate 	unsigned long amount = 0;
2480Sstevel@tonic-gate 	struct sockaddr_in6 from;
2490Sstevel@tonic-gate 	socklen_t fromlen;
2500Sstevel@tonic-gate 	boolean_t firsttrip = B_TRUE;
2510Sstevel@tonic-gate 	FILE *file;
2520Sstevel@tonic-gate 	int convert;	/* true if converting crlf -> lf */
2530Sstevel@tonic-gate 	int errcode;
2540Sstevel@tonic-gate 
2550Sstevel@tonic-gate 	startclock();
2560Sstevel@tonic-gate 	dp = w_init();
2570Sstevel@tonic-gate 	ap = &ackbuf.tb_hdr;
2580Sstevel@tonic-gate 	file = fdopen(fd, "w");
2590Sstevel@tonic-gate 	convert = (strcmp(mode, "netascii") == 0);
2600Sstevel@tonic-gate 
2610Sstevel@tonic-gate 	tsize_set = (tsize_opt != 0);
2620Sstevel@tonic-gate 	if (tsize_set)
2630Sstevel@tonic-gate 		tsize = 0;
2640Sstevel@tonic-gate 
2650Sstevel@tonic-gate 	if ((size = makerequest(RRQ, name, ap, mode)) == -1) {
2660Sstevel@tonic-gate 		(void) fprintf(stderr,
2670Sstevel@tonic-gate 		    "tftp: Error: Read request packet too big\n");
2680Sstevel@tonic-gate 		(void) fclose(file);
2690Sstevel@tonic-gate 		return;
2700Sstevel@tonic-gate 	}
2710Sstevel@tonic-gate 
2720Sstevel@tonic-gate 	do {
2730Sstevel@tonic-gate 		(void) signal(SIGALRM, timer);
2740Sstevel@tonic-gate 		if (firsttrip) {
2750Sstevel@tonic-gate 			firsttrip = B_FALSE;
2760Sstevel@tonic-gate 		} else {
2770Sstevel@tonic-gate 			ap->th_opcode = htons((ushort_t)ACK);
2780Sstevel@tonic-gate 			ap->th_block = htons((ushort_t)(block));
2790Sstevel@tonic-gate 			size = 4;
2800Sstevel@tonic-gate 			block++;
2810Sstevel@tonic-gate 		}
2820Sstevel@tonic-gate 
2830Sstevel@tonic-gate send_oack_ack:
2840Sstevel@tonic-gate 		timeout = 0;
2850Sstevel@tonic-gate 		(void) setjmp(timeoutbuf);
2860Sstevel@tonic-gate send_ack:
2870Sstevel@tonic-gate 		if (trace)
2880Sstevel@tonic-gate 			tpacket("sent", ap, size);
2890Sstevel@tonic-gate 		if (sendto(f, ackbuf.tb_data, size, 0, (struct sockaddr *)&sin6,
2900Sstevel@tonic-gate 		    sizeof (sin6)) != size) {
2910Sstevel@tonic-gate 			(void) alarm(0);
2920Sstevel@tonic-gate 			perror("tftp: sendto");
2930Sstevel@tonic-gate 			goto abort;
2940Sstevel@tonic-gate 		}
2950Sstevel@tonic-gate 		if (write_behind(file, convert) < 0) {
2960Sstevel@tonic-gate 			nak(errno + 100);
2970Sstevel@tonic-gate 			goto abort;
2980Sstevel@tonic-gate 		}
2990Sstevel@tonic-gate 		(void) alarm(rexmtval);
3000Sstevel@tonic-gate 		for (; ; ) {
3010Sstevel@tonic-gate 			(void) sigrelse(SIGALRM);
3020Sstevel@tonic-gate 			do  {
3030Sstevel@tonic-gate 				fromlen = (socklen_t)sizeof (from);
3040Sstevel@tonic-gate 				n = recvfrom(f, dp, blocksize + 4, 0,
3050Sstevel@tonic-gate 				    (struct sockaddr *)&from, &fromlen);
3060Sstevel@tonic-gate 				if (n < 0) {
3070Sstevel@tonic-gate 					perror("tftp: recvfrom");
3080Sstevel@tonic-gate 					goto abort;
3090Sstevel@tonic-gate 				}
3100Sstevel@tonic-gate 			} while (n < offsetof(struct tftphdr, th_data));
3110Sstevel@tonic-gate 			(void) sighold(SIGALRM);
3120Sstevel@tonic-gate 			sin6.sin6_port = from.sin6_port;   /* added */
3130Sstevel@tonic-gate 			if (trace)
3140Sstevel@tonic-gate 				tpacket("received", dp, n);
3150Sstevel@tonic-gate 			/* should verify client address */
3160Sstevel@tonic-gate 			dp->th_opcode = ntohs(dp->th_opcode);
3170Sstevel@tonic-gate 			if (dp->th_opcode == ERROR) {
3180Sstevel@tonic-gate 				dp->th_code = ntohs(dp->th_code);
3190Sstevel@tonic-gate 				(void) fprintf(stderr, "Error code %d",
3200Sstevel@tonic-gate 				    dp->th_code);
3210Sstevel@tonic-gate 				if (n > offsetof(struct tftphdr, th_data))
3220Sstevel@tonic-gate 					(void) fprintf(stderr, ": %.*s", n -
3230Sstevel@tonic-gate 					    offsetof(struct tftphdr, th_data),
3240Sstevel@tonic-gate 					    dp->th_msg);
3250Sstevel@tonic-gate 				(void) fputc('\n', stderr);
3260Sstevel@tonic-gate 				goto abort;
3270Sstevel@tonic-gate 			}
3280Sstevel@tonic-gate 			if ((block == 1) && (dp->th_opcode == OACK)) {
3290Sstevel@tonic-gate 				errcode = process_oack((tftpbuf *)dp, n);
3300Sstevel@tonic-gate 				if (errcode >= 0) {
3310Sstevel@tonic-gate 					cancel_alarm();
3320Sstevel@tonic-gate 					nak(errcode);
3330Sstevel@tonic-gate 					(void) fputs("Rejected OACK\n",
3340Sstevel@tonic-gate 					    stderr);
3350Sstevel@tonic-gate 					(void) fclose(file);
3360Sstevel@tonic-gate 					return;
3370Sstevel@tonic-gate 				}
3380Sstevel@tonic-gate 				ap->th_opcode = htons((ushort_t)ACK);
3390Sstevel@tonic-gate 				ap->th_block = htons(0);
3400Sstevel@tonic-gate 				size = 4;
3410Sstevel@tonic-gate 				goto send_oack_ack;
3420Sstevel@tonic-gate 			}
3430Sstevel@tonic-gate 			if (dp->th_opcode == DATA) {
3440Sstevel@tonic-gate 				int j;
3450Sstevel@tonic-gate 
3460Sstevel@tonic-gate 				dp->th_block = ntohs(dp->th_block);
3470Sstevel@tonic-gate 				if (dp->th_block == block) {
3480Sstevel@tonic-gate 					break;	/* have next packet */
3490Sstevel@tonic-gate 				}
3500Sstevel@tonic-gate 				/*
3510Sstevel@tonic-gate 				 * On an error, try to synchronize
3520Sstevel@tonic-gate 				 * both sides.
3530Sstevel@tonic-gate 				 */
3540Sstevel@tonic-gate 				j = synchnet(f);
3550Sstevel@tonic-gate 				if (j < 0) {
3560Sstevel@tonic-gate 					perror("tftp: recvfrom");
3570Sstevel@tonic-gate 					goto abort;
3580Sstevel@tonic-gate 				}
3590Sstevel@tonic-gate 				if ((j > 0) && trace) {
3600Sstevel@tonic-gate 					(void) printf("discarded %d packets\n",
3610Sstevel@tonic-gate 					    j);
3620Sstevel@tonic-gate 				}
3630Sstevel@tonic-gate 				if (dp->th_block == (block-1)) {
3640Sstevel@tonic-gate 					goto send_ack;  /* resend ack */
3650Sstevel@tonic-gate 				}
3660Sstevel@tonic-gate 			}
3670Sstevel@tonic-gate 		}
3680Sstevel@tonic-gate 		cancel_alarm();
3690Sstevel@tonic-gate 		size = writeit(file, &dp, n - 4, convert);
3700Sstevel@tonic-gate 		if (size < 0) {
3710Sstevel@tonic-gate 			nak(errno + 100);
3720Sstevel@tonic-gate 			goto abort;
3730Sstevel@tonic-gate 		}
3740Sstevel@tonic-gate 		amount += size;
3750Sstevel@tonic-gate 	} while (size == blocksize);
3760Sstevel@tonic-gate 
3770Sstevel@tonic-gate 	cancel_alarm();
3780Sstevel@tonic-gate 	if (write_behind(file, convert) < 0) {	/* flush last buffer */
3790Sstevel@tonic-gate 		nak(errno + 100);
3800Sstevel@tonic-gate 		goto abort;
3810Sstevel@tonic-gate 	}
3820Sstevel@tonic-gate 	n = fclose(file);
3830Sstevel@tonic-gate 	file = NULL;
3840Sstevel@tonic-gate 	if (n == EOF) {
3850Sstevel@tonic-gate 		nak(errno + 100);
3860Sstevel@tonic-gate 		goto abort;
3870Sstevel@tonic-gate 	}
3880Sstevel@tonic-gate 
3890Sstevel@tonic-gate 	/* ok to ack, since user has seen err msg */
3900Sstevel@tonic-gate 	ap->th_opcode = htons((ushort_t)ACK);
3910Sstevel@tonic-gate 	ap->th_block = htons((ushort_t)block);
3920Sstevel@tonic-gate 	if (trace)
3930Sstevel@tonic-gate 		tpacket("sent", ap, 4);
3940Sstevel@tonic-gate 	if (sendto(f, ackbuf.tb_data, 4, 0,
3950Sstevel@tonic-gate 	    (struct sockaddr *)&sin6, sizeof (sin6)) != 4)
3960Sstevel@tonic-gate 		perror("tftp: sendto");
3970Sstevel@tonic-gate 
3980Sstevel@tonic-gate abort:
3990Sstevel@tonic-gate 	cancel_alarm();
4000Sstevel@tonic-gate 	if (file != NULL)
4010Sstevel@tonic-gate 		(void) fclose(file);
4020Sstevel@tonic-gate 	stopclock();
4030Sstevel@tonic-gate 	if (amount > 0)
4040Sstevel@tonic-gate 		printstats("Received", amount);
4050Sstevel@tonic-gate }
4060Sstevel@tonic-gate 
4070Sstevel@tonic-gate static int
makerequest(int request,char * name,struct tftphdr * tp,char * mode)4080Sstevel@tonic-gate makerequest(int request, char *name, struct tftphdr *tp, char *mode)
4090Sstevel@tonic-gate {
4100Sstevel@tonic-gate 	char *cp, *cpend;
4110Sstevel@tonic-gate 	int len;
4120Sstevel@tonic-gate 
4130Sstevel@tonic-gate 	tp->th_opcode = htons((ushort_t)request);
4140Sstevel@tonic-gate 	cp = (char *)&tp->th_stuff;
4150Sstevel@tonic-gate 
4160Sstevel@tonic-gate 	/* Maximum size of a request packet is 512 bytes (RFC 2347) */
4170Sstevel@tonic-gate 	cpend = (char *)tp + SEGSIZE;
4180Sstevel@tonic-gate 
4190Sstevel@tonic-gate 	len = strlcpy(cp, name, cpend - cp) + 1;
4200Sstevel@tonic-gate 	cp += len;
4210Sstevel@tonic-gate 	if (cp > cpend)
4220Sstevel@tonic-gate 		return (-1);
4230Sstevel@tonic-gate 
4240Sstevel@tonic-gate 	len = strlcpy(cp, mode, cpend - cp) + 1;
4250Sstevel@tonic-gate 	cp += len;
4260Sstevel@tonic-gate 	if (cp > cpend)
4270Sstevel@tonic-gate 		return (-1);
4280Sstevel@tonic-gate 
4290Sstevel@tonic-gate 	len = add_options(cp, cpend);
4300Sstevel@tonic-gate 	if (len == -1)
4310Sstevel@tonic-gate 		return (-1);
4320Sstevel@tonic-gate 	cp += len;
4330Sstevel@tonic-gate 
4340Sstevel@tonic-gate 	return (cp - (char *)tp);
4350Sstevel@tonic-gate }
4360Sstevel@tonic-gate 
4370Sstevel@tonic-gate /*
4380Sstevel@tonic-gate  * Return the blksize option value string to include in the request packet.
4390Sstevel@tonic-gate  */
4400Sstevel@tonic-gate static char *
blksize_str(void)4410Sstevel@tonic-gate blksize_str(void)
4420Sstevel@tonic-gate {
4430Sstevel@tonic-gate 	blocksize = SEGSIZE;
4440Sstevel@tonic-gate 	if (blksize == 0)
4450Sstevel@tonic-gate 		return (NULL);
4460Sstevel@tonic-gate 
4470Sstevel@tonic-gate 	(void) snprintf(optbuf, sizeof (optbuf), "%d", blksize);
4480Sstevel@tonic-gate 	return (optbuf);
4490Sstevel@tonic-gate }
4500Sstevel@tonic-gate 
4510Sstevel@tonic-gate /*
4520Sstevel@tonic-gate  * Return the timeout option value string to include in the request packet.
4530Sstevel@tonic-gate  */
4540Sstevel@tonic-gate static char *
timeout_str(void)4550Sstevel@tonic-gate timeout_str(void)
4560Sstevel@tonic-gate {
4570Sstevel@tonic-gate 	if (srexmtval == 0)
4580Sstevel@tonic-gate 		return (NULL);
4590Sstevel@tonic-gate 
4600Sstevel@tonic-gate 	(void) snprintf(optbuf, sizeof (optbuf), "%d", srexmtval);
4610Sstevel@tonic-gate 	return (optbuf);
4620Sstevel@tonic-gate }
4630Sstevel@tonic-gate 
4640Sstevel@tonic-gate /*
4650Sstevel@tonic-gate  * Return the tsize option value string to include in the request packet.
4660Sstevel@tonic-gate  */
4670Sstevel@tonic-gate static char *
tsize_str(void)4680Sstevel@tonic-gate tsize_str(void)
4690Sstevel@tonic-gate {
4700Sstevel@tonic-gate 	if (tsize_set == B_FALSE)
4710Sstevel@tonic-gate 		return (NULL);
4720Sstevel@tonic-gate 
4730Sstevel@tonic-gate 	(void) snprintf(optbuf, sizeof (optbuf), OFF_T_FMT, tsize);
4740Sstevel@tonic-gate 	return (optbuf);
4750Sstevel@tonic-gate }
4760Sstevel@tonic-gate 
4770Sstevel@tonic-gate /*
4780Sstevel@tonic-gate  * Validate and action the blksize option value string from the OACK packet.
4790Sstevel@tonic-gate  * Returns -1 on success or an error code on failure.
4800Sstevel@tonic-gate  */
4810Sstevel@tonic-gate static int
blksize_handler(char * optstr)4820Sstevel@tonic-gate blksize_handler(char *optstr)
4830Sstevel@tonic-gate {
4840Sstevel@tonic-gate 	char *endp;
4850Sstevel@tonic-gate 	int value;
4860Sstevel@tonic-gate 
4870Sstevel@tonic-gate 	/* Make sure the option was requested */
4880Sstevel@tonic-gate 	if (blksize == 0)
4890Sstevel@tonic-gate 		return (EOPTNEG);
4900Sstevel@tonic-gate 	errno = 0;
4910Sstevel@tonic-gate 	value = (int)strtol(optstr, &endp, 10);
4920Sstevel@tonic-gate 	if (errno != 0 || value < MIN_BLKSIZE || value > blksize ||
4930Sstevel@tonic-gate 	    *endp != '\0')
4940Sstevel@tonic-gate 		return (EOPTNEG);
4950Sstevel@tonic-gate 	blocksize = value;
4960Sstevel@tonic-gate 	return (-1);
4970Sstevel@tonic-gate }
4980Sstevel@tonic-gate 
4990Sstevel@tonic-gate /*
5000Sstevel@tonic-gate  * Validate and action the timeout option value string from the OACK packet.
5010Sstevel@tonic-gate  * Returns -1 on success or an error code on failure.
5020Sstevel@tonic-gate  */
5030Sstevel@tonic-gate static int
timeout_handler(char * optstr)5040Sstevel@tonic-gate timeout_handler(char *optstr)
5050Sstevel@tonic-gate {
5060Sstevel@tonic-gate 	char *endp;
5070Sstevel@tonic-gate 	int value;
5080Sstevel@tonic-gate 
5090Sstevel@tonic-gate 	/* Make sure the option was requested */
5100Sstevel@tonic-gate 	if (srexmtval == 0)
5110Sstevel@tonic-gate 		return (EOPTNEG);
5120Sstevel@tonic-gate 	errno = 0;
5130Sstevel@tonic-gate 	value = (int)strtol(optstr, &endp, 10);
5140Sstevel@tonic-gate 	if (errno != 0 || value != srexmtval || *endp != '\0')
5150Sstevel@tonic-gate 		return (EOPTNEG);
5160Sstevel@tonic-gate 	/*
5170Sstevel@tonic-gate 	 * Nothing to set, client and server retransmission intervals are
5180Sstevel@tonic-gate 	 * set separately in the client.
5190Sstevel@tonic-gate 	 */
5200Sstevel@tonic-gate 	return (-1);
5210Sstevel@tonic-gate }
5220Sstevel@tonic-gate 
5230Sstevel@tonic-gate /*
5240Sstevel@tonic-gate  * Validate and action the tsize option value string from the OACK packet.
5250Sstevel@tonic-gate  * Returns -1 on success or an error code on failure.
5260Sstevel@tonic-gate  */
5270Sstevel@tonic-gate static int
tsize_handler(char * optstr)5280Sstevel@tonic-gate tsize_handler(char *optstr)
5290Sstevel@tonic-gate {
5300Sstevel@tonic-gate 	char *endp;
5310Sstevel@tonic-gate 	longlong_t value;
5320Sstevel@tonic-gate 
5330Sstevel@tonic-gate 	/* Make sure the option was requested */
5340Sstevel@tonic-gate 	if (tsize_set == B_FALSE)
5350Sstevel@tonic-gate 		return (EOPTNEG);
5360Sstevel@tonic-gate 	errno = 0;
5370Sstevel@tonic-gate 	value = strtoll(optstr, &endp, 10);
5380Sstevel@tonic-gate 	if (errno != 0 || value < 0 || *endp != '\0')
5390Sstevel@tonic-gate 		return (EOPTNEG);
5400Sstevel@tonic-gate #if _FILE_OFFSET_BITS == 32
5410Sstevel@tonic-gate 	if (value > MAXOFF_T)
5420Sstevel@tonic-gate 		return (ENOSPACE);
5430Sstevel@tonic-gate #endif
5440Sstevel@tonic-gate 	/*
5450Sstevel@tonic-gate 	 * Don't bother checking the tsize value we specified in a write
5460Sstevel@tonic-gate 	 * request is echoed back in the OACK.
5470Sstevel@tonic-gate 	 */
5480Sstevel@tonic-gate 	if (tsize == 0)
5490Sstevel@tonic-gate 		tsize = value;
5500Sstevel@tonic-gate 	return (-1);
5510Sstevel@tonic-gate }
5520Sstevel@tonic-gate 
5530Sstevel@tonic-gate /*
5540Sstevel@tonic-gate  * Add TFTP options to a request packet.
5550Sstevel@tonic-gate  */
5560Sstevel@tonic-gate static int
add_options(char * obuf,char * obufend)5570Sstevel@tonic-gate add_options(char *obuf, char *obufend)
5580Sstevel@tonic-gate {
5590Sstevel@tonic-gate 	int i;
5600Sstevel@tonic-gate 	char *cp, *ostr;
5610Sstevel@tonic-gate 
5620Sstevel@tonic-gate 	cp = obuf;
5630Sstevel@tonic-gate 	for (i = 0; options[i].opt_name != NULL; i++) {
5640Sstevel@tonic-gate 		ostr = options[i].opt_str();
5650Sstevel@tonic-gate 		if (ostr != NULL) {
5660Sstevel@tonic-gate 			cp += strlcpy(cp, options[i].opt_name, obufend - cp)
5670Sstevel@tonic-gate 			    + 1;
5680Sstevel@tonic-gate 			if (cp > obufend)
5690Sstevel@tonic-gate 				return (-1);
5700Sstevel@tonic-gate 
5710Sstevel@tonic-gate 			cp += strlcpy(cp, ostr, obufend - cp) + 1;
5720Sstevel@tonic-gate 			if (cp > obufend)
5730Sstevel@tonic-gate 				return (-1);
5740Sstevel@tonic-gate 		}
5750Sstevel@tonic-gate 	}
5760Sstevel@tonic-gate 	return (cp - obuf);
5770Sstevel@tonic-gate }
5780Sstevel@tonic-gate 
5790Sstevel@tonic-gate /*
5800Sstevel@tonic-gate  * Process OACK packet sent by server in response to options in the request
5810Sstevel@tonic-gate  * packet. Returns -1 on success or an error code on failure.
5820Sstevel@tonic-gate  */
5830Sstevel@tonic-gate static int
process_oack(tftpbuf * oackbuf,int n)5840Sstevel@tonic-gate process_oack(tftpbuf *oackbuf, int n)
5850Sstevel@tonic-gate {
5860Sstevel@tonic-gate 	char *cp, *oackend, *optname, *optval;
5870Sstevel@tonic-gate 	struct tftphdr *oackp;
5880Sstevel@tonic-gate 	int i, errcode;
5890Sstevel@tonic-gate 
5900Sstevel@tonic-gate 	oackp = &oackbuf->tb_hdr;
5910Sstevel@tonic-gate 	cp = (char *)&oackp->th_stuff;
5920Sstevel@tonic-gate 	oackend = (char *)oackbuf + n;
5930Sstevel@tonic-gate 
5940Sstevel@tonic-gate 	while (cp < oackend) {
5950Sstevel@tonic-gate 		optname = cp;
5960Sstevel@tonic-gate 		if ((optval = next_field(optname, oackend)) == NULL)
5970Sstevel@tonic-gate 			return (EOPTNEG);
5980Sstevel@tonic-gate 		if ((cp = next_field(optval, oackend)) == NULL)
5990Sstevel@tonic-gate 			return (EOPTNEG);
6000Sstevel@tonic-gate 		for (i = 0; options[i].opt_name != NULL; i++) {
6010Sstevel@tonic-gate 			if (strcasecmp(optname, options[i].opt_name) == 0)
6020Sstevel@tonic-gate 				break;
6030Sstevel@tonic-gate 		}
6040Sstevel@tonic-gate 		if (options[i].opt_name == NULL)
6050Sstevel@tonic-gate 			return (EOPTNEG);
6060Sstevel@tonic-gate 		errcode = options[i].opt_handler(optval);
6070Sstevel@tonic-gate 		if (errcode >= 0)
6080Sstevel@tonic-gate 			return (errcode);
6090Sstevel@tonic-gate 	}
6100Sstevel@tonic-gate 	return (-1);
6110Sstevel@tonic-gate }
6120Sstevel@tonic-gate 
6130Sstevel@tonic-gate /*
6140Sstevel@tonic-gate  * Send a nak packet (error message).
6150Sstevel@tonic-gate  * Error code passed in is one of the
6160Sstevel@tonic-gate  * standard TFTP codes, or a UNIX errno
6170Sstevel@tonic-gate  * offset by 100.
6180Sstevel@tonic-gate  */
6190Sstevel@tonic-gate static void
nak(int error)6200Sstevel@tonic-gate nak(int error)
6210Sstevel@tonic-gate {
6220Sstevel@tonic-gate 	struct tftphdr *tp;
6230Sstevel@tonic-gate 	int length;
6240Sstevel@tonic-gate 	struct errmsg *pe;
6250Sstevel@tonic-gate 
6260Sstevel@tonic-gate 	tp = &ackbuf.tb_hdr;
6270Sstevel@tonic-gate 	tp->th_opcode = htons((ushort_t)ERROR);
6280Sstevel@tonic-gate 	tp->th_code = htons((ushort_t)error);
6290Sstevel@tonic-gate 	for (pe = errmsgs; pe->e_code >= 0; pe++)
6300Sstevel@tonic-gate 		if (pe->e_code == error)
6310Sstevel@tonic-gate 			break;
6320Sstevel@tonic-gate 	if (pe->e_code < 0) {
6330Sstevel@tonic-gate 		pe->e_msg = strerror(error - 100);
6340Sstevel@tonic-gate 		tp->th_code = EUNDEF;
6350Sstevel@tonic-gate 	}
6360Sstevel@tonic-gate 	(void) strlcpy(tp->th_msg, pe->e_msg,
6370Sstevel@tonic-gate 	    sizeof (ackbuf) - sizeof (struct tftphdr));
6380Sstevel@tonic-gate 	length = strlen(pe->e_msg) + 4;
6390Sstevel@tonic-gate 	if (trace)
6400Sstevel@tonic-gate 		tpacket("sent", tp, length);
6410Sstevel@tonic-gate 	if (sendto(f, ackbuf.tb_data, length, 0,
6420Sstevel@tonic-gate 	    (struct sockaddr *)&sin6, sizeof (sin6)) != length)
6430Sstevel@tonic-gate 		perror("nak");
6440Sstevel@tonic-gate }
6450Sstevel@tonic-gate 
6460Sstevel@tonic-gate static void
tpacket(char * s,struct tftphdr * tp,int n)6470Sstevel@tonic-gate tpacket(char *s, struct tftphdr *tp, int n)
6480Sstevel@tonic-gate {
6490Sstevel@tonic-gate 	static char *opcodes[] = \
6500Sstevel@tonic-gate 		{ "#0", "RRQ", "WRQ", "DATA", "ACK", "ERROR", "OACK" };
6510Sstevel@tonic-gate 	char *cp, *file, *mode;
6520Sstevel@tonic-gate 	ushort_t op = ntohs(tp->th_opcode);
6530Sstevel@tonic-gate 	char *tpend;
6540Sstevel@tonic-gate 
6550Sstevel@tonic-gate 	if (op < RRQ || op > OACK)
6560Sstevel@tonic-gate 		(void) printf("%s opcode=%x ", s, op);
6570Sstevel@tonic-gate 	else
6580Sstevel@tonic-gate 		(void) printf("%s %s ", s, opcodes[op]);
6590Sstevel@tonic-gate 
6600Sstevel@tonic-gate 	switch (op) {
6610Sstevel@tonic-gate 	case RRQ:
6620Sstevel@tonic-gate 	case WRQ:
6630Sstevel@tonic-gate 		tpend = (char *)tp + n;
6640Sstevel@tonic-gate 		n -= sizeof (tp->th_opcode);
6650Sstevel@tonic-gate 		file = (char *)&tp->th_stuff;
6660Sstevel@tonic-gate 		if ((mode = next_field(file, tpend)) == NULL) {
6670Sstevel@tonic-gate 			(void) printf("<file=%.*s>\n", n, file);
6680Sstevel@tonic-gate 			break;
6690Sstevel@tonic-gate 		}
6700Sstevel@tonic-gate 		n -= mode - file;
6710Sstevel@tonic-gate 		if ((cp = next_field(mode, tpend)) == NULL) {
6720Sstevel@tonic-gate 			(void) printf("<file=%s, mode=%.*s>\n", file, n, mode);
6730Sstevel@tonic-gate 			break;
6740Sstevel@tonic-gate 		}
6750Sstevel@tonic-gate 		(void) printf("<file=%s, mode=%s", file, mode);
6760Sstevel@tonic-gate 		n -= cp - mode;
6770Sstevel@tonic-gate 		if (n > 0) {
6780Sstevel@tonic-gate 			(void) printf(", options: ");
6790Sstevel@tonic-gate 			print_options(stdout, cp, n);
6800Sstevel@tonic-gate 		}
6810Sstevel@tonic-gate 		(void) puts(">");
6820Sstevel@tonic-gate 		break;
6830Sstevel@tonic-gate 
6840Sstevel@tonic-gate 	case DATA:
6850Sstevel@tonic-gate 		(void) printf("<block=%d, %d bytes>\n", ntohs(tp->th_block),
6860Sstevel@tonic-gate 		    n - sizeof (tp->th_opcode) - sizeof (tp->th_block));
6870Sstevel@tonic-gate 		break;
6880Sstevel@tonic-gate 
6890Sstevel@tonic-gate 	case ACK:
6900Sstevel@tonic-gate 		(void) printf("<block=%d>\n", ntohs(tp->th_block));
6910Sstevel@tonic-gate 		break;
6920Sstevel@tonic-gate 
6930Sstevel@tonic-gate 	case OACK:
6940Sstevel@tonic-gate 		(void) printf("<options: ");
6950Sstevel@tonic-gate 		print_options(stdout, (char *)&tp->th_stuff,
6960Sstevel@tonic-gate 		    n - sizeof (tp->th_opcode));
6970Sstevel@tonic-gate 		(void) puts(">");
6980Sstevel@tonic-gate 		break;
6990Sstevel@tonic-gate 
7000Sstevel@tonic-gate 	case ERROR:
7010Sstevel@tonic-gate 		(void) printf("<code=%d", ntohs(tp->th_code));
7020Sstevel@tonic-gate 		n = n - sizeof (tp->th_opcode) - sizeof (tp->th_code);
7030Sstevel@tonic-gate 		if (n > 0)
7040Sstevel@tonic-gate 			(void) printf(", msg=%.*s", n, tp->th_msg);
7050Sstevel@tonic-gate 		(void) puts(">");
7060Sstevel@tonic-gate 		break;
7070Sstevel@tonic-gate 	}
7080Sstevel@tonic-gate }
7090Sstevel@tonic-gate 
7100Sstevel@tonic-gate static hrtime_t	tstart, tstop;
7110Sstevel@tonic-gate 
7120Sstevel@tonic-gate static void
startclock(void)7130Sstevel@tonic-gate startclock(void)
7140Sstevel@tonic-gate {
7150Sstevel@tonic-gate 	tstart = gethrtime();
7160Sstevel@tonic-gate }
7170Sstevel@tonic-gate 
7180Sstevel@tonic-gate static void
stopclock(void)7190Sstevel@tonic-gate stopclock(void)
7200Sstevel@tonic-gate {
7210Sstevel@tonic-gate 	tstop = gethrtime();
7220Sstevel@tonic-gate }
7230Sstevel@tonic-gate 
7240Sstevel@tonic-gate static void
printstats(char * direction,off_t amount)7250Sstevel@tonic-gate printstats(char *direction, off_t amount)
7260Sstevel@tonic-gate {
7270Sstevel@tonic-gate 	hrtime_t	delta, tenths;
7280Sstevel@tonic-gate 
7290Sstevel@tonic-gate 	delta = tstop - tstart;
7300Sstevel@tonic-gate 	tenths = delta / (NANOSEC / 10);
7310Sstevel@tonic-gate 	(void) printf("%s " OFF_T_FMT " bytes in %" PRId64 ".%" PRId64
7320Sstevel@tonic-gate 	    " seconds", direction, amount, tenths / 10, tenths % 10);
7330Sstevel@tonic-gate 	if (verbose)
7340Sstevel@tonic-gate 		(void) printf(" [%" PRId64 " bits/sec]\n",
7350Sstevel@tonic-gate 		    ((hrtime_t)amount * 8 * NANOSEC) / delta);
7360Sstevel@tonic-gate 	else
7370Sstevel@tonic-gate 		(void) putchar('\n');
7380Sstevel@tonic-gate }
739