1*0Sstevel@tonic-gate /*
2*0Sstevel@tonic-gate  * CDDL HEADER START
3*0Sstevel@tonic-gate  *
4*0Sstevel@tonic-gate  * The contents of this file are subject to the terms of the
5*0Sstevel@tonic-gate  * Common Development and Distribution License, Version 1.0 only
6*0Sstevel@tonic-gate  * (the "License").  You may not use this file except in compliance
7*0Sstevel@tonic-gate  * with the License.
8*0Sstevel@tonic-gate  *
9*0Sstevel@tonic-gate  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10*0Sstevel@tonic-gate  * or http://www.opensolaris.org/os/licensing.
11*0Sstevel@tonic-gate  * See the License for the specific language governing permissions
12*0Sstevel@tonic-gate  * and limitations under the License.
13*0Sstevel@tonic-gate  *
14*0Sstevel@tonic-gate  * When distributing Covered Code, include this CDDL HEADER in each
15*0Sstevel@tonic-gate  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16*0Sstevel@tonic-gate  * If applicable, add the following below this CDDL HEADER, with the
17*0Sstevel@tonic-gate  * fields enclosed by brackets "[]" replaced with your own identifying
18*0Sstevel@tonic-gate  * information: Portions Copyright [yyyy] [name of copyright owner]
19*0Sstevel@tonic-gate  *
20*0Sstevel@tonic-gate  * CDDL HEADER END
21*0Sstevel@tonic-gate  */
22*0Sstevel@tonic-gate /*
23*0Sstevel@tonic-gate  * Copyright 2002 Sun Microsystems, Inc.  All rights reserved.
24*0Sstevel@tonic-gate  * Use is subject to license terms.
25*0Sstevel@tonic-gate  */
26*0Sstevel@tonic-gate 
27*0Sstevel@tonic-gate /*	Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T	*/
28*0Sstevel@tonic-gate /*	  All Rights Reserved  	*/
29*0Sstevel@tonic-gate 
30*0Sstevel@tonic-gate /*
31*0Sstevel@tonic-gate  * University Copyright- Copyright (c) 1982, 1986, 1988
32*0Sstevel@tonic-gate  * The Regents of the University of California
33*0Sstevel@tonic-gate  * All Rights Reserved
34*0Sstevel@tonic-gate  *
35*0Sstevel@tonic-gate  * University Acknowledgment- Portions of this document are derived from
36*0Sstevel@tonic-gate  * software developed by the University of California, Berkeley, and its
37*0Sstevel@tonic-gate  * contributors.
38*0Sstevel@tonic-gate  */
39*0Sstevel@tonic-gate 
40*0Sstevel@tonic-gate #pragma ident	"%Z%%M%	%I%	%E% SMI"
41*0Sstevel@tonic-gate 
42*0Sstevel@tonic-gate /*
43*0Sstevel@tonic-gate  * TFTP User Program -- Protocol Machines
44*0Sstevel@tonic-gate  */
45*0Sstevel@tonic-gate #include <sys/types.h>
46*0Sstevel@tonic-gate #include <sys/socket.h>
47*0Sstevel@tonic-gate #include <sys/time.h>
48*0Sstevel@tonic-gate #include <sys/stat.h>
49*0Sstevel@tonic-gate 
50*0Sstevel@tonic-gate #include <signal.h>
51*0Sstevel@tonic-gate #include <stdio.h>
52*0Sstevel@tonic-gate #include <stdlib.h>
53*0Sstevel@tonic-gate #include <unistd.h>
54*0Sstevel@tonic-gate #include <errno.h>
55*0Sstevel@tonic-gate #include <string.h>
56*0Sstevel@tonic-gate #include <stddef.h>
57*0Sstevel@tonic-gate #include <inttypes.h>
58*0Sstevel@tonic-gate 
59*0Sstevel@tonic-gate #include "tftpcommon.h"
60*0Sstevel@tonic-gate #include "tftpprivate.h"
61*0Sstevel@tonic-gate 
62*0Sstevel@tonic-gate static char	*blksize_str(void);
63*0Sstevel@tonic-gate static char	*timeout_str(void);
64*0Sstevel@tonic-gate static char	*tsize_str(void);
65*0Sstevel@tonic-gate static int	blksize_handler(char *);
66*0Sstevel@tonic-gate static int	timeout_handler(char *);
67*0Sstevel@tonic-gate static int	tsize_handler(char *);
68*0Sstevel@tonic-gate static int	add_options(char *, char *);
69*0Sstevel@tonic-gate static int	process_oack(tftpbuf *, int);
70*0Sstevel@tonic-gate static void	nak(int);
71*0Sstevel@tonic-gate static void	startclock(void);
72*0Sstevel@tonic-gate static void	stopclock(void);
73*0Sstevel@tonic-gate static void	printstats(char *, off_t);
74*0Sstevel@tonic-gate static int	makerequest(int, char *, struct tftphdr *, char *);
75*0Sstevel@tonic-gate static void	tpacket(char *, struct tftphdr *, int);
76*0Sstevel@tonic-gate 
77*0Sstevel@tonic-gate static struct options {
78*0Sstevel@tonic-gate 	char	*opt_name;
79*0Sstevel@tonic-gate 	char	*(*opt_str)(void);
80*0Sstevel@tonic-gate 	int	(*opt_handler)(char *);
81*0Sstevel@tonic-gate } options[] = {
82*0Sstevel@tonic-gate 	{ "blksize",	blksize_str, blksize_handler },
83*0Sstevel@tonic-gate 	{ "timeout",	timeout_str, timeout_handler },
84*0Sstevel@tonic-gate 	{ "tsize",	tsize_str, tsize_handler },
85*0Sstevel@tonic-gate 	{ NULL }
86*0Sstevel@tonic-gate };
87*0Sstevel@tonic-gate 
88*0Sstevel@tonic-gate static char		optbuf[MAX_OPTVAL_LEN];
89*0Sstevel@tonic-gate static boolean_t	tsize_set;
90*0Sstevel@tonic-gate 
91*0Sstevel@tonic-gate static tftpbuf	ackbuf;
92*0Sstevel@tonic-gate static int	timeout;
93*0Sstevel@tonic-gate static off_t	tsize;
94*0Sstevel@tonic-gate static jmp_buf	timeoutbuf;
95*0Sstevel@tonic-gate 
96*0Sstevel@tonic-gate int	blocksize = SEGSIZE;	/* Number of data bytes in a DATA packet */
97*0Sstevel@tonic-gate 
98*0Sstevel@tonic-gate /*ARGSUSED*/
99*0Sstevel@tonic-gate static void
100*0Sstevel@tonic-gate timer(int signum)
101*0Sstevel@tonic-gate {
102*0Sstevel@tonic-gate 	timeout += rexmtval;
103*0Sstevel@tonic-gate 	if (timeout >= maxtimeout) {
104*0Sstevel@tonic-gate 		(void) fputs("Transfer timed out.\n", stderr);
105*0Sstevel@tonic-gate 		longjmp(toplevel, -1);
106*0Sstevel@tonic-gate 	}
107*0Sstevel@tonic-gate 	(void) signal(SIGALRM, timer);
108*0Sstevel@tonic-gate 	longjmp(timeoutbuf, 1);
109*0Sstevel@tonic-gate }
110*0Sstevel@tonic-gate 
111*0Sstevel@tonic-gate /*
112*0Sstevel@tonic-gate  * Send the requested file.
113*0Sstevel@tonic-gate  */
114*0Sstevel@tonic-gate void
115*0Sstevel@tonic-gate tftp_sendfile(int fd, char *name, char *mode)
116*0Sstevel@tonic-gate {
117*0Sstevel@tonic-gate 	struct tftphdr *ap;	/* data and ack packets */
118*0Sstevel@tonic-gate 	struct tftphdr *dp;
119*0Sstevel@tonic-gate 	int block = 0, size, n;
120*0Sstevel@tonic-gate 	off_t amount = 0;
121*0Sstevel@tonic-gate 	struct sockaddr_in6 from;
122*0Sstevel@tonic-gate 	socklen_t fromlen;
123*0Sstevel@tonic-gate 	int convert;	/* true if doing nl->crlf conversion */
124*0Sstevel@tonic-gate 	FILE *file;
125*0Sstevel@tonic-gate 	struct stat statb;
126*0Sstevel@tonic-gate 	int errcode;
127*0Sstevel@tonic-gate 
128*0Sstevel@tonic-gate 	startclock();	/* start stat's clock */
129*0Sstevel@tonic-gate 	dp = r_init();	/* reset fillbuf/read-ahead code */
130*0Sstevel@tonic-gate 	ap = &ackbuf.tb_hdr;
131*0Sstevel@tonic-gate 	file = fdopen(fd, "r");
132*0Sstevel@tonic-gate 	convert = (strcmp(mode, "netascii") == 0);
133*0Sstevel@tonic-gate 
134*0Sstevel@tonic-gate 	tsize_set = ((tsize_opt != 0) && !convert && (fstat(fd, &statb) == 0));
135*0Sstevel@tonic-gate 	if (tsize_set)
136*0Sstevel@tonic-gate 		tsize = statb.st_size;
137*0Sstevel@tonic-gate 
138*0Sstevel@tonic-gate 	do {
139*0Sstevel@tonic-gate 		(void) signal(SIGALRM, timer);
140*0Sstevel@tonic-gate 		if (block == 0) {
141*0Sstevel@tonic-gate 			if ((size = makerequest(WRQ, name, dp, mode)) == -1) {
142*0Sstevel@tonic-gate 				(void) fprintf(stderr,
143*0Sstevel@tonic-gate 				    "tftp: Error: Write request packet too "
144*0Sstevel@tonic-gate 				    "big\n");
145*0Sstevel@tonic-gate 				(void) fclose(file);
146*0Sstevel@tonic-gate 				return;
147*0Sstevel@tonic-gate 			}
148*0Sstevel@tonic-gate 			size -= 4;
149*0Sstevel@tonic-gate 		} else {
150*0Sstevel@tonic-gate 			size = readit(file, &dp, convert);
151*0Sstevel@tonic-gate 			if (size < 0) {
152*0Sstevel@tonic-gate 				nak(errno + 100);
153*0Sstevel@tonic-gate 				break;
154*0Sstevel@tonic-gate 			}
155*0Sstevel@tonic-gate 			dp->th_opcode = htons((ushort_t)DATA);
156*0Sstevel@tonic-gate 			dp->th_block = htons((ushort_t)block);
157*0Sstevel@tonic-gate 		}
158*0Sstevel@tonic-gate 		timeout = 0;
159*0Sstevel@tonic-gate 		(void) setjmp(timeoutbuf);
160*0Sstevel@tonic-gate 		if (trace)
161*0Sstevel@tonic-gate 			tpacket("sent", dp, size + 4);
162*0Sstevel@tonic-gate 		n = sendto(f, dp, size + 4, 0,
163*0Sstevel@tonic-gate 		    (struct sockaddr *)&sin6, sizeof (sin6));
164*0Sstevel@tonic-gate 		if (n != size + 4) {
165*0Sstevel@tonic-gate 			perror("tftp: sendto");
166*0Sstevel@tonic-gate 			goto abort;
167*0Sstevel@tonic-gate 		}
168*0Sstevel@tonic-gate 		/* Can't read-ahead first block as OACK may change blocksize */
169*0Sstevel@tonic-gate 		if (block != 0)
170*0Sstevel@tonic-gate 			read_ahead(file, convert);
171*0Sstevel@tonic-gate 		(void) alarm(rexmtval);
172*0Sstevel@tonic-gate 		for (; ; ) {
173*0Sstevel@tonic-gate 			(void) sigrelse(SIGALRM);
174*0Sstevel@tonic-gate 			do {
175*0Sstevel@tonic-gate 				fromlen = (socklen_t)sizeof (from);
176*0Sstevel@tonic-gate 				n = recvfrom(f, ackbuf.tb_data,
177*0Sstevel@tonic-gate 				    sizeof (ackbuf.tb_data), 0,
178*0Sstevel@tonic-gate 				    (struct sockaddr *)&from, &fromlen);
179*0Sstevel@tonic-gate 				if (n < 0) {
180*0Sstevel@tonic-gate 					perror("tftp: recvfrom");
181*0Sstevel@tonic-gate 					goto abort;
182*0Sstevel@tonic-gate 				}
183*0Sstevel@tonic-gate 			} while (n < offsetof(struct tftphdr, th_data));
184*0Sstevel@tonic-gate 			(void) sighold(SIGALRM);
185*0Sstevel@tonic-gate 			sin6.sin6_port = from.sin6_port;   /* added */
186*0Sstevel@tonic-gate 			if (trace)
187*0Sstevel@tonic-gate 				tpacket("received", ap, n);
188*0Sstevel@tonic-gate 			/* should verify packet came from server */
189*0Sstevel@tonic-gate 			ap->th_opcode = ntohs(ap->th_opcode);
190*0Sstevel@tonic-gate 			if (ap->th_opcode == ERROR) {
191*0Sstevel@tonic-gate 				ap->th_code = ntohs(ap->th_code);
192*0Sstevel@tonic-gate 				(void) fprintf(stderr,
193*0Sstevel@tonic-gate 					"Error code %d", ap->th_code);
194*0Sstevel@tonic-gate 				if (n > offsetof(struct tftphdr, th_data))
195*0Sstevel@tonic-gate 					(void) fprintf(stderr, ": %.*s", n -
196*0Sstevel@tonic-gate 					    offsetof(struct tftphdr, th_data),
197*0Sstevel@tonic-gate 					    ap->th_msg);
198*0Sstevel@tonic-gate 				(void) fputc('\n', stderr);
199*0Sstevel@tonic-gate 				goto abort;
200*0Sstevel@tonic-gate 			}
201*0Sstevel@tonic-gate 			if ((block == 0) && (ap->th_opcode == OACK)) {
202*0Sstevel@tonic-gate 				errcode = process_oack(&ackbuf, n);
203*0Sstevel@tonic-gate 				if (errcode >= 0) {
204*0Sstevel@tonic-gate 					nak(errcode);
205*0Sstevel@tonic-gate 					(void) fputs("Rejected OACK\n",
206*0Sstevel@tonic-gate 					    stderr);
207*0Sstevel@tonic-gate 					goto abort;
208*0Sstevel@tonic-gate 				}
209*0Sstevel@tonic-gate 				break;
210*0Sstevel@tonic-gate 			}
211*0Sstevel@tonic-gate 			if (ap->th_opcode == ACK) {
212*0Sstevel@tonic-gate 				ap->th_block = ntohs(ap->th_block);
213*0Sstevel@tonic-gate 				if (ap->th_block == block) {
214*0Sstevel@tonic-gate 					break;
215*0Sstevel@tonic-gate 				}
216*0Sstevel@tonic-gate 				/*
217*0Sstevel@tonic-gate 				 * Never resend the current DATA packet on
218*0Sstevel@tonic-gate 				 * receipt of a duplicate ACK, doing so would
219*0Sstevel@tonic-gate 				 * cause the "Sorcerer's Apprentice Syndrome".
220*0Sstevel@tonic-gate 				 */
221*0Sstevel@tonic-gate 			}
222*0Sstevel@tonic-gate 		}
223*0Sstevel@tonic-gate 		cancel_alarm();
224*0Sstevel@tonic-gate 		if (block > 0)
225*0Sstevel@tonic-gate 			amount += size;
226*0Sstevel@tonic-gate 		block++;
227*0Sstevel@tonic-gate 	} while (size == blocksize || block == 1);
228*0Sstevel@tonic-gate abort:
229*0Sstevel@tonic-gate 	cancel_alarm();
230*0Sstevel@tonic-gate 	(void) fclose(file);
231*0Sstevel@tonic-gate 	stopclock();
232*0Sstevel@tonic-gate 	if (amount > 0)
233*0Sstevel@tonic-gate 		printstats("Sent", amount);
234*0Sstevel@tonic-gate }
235*0Sstevel@tonic-gate 
236*0Sstevel@tonic-gate /*
237*0Sstevel@tonic-gate  * Receive a file.
238*0Sstevel@tonic-gate  */
239*0Sstevel@tonic-gate void
240*0Sstevel@tonic-gate tftp_recvfile(int fd, char *name, char *mode)
241*0Sstevel@tonic-gate {
242*0Sstevel@tonic-gate 	struct tftphdr *ap;
243*0Sstevel@tonic-gate 	struct tftphdr *dp;
244*0Sstevel@tonic-gate 	int block = 1, n, size;
245*0Sstevel@tonic-gate 	unsigned long amount = 0;
246*0Sstevel@tonic-gate 	struct sockaddr_in6 from;
247*0Sstevel@tonic-gate 	socklen_t fromlen;
248*0Sstevel@tonic-gate 	boolean_t firsttrip = B_TRUE;
249*0Sstevel@tonic-gate 	FILE *file;
250*0Sstevel@tonic-gate 	int convert;	/* true if converting crlf -> lf */
251*0Sstevel@tonic-gate 	int errcode;
252*0Sstevel@tonic-gate 
253*0Sstevel@tonic-gate 	startclock();
254*0Sstevel@tonic-gate 	dp = w_init();
255*0Sstevel@tonic-gate 	ap = &ackbuf.tb_hdr;
256*0Sstevel@tonic-gate 	file = fdopen(fd, "w");
257*0Sstevel@tonic-gate 	convert = (strcmp(mode, "netascii") == 0);
258*0Sstevel@tonic-gate 
259*0Sstevel@tonic-gate 	tsize_set = (tsize_opt != 0);
260*0Sstevel@tonic-gate 	if (tsize_set)
261*0Sstevel@tonic-gate 		tsize = 0;
262*0Sstevel@tonic-gate 
263*0Sstevel@tonic-gate 	if ((size = makerequest(RRQ, name, ap, mode)) == -1) {
264*0Sstevel@tonic-gate 		(void) fprintf(stderr,
265*0Sstevel@tonic-gate 		    "tftp: Error: Read request packet too big\n");
266*0Sstevel@tonic-gate 		(void) fclose(file);
267*0Sstevel@tonic-gate 		return;
268*0Sstevel@tonic-gate 	}
269*0Sstevel@tonic-gate 
270*0Sstevel@tonic-gate 	do {
271*0Sstevel@tonic-gate 		(void) signal(SIGALRM, timer);
272*0Sstevel@tonic-gate 		if (firsttrip) {
273*0Sstevel@tonic-gate 			firsttrip = B_FALSE;
274*0Sstevel@tonic-gate 		} else {
275*0Sstevel@tonic-gate 			ap->th_opcode = htons((ushort_t)ACK);
276*0Sstevel@tonic-gate 			ap->th_block = htons((ushort_t)(block));
277*0Sstevel@tonic-gate 			size = 4;
278*0Sstevel@tonic-gate 			block++;
279*0Sstevel@tonic-gate 		}
280*0Sstevel@tonic-gate 
281*0Sstevel@tonic-gate send_oack_ack:
282*0Sstevel@tonic-gate 		timeout = 0;
283*0Sstevel@tonic-gate 		(void) setjmp(timeoutbuf);
284*0Sstevel@tonic-gate send_ack:
285*0Sstevel@tonic-gate 		if (trace)
286*0Sstevel@tonic-gate 			tpacket("sent", ap, size);
287*0Sstevel@tonic-gate 		if (sendto(f, ackbuf.tb_data, size, 0, (struct sockaddr *)&sin6,
288*0Sstevel@tonic-gate 		    sizeof (sin6)) != size) {
289*0Sstevel@tonic-gate 			(void) alarm(0);
290*0Sstevel@tonic-gate 			perror("tftp: sendto");
291*0Sstevel@tonic-gate 			goto abort;
292*0Sstevel@tonic-gate 		}
293*0Sstevel@tonic-gate 		if (write_behind(file, convert) < 0) {
294*0Sstevel@tonic-gate 			nak(errno + 100);
295*0Sstevel@tonic-gate 			goto abort;
296*0Sstevel@tonic-gate 		}
297*0Sstevel@tonic-gate 		(void) alarm(rexmtval);
298*0Sstevel@tonic-gate 		for (; ; ) {
299*0Sstevel@tonic-gate 			(void) sigrelse(SIGALRM);
300*0Sstevel@tonic-gate 			do  {
301*0Sstevel@tonic-gate 				fromlen = (socklen_t)sizeof (from);
302*0Sstevel@tonic-gate 				n = recvfrom(f, dp, blocksize + 4, 0,
303*0Sstevel@tonic-gate 				    (struct sockaddr *)&from, &fromlen);
304*0Sstevel@tonic-gate 				if (n < 0) {
305*0Sstevel@tonic-gate 					perror("tftp: recvfrom");
306*0Sstevel@tonic-gate 					goto abort;
307*0Sstevel@tonic-gate 				}
308*0Sstevel@tonic-gate 			} while (n < offsetof(struct tftphdr, th_data));
309*0Sstevel@tonic-gate 			(void) sighold(SIGALRM);
310*0Sstevel@tonic-gate 			sin6.sin6_port = from.sin6_port;   /* added */
311*0Sstevel@tonic-gate 			if (trace)
312*0Sstevel@tonic-gate 				tpacket("received", dp, n);
313*0Sstevel@tonic-gate 			/* should verify client address */
314*0Sstevel@tonic-gate 			dp->th_opcode = ntohs(dp->th_opcode);
315*0Sstevel@tonic-gate 			if (dp->th_opcode == ERROR) {
316*0Sstevel@tonic-gate 				dp->th_code = ntohs(dp->th_code);
317*0Sstevel@tonic-gate 				(void) fprintf(stderr, "Error code %d",
318*0Sstevel@tonic-gate 				    dp->th_code);
319*0Sstevel@tonic-gate 				if (n > offsetof(struct tftphdr, th_data))
320*0Sstevel@tonic-gate 					(void) fprintf(stderr, ": %.*s", n -
321*0Sstevel@tonic-gate 					    offsetof(struct tftphdr, th_data),
322*0Sstevel@tonic-gate 					    dp->th_msg);
323*0Sstevel@tonic-gate 				(void) fputc('\n', stderr);
324*0Sstevel@tonic-gate 				goto abort;
325*0Sstevel@tonic-gate 			}
326*0Sstevel@tonic-gate 			if ((block == 1) && (dp->th_opcode == OACK)) {
327*0Sstevel@tonic-gate 				errcode = process_oack((tftpbuf *)dp, n);
328*0Sstevel@tonic-gate 				if (errcode >= 0) {
329*0Sstevel@tonic-gate 					cancel_alarm();
330*0Sstevel@tonic-gate 					nak(errcode);
331*0Sstevel@tonic-gate 					(void) fputs("Rejected OACK\n",
332*0Sstevel@tonic-gate 					    stderr);
333*0Sstevel@tonic-gate 					(void) fclose(file);
334*0Sstevel@tonic-gate 					return;
335*0Sstevel@tonic-gate 				}
336*0Sstevel@tonic-gate 				ap->th_opcode = htons((ushort_t)ACK);
337*0Sstevel@tonic-gate 				ap->th_block = htons(0);
338*0Sstevel@tonic-gate 				size = 4;
339*0Sstevel@tonic-gate 				goto send_oack_ack;
340*0Sstevel@tonic-gate 			}
341*0Sstevel@tonic-gate 			if (dp->th_opcode == DATA) {
342*0Sstevel@tonic-gate 				int j;
343*0Sstevel@tonic-gate 
344*0Sstevel@tonic-gate 				dp->th_block = ntohs(dp->th_block);
345*0Sstevel@tonic-gate 				if (dp->th_block == block) {
346*0Sstevel@tonic-gate 					break;	/* have next packet */
347*0Sstevel@tonic-gate 				}
348*0Sstevel@tonic-gate 				/*
349*0Sstevel@tonic-gate 				 * On an error, try to synchronize
350*0Sstevel@tonic-gate 				 * both sides.
351*0Sstevel@tonic-gate 				 */
352*0Sstevel@tonic-gate 				j = synchnet(f);
353*0Sstevel@tonic-gate 				if (j < 0) {
354*0Sstevel@tonic-gate 					perror("tftp: recvfrom");
355*0Sstevel@tonic-gate 					goto abort;
356*0Sstevel@tonic-gate 				}
357*0Sstevel@tonic-gate 				if ((j > 0) && trace) {
358*0Sstevel@tonic-gate 					(void) printf("discarded %d packets\n",
359*0Sstevel@tonic-gate 					    j);
360*0Sstevel@tonic-gate 				}
361*0Sstevel@tonic-gate 				if (dp->th_block == (block-1)) {
362*0Sstevel@tonic-gate 					goto send_ack;  /* resend ack */
363*0Sstevel@tonic-gate 				}
364*0Sstevel@tonic-gate 			}
365*0Sstevel@tonic-gate 		}
366*0Sstevel@tonic-gate 		cancel_alarm();
367*0Sstevel@tonic-gate 		size = writeit(file, &dp, n - 4, convert);
368*0Sstevel@tonic-gate 		if (size < 0) {
369*0Sstevel@tonic-gate 			nak(errno + 100);
370*0Sstevel@tonic-gate 			goto abort;
371*0Sstevel@tonic-gate 		}
372*0Sstevel@tonic-gate 		amount += size;
373*0Sstevel@tonic-gate 	} while (size == blocksize);
374*0Sstevel@tonic-gate 
375*0Sstevel@tonic-gate 	cancel_alarm();
376*0Sstevel@tonic-gate 	if (write_behind(file, convert) < 0) {	/* flush last buffer */
377*0Sstevel@tonic-gate 		nak(errno + 100);
378*0Sstevel@tonic-gate 		goto abort;
379*0Sstevel@tonic-gate 	}
380*0Sstevel@tonic-gate 	n = fclose(file);
381*0Sstevel@tonic-gate 	file = NULL;
382*0Sstevel@tonic-gate 	if (n == EOF) {
383*0Sstevel@tonic-gate 		nak(errno + 100);
384*0Sstevel@tonic-gate 		goto abort;
385*0Sstevel@tonic-gate 	}
386*0Sstevel@tonic-gate 
387*0Sstevel@tonic-gate 	/* ok to ack, since user has seen err msg */
388*0Sstevel@tonic-gate 	ap->th_opcode = htons((ushort_t)ACK);
389*0Sstevel@tonic-gate 	ap->th_block = htons((ushort_t)block);
390*0Sstevel@tonic-gate 	if (trace)
391*0Sstevel@tonic-gate 		tpacket("sent", ap, 4);
392*0Sstevel@tonic-gate 	if (sendto(f, ackbuf.tb_data, 4, 0,
393*0Sstevel@tonic-gate 	    (struct sockaddr *)&sin6, sizeof (sin6)) != 4)
394*0Sstevel@tonic-gate 		perror("tftp: sendto");
395*0Sstevel@tonic-gate 
396*0Sstevel@tonic-gate abort:
397*0Sstevel@tonic-gate 	cancel_alarm();
398*0Sstevel@tonic-gate 	if (file != NULL)
399*0Sstevel@tonic-gate 		(void) fclose(file);
400*0Sstevel@tonic-gate 	stopclock();
401*0Sstevel@tonic-gate 	if (amount > 0)
402*0Sstevel@tonic-gate 		printstats("Received", amount);
403*0Sstevel@tonic-gate }
404*0Sstevel@tonic-gate 
405*0Sstevel@tonic-gate static int
406*0Sstevel@tonic-gate makerequest(int request, char *name, struct tftphdr *tp, char *mode)
407*0Sstevel@tonic-gate {
408*0Sstevel@tonic-gate 	char *cp, *cpend;
409*0Sstevel@tonic-gate 	int len;
410*0Sstevel@tonic-gate 
411*0Sstevel@tonic-gate 	tp->th_opcode = htons((ushort_t)request);
412*0Sstevel@tonic-gate 	cp = (char *)&tp->th_stuff;
413*0Sstevel@tonic-gate 
414*0Sstevel@tonic-gate 	/* Maximum size of a request packet is 512 bytes (RFC 2347) */
415*0Sstevel@tonic-gate 	cpend = (char *)tp + SEGSIZE;
416*0Sstevel@tonic-gate 
417*0Sstevel@tonic-gate 	len = strlcpy(cp, name, cpend - cp) + 1;
418*0Sstevel@tonic-gate 	cp += len;
419*0Sstevel@tonic-gate 	if (cp > cpend)
420*0Sstevel@tonic-gate 		return (-1);
421*0Sstevel@tonic-gate 
422*0Sstevel@tonic-gate 	len = strlcpy(cp, mode, cpend - cp) + 1;
423*0Sstevel@tonic-gate 	cp += len;
424*0Sstevel@tonic-gate 	if (cp > cpend)
425*0Sstevel@tonic-gate 		return (-1);
426*0Sstevel@tonic-gate 
427*0Sstevel@tonic-gate 	len = add_options(cp, cpend);
428*0Sstevel@tonic-gate 	if (len == -1)
429*0Sstevel@tonic-gate 		return (-1);
430*0Sstevel@tonic-gate 	cp += len;
431*0Sstevel@tonic-gate 
432*0Sstevel@tonic-gate 	return (cp - (char *)tp);
433*0Sstevel@tonic-gate }
434*0Sstevel@tonic-gate 
435*0Sstevel@tonic-gate /*
436*0Sstevel@tonic-gate  * Return the blksize option value string to include in the request packet.
437*0Sstevel@tonic-gate  */
438*0Sstevel@tonic-gate static char *
439*0Sstevel@tonic-gate blksize_str(void)
440*0Sstevel@tonic-gate {
441*0Sstevel@tonic-gate 	blocksize = SEGSIZE;
442*0Sstevel@tonic-gate 	if (blksize == 0)
443*0Sstevel@tonic-gate 		return (NULL);
444*0Sstevel@tonic-gate 
445*0Sstevel@tonic-gate 	(void) snprintf(optbuf, sizeof (optbuf), "%d", blksize);
446*0Sstevel@tonic-gate 	return (optbuf);
447*0Sstevel@tonic-gate }
448*0Sstevel@tonic-gate 
449*0Sstevel@tonic-gate /*
450*0Sstevel@tonic-gate  * Return the timeout option value string to include in the request packet.
451*0Sstevel@tonic-gate  */
452*0Sstevel@tonic-gate static char *
453*0Sstevel@tonic-gate timeout_str(void)
454*0Sstevel@tonic-gate {
455*0Sstevel@tonic-gate 	if (srexmtval == 0)
456*0Sstevel@tonic-gate 		return (NULL);
457*0Sstevel@tonic-gate 
458*0Sstevel@tonic-gate 	(void) snprintf(optbuf, sizeof (optbuf), "%d", srexmtval);
459*0Sstevel@tonic-gate 	return (optbuf);
460*0Sstevel@tonic-gate }
461*0Sstevel@tonic-gate 
462*0Sstevel@tonic-gate /*
463*0Sstevel@tonic-gate  * Return the tsize option value string to include in the request packet.
464*0Sstevel@tonic-gate  */
465*0Sstevel@tonic-gate static char *
466*0Sstevel@tonic-gate tsize_str(void)
467*0Sstevel@tonic-gate {
468*0Sstevel@tonic-gate 	if (tsize_set == B_FALSE)
469*0Sstevel@tonic-gate 		return (NULL);
470*0Sstevel@tonic-gate 
471*0Sstevel@tonic-gate 	(void) snprintf(optbuf, sizeof (optbuf), OFF_T_FMT, tsize);
472*0Sstevel@tonic-gate 	return (optbuf);
473*0Sstevel@tonic-gate }
474*0Sstevel@tonic-gate 
475*0Sstevel@tonic-gate /*
476*0Sstevel@tonic-gate  * Validate and action the blksize option value string from the OACK packet.
477*0Sstevel@tonic-gate  * Returns -1 on success or an error code on failure.
478*0Sstevel@tonic-gate  */
479*0Sstevel@tonic-gate static int
480*0Sstevel@tonic-gate blksize_handler(char *optstr)
481*0Sstevel@tonic-gate {
482*0Sstevel@tonic-gate 	char *endp;
483*0Sstevel@tonic-gate 	int value;
484*0Sstevel@tonic-gate 
485*0Sstevel@tonic-gate 	/* Make sure the option was requested */
486*0Sstevel@tonic-gate 	if (blksize == 0)
487*0Sstevel@tonic-gate 		return (EOPTNEG);
488*0Sstevel@tonic-gate 	errno = 0;
489*0Sstevel@tonic-gate 	value = (int)strtol(optstr, &endp, 10);
490*0Sstevel@tonic-gate 	if (errno != 0 || value < MIN_BLKSIZE || value > blksize ||
491*0Sstevel@tonic-gate 	    *endp != '\0')
492*0Sstevel@tonic-gate 		return (EOPTNEG);
493*0Sstevel@tonic-gate 	blocksize = value;
494*0Sstevel@tonic-gate 	return (-1);
495*0Sstevel@tonic-gate }
496*0Sstevel@tonic-gate 
497*0Sstevel@tonic-gate /*
498*0Sstevel@tonic-gate  * Validate and action the timeout option value string from the OACK packet.
499*0Sstevel@tonic-gate  * Returns -1 on success or an error code on failure.
500*0Sstevel@tonic-gate  */
501*0Sstevel@tonic-gate static int
502*0Sstevel@tonic-gate timeout_handler(char *optstr)
503*0Sstevel@tonic-gate {
504*0Sstevel@tonic-gate 	char *endp;
505*0Sstevel@tonic-gate 	int value;
506*0Sstevel@tonic-gate 
507*0Sstevel@tonic-gate 	/* Make sure the option was requested */
508*0Sstevel@tonic-gate 	if (srexmtval == 0)
509*0Sstevel@tonic-gate 		return (EOPTNEG);
510*0Sstevel@tonic-gate 	errno = 0;
511*0Sstevel@tonic-gate 	value = (int)strtol(optstr, &endp, 10);
512*0Sstevel@tonic-gate 	if (errno != 0 || value != srexmtval || *endp != '\0')
513*0Sstevel@tonic-gate 		return (EOPTNEG);
514*0Sstevel@tonic-gate 	/*
515*0Sstevel@tonic-gate 	 * Nothing to set, client and server retransmission intervals are
516*0Sstevel@tonic-gate 	 * set separately in the client.
517*0Sstevel@tonic-gate 	 */
518*0Sstevel@tonic-gate 	return (-1);
519*0Sstevel@tonic-gate }
520*0Sstevel@tonic-gate 
521*0Sstevel@tonic-gate /*
522*0Sstevel@tonic-gate  * Validate and action the tsize option value string from the OACK packet.
523*0Sstevel@tonic-gate  * Returns -1 on success or an error code on failure.
524*0Sstevel@tonic-gate  */
525*0Sstevel@tonic-gate static int
526*0Sstevel@tonic-gate tsize_handler(char *optstr)
527*0Sstevel@tonic-gate {
528*0Sstevel@tonic-gate 	char *endp;
529*0Sstevel@tonic-gate 	longlong_t value;
530*0Sstevel@tonic-gate 
531*0Sstevel@tonic-gate 	/* Make sure the option was requested */
532*0Sstevel@tonic-gate 	if (tsize_set == B_FALSE)
533*0Sstevel@tonic-gate 		return (EOPTNEG);
534*0Sstevel@tonic-gate 	errno = 0;
535*0Sstevel@tonic-gate 	value = strtoll(optstr, &endp, 10);
536*0Sstevel@tonic-gate 	if (errno != 0 || value < 0 || *endp != '\0')
537*0Sstevel@tonic-gate 		return (EOPTNEG);
538*0Sstevel@tonic-gate #if _FILE_OFFSET_BITS == 32
539*0Sstevel@tonic-gate 	if (value > MAXOFF_T)
540*0Sstevel@tonic-gate 		return (ENOSPACE);
541*0Sstevel@tonic-gate #endif
542*0Sstevel@tonic-gate 	/*
543*0Sstevel@tonic-gate 	 * Don't bother checking the tsize value we specified in a write
544*0Sstevel@tonic-gate 	 * request is echoed back in the OACK.
545*0Sstevel@tonic-gate 	 */
546*0Sstevel@tonic-gate 	if (tsize == 0)
547*0Sstevel@tonic-gate 		tsize = value;
548*0Sstevel@tonic-gate 	return (-1);
549*0Sstevel@tonic-gate }
550*0Sstevel@tonic-gate 
551*0Sstevel@tonic-gate /*
552*0Sstevel@tonic-gate  * Add TFTP options to a request packet.
553*0Sstevel@tonic-gate  */
554*0Sstevel@tonic-gate static int
555*0Sstevel@tonic-gate add_options(char *obuf, char *obufend)
556*0Sstevel@tonic-gate {
557*0Sstevel@tonic-gate 	int i;
558*0Sstevel@tonic-gate 	char *cp, *ostr;
559*0Sstevel@tonic-gate 
560*0Sstevel@tonic-gate 	cp = obuf;
561*0Sstevel@tonic-gate 	for (i = 0; options[i].opt_name != NULL; i++) {
562*0Sstevel@tonic-gate 		ostr = options[i].opt_str();
563*0Sstevel@tonic-gate 		if (ostr != NULL) {
564*0Sstevel@tonic-gate 			cp += strlcpy(cp, options[i].opt_name, obufend - cp)
565*0Sstevel@tonic-gate 			    + 1;
566*0Sstevel@tonic-gate 			if (cp > obufend)
567*0Sstevel@tonic-gate 				return (-1);
568*0Sstevel@tonic-gate 
569*0Sstevel@tonic-gate 			cp += strlcpy(cp, ostr, obufend - cp) + 1;
570*0Sstevel@tonic-gate 			if (cp > obufend)
571*0Sstevel@tonic-gate 				return (-1);
572*0Sstevel@tonic-gate 		}
573*0Sstevel@tonic-gate 	}
574*0Sstevel@tonic-gate 	return (cp - obuf);
575*0Sstevel@tonic-gate }
576*0Sstevel@tonic-gate 
577*0Sstevel@tonic-gate /*
578*0Sstevel@tonic-gate  * Process OACK packet sent by server in response to options in the request
579*0Sstevel@tonic-gate  * packet. Returns -1 on success or an error code on failure.
580*0Sstevel@tonic-gate  */
581*0Sstevel@tonic-gate static int
582*0Sstevel@tonic-gate process_oack(tftpbuf *oackbuf, int n)
583*0Sstevel@tonic-gate {
584*0Sstevel@tonic-gate 	char *cp, *oackend, *optname, *optval;
585*0Sstevel@tonic-gate 	struct tftphdr *oackp;
586*0Sstevel@tonic-gate 	int i, errcode;
587*0Sstevel@tonic-gate 
588*0Sstevel@tonic-gate 	oackp = &oackbuf->tb_hdr;
589*0Sstevel@tonic-gate 	cp = (char *)&oackp->th_stuff;
590*0Sstevel@tonic-gate 	oackend = (char *)oackbuf + n;
591*0Sstevel@tonic-gate 
592*0Sstevel@tonic-gate 	while (cp < oackend) {
593*0Sstevel@tonic-gate 		optname = cp;
594*0Sstevel@tonic-gate 		if ((optval = next_field(optname, oackend)) == NULL)
595*0Sstevel@tonic-gate 			return (EOPTNEG);
596*0Sstevel@tonic-gate 		if ((cp = next_field(optval, oackend)) == NULL)
597*0Sstevel@tonic-gate 			return (EOPTNEG);
598*0Sstevel@tonic-gate 		for (i = 0; options[i].opt_name != NULL; i++) {
599*0Sstevel@tonic-gate 			if (strcasecmp(optname, options[i].opt_name) == 0)
600*0Sstevel@tonic-gate 				break;
601*0Sstevel@tonic-gate 		}
602*0Sstevel@tonic-gate 		if (options[i].opt_name == NULL)
603*0Sstevel@tonic-gate 			return (EOPTNEG);
604*0Sstevel@tonic-gate 		errcode = options[i].opt_handler(optval);
605*0Sstevel@tonic-gate 		if (errcode >= 0)
606*0Sstevel@tonic-gate 			return (errcode);
607*0Sstevel@tonic-gate 	}
608*0Sstevel@tonic-gate 	return (-1);
609*0Sstevel@tonic-gate }
610*0Sstevel@tonic-gate 
611*0Sstevel@tonic-gate /*
612*0Sstevel@tonic-gate  * Send a nak packet (error message).
613*0Sstevel@tonic-gate  * Error code passed in is one of the
614*0Sstevel@tonic-gate  * standard TFTP codes, or a UNIX errno
615*0Sstevel@tonic-gate  * offset by 100.
616*0Sstevel@tonic-gate  */
617*0Sstevel@tonic-gate static void
618*0Sstevel@tonic-gate nak(int error)
619*0Sstevel@tonic-gate {
620*0Sstevel@tonic-gate 	struct tftphdr *tp;
621*0Sstevel@tonic-gate 	int length;
622*0Sstevel@tonic-gate 	struct errmsg *pe;
623*0Sstevel@tonic-gate 
624*0Sstevel@tonic-gate 	tp = &ackbuf.tb_hdr;
625*0Sstevel@tonic-gate 	tp->th_opcode = htons((ushort_t)ERROR);
626*0Sstevel@tonic-gate 	tp->th_code = htons((ushort_t)error);
627*0Sstevel@tonic-gate 	for (pe = errmsgs; pe->e_code >= 0; pe++)
628*0Sstevel@tonic-gate 		if (pe->e_code == error)
629*0Sstevel@tonic-gate 			break;
630*0Sstevel@tonic-gate 	if (pe->e_code < 0) {
631*0Sstevel@tonic-gate 		pe->e_msg = strerror(error - 100);
632*0Sstevel@tonic-gate 		tp->th_code = EUNDEF;
633*0Sstevel@tonic-gate 	}
634*0Sstevel@tonic-gate 	(void) strlcpy(tp->th_msg, pe->e_msg,
635*0Sstevel@tonic-gate 	    sizeof (ackbuf) - sizeof (struct tftphdr));
636*0Sstevel@tonic-gate 	length = strlen(pe->e_msg) + 4;
637*0Sstevel@tonic-gate 	if (trace)
638*0Sstevel@tonic-gate 		tpacket("sent", tp, length);
639*0Sstevel@tonic-gate 	if (sendto(f, ackbuf.tb_data, length, 0,
640*0Sstevel@tonic-gate 	    (struct sockaddr *)&sin6, sizeof (sin6)) != length)
641*0Sstevel@tonic-gate 		perror("nak");
642*0Sstevel@tonic-gate }
643*0Sstevel@tonic-gate 
644*0Sstevel@tonic-gate static void
645*0Sstevel@tonic-gate tpacket(char *s, struct tftphdr *tp, int n)
646*0Sstevel@tonic-gate {
647*0Sstevel@tonic-gate 	static char *opcodes[] = \
648*0Sstevel@tonic-gate 		{ "#0", "RRQ", "WRQ", "DATA", "ACK", "ERROR", "OACK" };
649*0Sstevel@tonic-gate 	char *cp, *file, *mode;
650*0Sstevel@tonic-gate 	ushort_t op = ntohs(tp->th_opcode);
651*0Sstevel@tonic-gate 	char *tpend;
652*0Sstevel@tonic-gate 
653*0Sstevel@tonic-gate 	if (op < RRQ || op > OACK)
654*0Sstevel@tonic-gate 		(void) printf("%s opcode=%x ", s, op);
655*0Sstevel@tonic-gate 	else
656*0Sstevel@tonic-gate 		(void) printf("%s %s ", s, opcodes[op]);
657*0Sstevel@tonic-gate 
658*0Sstevel@tonic-gate 	switch (op) {
659*0Sstevel@tonic-gate 	case RRQ:
660*0Sstevel@tonic-gate 	case WRQ:
661*0Sstevel@tonic-gate 		tpend = (char *)tp + n;
662*0Sstevel@tonic-gate 		n -= sizeof (tp->th_opcode);
663*0Sstevel@tonic-gate 		file = (char *)&tp->th_stuff;
664*0Sstevel@tonic-gate 		if ((mode = next_field(file, tpend)) == NULL) {
665*0Sstevel@tonic-gate 			(void) printf("<file=%.*s>\n", n, file);
666*0Sstevel@tonic-gate 			break;
667*0Sstevel@tonic-gate 		}
668*0Sstevel@tonic-gate 		n -= mode - file;
669*0Sstevel@tonic-gate 		if ((cp = next_field(mode, tpend)) == NULL) {
670*0Sstevel@tonic-gate 			(void) printf("<file=%s, mode=%.*s>\n", file, n, mode);
671*0Sstevel@tonic-gate 			break;
672*0Sstevel@tonic-gate 		}
673*0Sstevel@tonic-gate 		(void) printf("<file=%s, mode=%s", file, mode);
674*0Sstevel@tonic-gate 		n -= cp - mode;
675*0Sstevel@tonic-gate 		if (n > 0) {
676*0Sstevel@tonic-gate 			(void) printf(", options: ");
677*0Sstevel@tonic-gate 			print_options(stdout, cp, n);
678*0Sstevel@tonic-gate 		}
679*0Sstevel@tonic-gate 		(void) puts(">");
680*0Sstevel@tonic-gate 		break;
681*0Sstevel@tonic-gate 
682*0Sstevel@tonic-gate 	case DATA:
683*0Sstevel@tonic-gate 		(void) printf("<block=%d, %d bytes>\n", ntohs(tp->th_block),
684*0Sstevel@tonic-gate 		    n - sizeof (tp->th_opcode) - sizeof (tp->th_block));
685*0Sstevel@tonic-gate 		break;
686*0Sstevel@tonic-gate 
687*0Sstevel@tonic-gate 	case ACK:
688*0Sstevel@tonic-gate 		(void) printf("<block=%d>\n", ntohs(tp->th_block));
689*0Sstevel@tonic-gate 		break;
690*0Sstevel@tonic-gate 
691*0Sstevel@tonic-gate 	case OACK:
692*0Sstevel@tonic-gate 		(void) printf("<options: ");
693*0Sstevel@tonic-gate 		print_options(stdout, (char *)&tp->th_stuff,
694*0Sstevel@tonic-gate 		    n - sizeof (tp->th_opcode));
695*0Sstevel@tonic-gate 		(void) puts(">");
696*0Sstevel@tonic-gate 		break;
697*0Sstevel@tonic-gate 
698*0Sstevel@tonic-gate 	case ERROR:
699*0Sstevel@tonic-gate 		(void) printf("<code=%d", ntohs(tp->th_code));
700*0Sstevel@tonic-gate 		n = n - sizeof (tp->th_opcode) - sizeof (tp->th_code);
701*0Sstevel@tonic-gate 		if (n > 0)
702*0Sstevel@tonic-gate 			(void) printf(", msg=%.*s", n, tp->th_msg);
703*0Sstevel@tonic-gate 		(void) puts(">");
704*0Sstevel@tonic-gate 		break;
705*0Sstevel@tonic-gate 	}
706*0Sstevel@tonic-gate }
707*0Sstevel@tonic-gate 
708*0Sstevel@tonic-gate static hrtime_t	tstart, tstop;
709*0Sstevel@tonic-gate 
710*0Sstevel@tonic-gate static void
711*0Sstevel@tonic-gate startclock(void)
712*0Sstevel@tonic-gate {
713*0Sstevel@tonic-gate 	tstart = gethrtime();
714*0Sstevel@tonic-gate }
715*0Sstevel@tonic-gate 
716*0Sstevel@tonic-gate static void
717*0Sstevel@tonic-gate stopclock(void)
718*0Sstevel@tonic-gate {
719*0Sstevel@tonic-gate 	tstop = gethrtime();
720*0Sstevel@tonic-gate }
721*0Sstevel@tonic-gate 
722*0Sstevel@tonic-gate static void
723*0Sstevel@tonic-gate printstats(char *direction, off_t amount)
724*0Sstevel@tonic-gate {
725*0Sstevel@tonic-gate 	hrtime_t	delta, tenths;
726*0Sstevel@tonic-gate 
727*0Sstevel@tonic-gate 	delta = tstop - tstart;
728*0Sstevel@tonic-gate 	tenths = delta / (NANOSEC / 10);
729*0Sstevel@tonic-gate 	(void) printf("%s " OFF_T_FMT " bytes in %" PRId64 ".%" PRId64
730*0Sstevel@tonic-gate 	    " seconds", direction, amount, tenths / 10, tenths % 10);
731*0Sstevel@tonic-gate 	if (verbose)
732*0Sstevel@tonic-gate 		(void) printf(" [%" PRId64 " bits/sec]\n",
733*0Sstevel@tonic-gate 		    ((hrtime_t)amount * 8 * NANOSEC) / delta);
734*0Sstevel@tonic-gate 	else
735*0Sstevel@tonic-gate 		(void) putchar('\n');
736*0Sstevel@tonic-gate }
737