1114472Sru/*	$NetBSD: tftp.c,v 1.37 2023/01/06 17:18:56 christos Exp $	*/
2146515Sru
356160Sru/*
4146515Sru * Copyright (c) 1983, 1993
556160Sru *	The Regents of the University of California.  All rights reserved.
656160Sru *
756160Sru * Redistribution and use in source and binary forms, with or without
856160Sru * modification, are permitted provided that the following conditions
956160Sru * are met:
1056160Sru * 1. Redistributions of source code must retain the above copyright
1156160Sru *    notice, this list of conditions and the following disclaimer.
1256160Sru * 2. Redistributions in binary form must reproduce the above copyright
1356160Sru *    notice, this list of conditions and the following disclaimer in the
1456160Sru *    documentation and/or other materials provided with the distribution.
1556160Sru * 3. Neither the name of the University nor the names of its contributors
1656160Sru *    may be used to endorse or promote products derived from this software
1756160Sru *    without specific prior written permission.
1856160Sru *
1956160Sru * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20114472Sru * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2156160Sru * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2256160Sru * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
2356160Sru * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2456160Sru * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2556160Sru * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2656160Sru * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2756160Sru * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2856160Sru * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2993139Sru * SUCH DAMAGE.
3056160Sru */
3156160Sru
3256160Sru#include <sys/cdefs.h>
3356160Sru#ifndef lint
3456160Sru#if 0
3556160Srustatic char sccsid[] = "@(#)tftp.c	8.1 (Berkeley) 6/6/93";
3656160Sru#else
37146515Sru__RCSID("$NetBSD: tftp.c,v 1.37 2023/01/06 17:18:56 christos Exp $");
3856160Sru#endif
3956160Sru#endif /* not lint */
4056160Sru
4156160Sru/* Many bug fixes are from Jim Guyton <guyton@rand-unix> */
4256160Sru
4356160Sru/*
4456160Sru * TFTP User Program -- Protocol Machines
4556160Sru */
4656160Sru#include <sys/types.h>
4756160Sru#include <sys/param.h>
4856160Sru#include <sys/socket.h>
4956160Sru#include <sys/stat.h>
5056160Sru#include <sys/time.h>
5156160Sru
5256160Sru#include <netinet/in.h>
5356160Sru
5456160Sru#include <arpa/tftp.h>
5556160Sru#include <arpa/inet.h>
56116525Sru
5756160Sru#include <err.h>
5856160Sru#include <errno.h>
5956160Sru#include <setjmp.h>
6056160Sru#include <signal.h>
6156160Sru#include <stdio.h>
6256160Sru#include <stdlib.h>
6356160Sru#include <string.h>
6456160Sru#include <unistd.h>
6556160Sru#include <netdb.h>
6656160Sru
6756160Sru#include "extern.h"
6856160Sru#include "tftpsubs.h"
6956160Sru
7056160Sruextern jmp_buf	toplevel;
7156160Sru
7256160Sruchar    ackbuf[PKTSIZE];
7356160Sruint	timeout;
7456160Srujmp_buf	timeoutbuf;
7556160Sru
7656160Srustatic void nak(int, struct sockaddr *);
7756160Srustatic int makerequest(int, const char *, struct tftphdr *, const char *, off_t);
7856160Srustatic void printstats(const char *, unsigned long);
7956160Srustatic void startclock(void);
8056160Srustatic void stopclock(void);
8156160Srustatic __dead void timer(int);
8256160Srustatic void tpacket(const char *, struct tftphdr *, int);
8356160Srustatic int cmpport(struct sockaddr *, struct sockaddr *);
8456160Sru
85146515Srustatic void get_options(struct tftphdr *, int);
86146515Srustatic int tftp_igmp_join(void);
87146515Srustatic void tftp_igmp_leave(int);
88146515Sru
89146515Srustatic void
90146515Sruget_options(struct tftphdr *ap, int size)
9156160Sru{
9256160Sru	unsigned long val;
9356160Sru	char *opt, *endp, *nextopt, *valp;
9456160Sru	int l;
95146515Sru
96146515Sru	size -= 2;	/* skip over opcode */
9756160Sru	opt = ap->th_stuff;
9856160Sru	endp = opt + size - 1;
9956160Sru	*endp = '\0';
10056160Sru
10156160Sru	while (opt < endp) {
10256160Sru		int ismulticast;
10356160Sru		l = strlen(opt) + 1;
10456160Sru		valp = opt + l;
10556160Sru		ismulticast = !strcasecmp(opt, "multicast");
10656160Sru		if (valp < endp) {
10756160Sru			val = strtoul(valp, NULL, 10);
10856160Sru			l = strlen(valp) + 1;
10956160Sru			nextopt = valp + l;
11056160Sru			if (!ismulticast) {
111114472Sru				if (val == ULONG_MAX && errno == ERANGE) {
11256160Sru					/* Report illegal value */
11356160Sru					opt = nextopt;
114114472Sru					continue;
11556160Sru				}
11656160Sru			}
11756160Sru		} else {
11856160Sru			/* Badly formed OACK */
11956160Sru			break;
12056160Sru		}
12156160Sru		if (strcmp(opt, "tsize") == 0) {
122146515Sru			/* cool, but we'll ignore it */
12356160Sru		} else if (strcmp(opt, "timeout") == 0) {
124146515Sru			if (val >= 1 && val <= 255) {
12556160Sru				rexmtval = val;
126146515Sru			} else {
127146515Sru				/* Report error? */
128146515Sru			}
129146515Sru		} else if (strcmp(opt, "blksize") == 0) {
130146515Sru			if (val >= 8 && val <= MAXSEGSIZE) {
13156160Sru				blksize = val;
13256160Sru			} else {
13356160Sru				/* Report error? */
13456160Sru			}
13556160Sru		} else if (ismulticast) {
13656160Sru			char multicast[24];
137146515Sru			char *pmulticast;
13856160Sru			char *addr;
13956160Sru
14056160Sru			strlcpy(multicast, valp, sizeof(multicast));
14156160Sru			pmulticast = multicast;
14256160Sru			addr = strsep(&pmulticast, ",");
14356160Sru			if (pmulticast == NULL)
14456160Sru				continue; /* Report error? */
14556160Sru			mcport = atoi(strsep(&pmulticast, ","));
14656160Sru			if (pmulticast == NULL)
14756160Sru				continue; /* Report error? */
14856160Sru			mcmasterslave = atoi(pmulticast);
14956160Sru			mcaddr = inet_addr(addr);
15056160Sru			if (mcaddr == INADDR_NONE)
15156160Sru				continue; /* Report error? */
15256160Sru		} else {
153146515Sru			/* unknown option */
154146515Sru		}
15556160Sru		opt = nextopt;
15656160Sru	}
15756160Sru}
15856160Sru
15956160Srustatic int
16056160Srutftp_igmp_join(void)
16156160Sru{
162116525Sru	struct ip_mreq req;
16356160Sru	struct sockaddr_in s;
16456160Sru	int fd, rv;
16556160Sru
16656160Sru	memset(&req, 0, sizeof(struct ip_mreq));
16756160Sru	req.imr_multiaddr.s_addr = mcaddr;
16856160Sru	req.imr_interface.s_addr = INADDR_ANY;
16956160Sru
17056160Sru	fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
17156160Sru	if (fd < 0) {
172116525Sru		perror("socket");
173146515Sru		return fd;
174146515Sru	}
175146515Sru
176146515Sru	memset(&s, 0, sizeof(struct sockaddr_in));
177146515Sru	s.sin_family = AF_INET;
17856160Sru	s.sin_port = htons(mcport);
179146515Sru	s.sin_len = sizeof(struct sockaddr_in);
18056160Sru	rv = bind(fd, (struct sockaddr *)&s, sizeof(struct sockaddr_in));
18156160Sru	if (rv < 0) {
18256160Sru		perror("bind");
18356160Sru		close(fd);
18456160Sru		return rv;
18556160Sru	}
18656160Sru
18756160Sru	rv = setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &req,
18856160Sru	    sizeof(struct ip_mreq));
18956160Sru	if (rv < 0) {
19056160Sru		perror("setsockopt");
19156160Sru		close(fd);
19256160Sru		return rv;
19356160Sru	}
19456160Sru
19556160Sru	return fd;
19656160Sru}
19756160Sru
19856160Srustatic void
19956160Srutftp_igmp_leave(int fd)
20056160Sru{
20156160Sru	struct ip_mreq req;
20256160Sru	int rv;
20356160Sru
20456160Sru	memset(&req, 0, sizeof(struct ip_mreq));
20556160Sru	req.imr_multiaddr.s_addr = mcaddr;
20656160Sru	req.imr_interface.s_addr = INADDR_ANY;
20756160Sru
20856160Sru	rv = setsockopt(fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &req,
20956160Sru	    sizeof(struct ip_mreq));
21056160Sru	if (rv < 0)
21156160Sru		perror("setsockopt");
21256160Sru
21356160Sru	close(fd);
21456160Sru
21556160Sru	return;
21656160Sru}
217114472Sru
21856160Sru/*
219146515Sru * Send the requested file.
220146515Sru */
221146515Sruvoid
222146515Srusendfile(int fd, const char *name, const char *mode)
223146515Sru{
224146515Sru	struct tftphdr *ap;	   /* data and ack packets */
225146515Sru	struct tftphdr *dp;
226146515Sru	int j, n;
227146515Sru	volatile unsigned int block;
228146515Sru	volatile int size, convert;
229146515Sru	volatile unsigned long amount;
230146515Sru	struct sockaddr_storage from;
231146515Sru	struct stat sbuf;
232146515Sru	volatile off_t filesize = 0;
233146515Sru	socklen_t fromlen;
234146515Sru	FILE *file;
235146515Sru	struct sockaddr_storage peer;
236146515Sru	struct sockaddr_storage serv;	/* valid server port number */
237146515Sru
238146515Sru	startclock();		/* start stat's clock */
239146515Sru	dp = r_init();		/* reset fillbuf/read-ahead code */
240146515Sru	ap = (struct tftphdr *)(void *)ackbuf;
241146515Sru	if (tsize) {
242146515Sru		if (fstat(fd, &sbuf) == 0) {
24356160Sru			filesize = sbuf.st_size;
24456160Sru		} else {
24556160Sru			filesize = -1ULL;
24656160Sru		}
24756160Sru	}
248146515Sru	file = fdopen(fd, "r");
249146515Sru	convert = !strcmp(mode, "netascii");
250146515Sru	block = 0;
251146515Sru	amount = 0;
252146515Sru	(void)memcpy(&peer, &peeraddr, (size_t)peeraddr.ss_len);
253146515Sru	(void)memset(&serv, 0, sizeof(serv));
254146515Sru
255146515Sru	(void)signal(SIGALRM, timer);
256146515Sru	do {
257146515Sru		if (block == 0)
258146515Sru			size = makerequest(WRQ, name, dp, mode, filesize) - 4;
259221386Sdim		else {
260146515Sru		/*	size = read(fd, dp->th_data, SEGSIZE);	 */
261146515Sru			size = readit(file, &dp, blksize, convert);
262146515Sru			if (size < 0) {
263146515Sru				nak(errno + 100,
264146515Sru				    (struct sockaddr *)(void *)&peer);
265221386Sdim				break;
266146515Sru			}
267146515Sru			dp->th_opcode = htons((u_short)DATA);
268146515Sru			dp->th_block = htons((u_short)block);
269146515Sru		}
270146515Sru		timeout = 0;
271146515Sru		(void) setjmp(timeoutbuf);
272146515Srusend_data:
273146515Sru		if (trace)
274146515Sru			tpacket("sent", dp, size + 4);
275146515Sru		n = sendto(f, dp, (socklen_t)(size + 4), 0,
276146515Sru		    (struct sockaddr *)(void *)&peer, (socklen_t)peer.ss_len);
277146515Sru		if (n != size + 4) {
278146515Sru			warn("sendto");
279146515Sru			goto abort;
280146515Sru		}
281146515Sru		if (block)
282146515Sru			read_ahead(file, blksize, convert);
283146515Sru		for ( ; ; ) {
284146515Sru			(void)alarm(rexmtval);
285146515Sru			do {
286146515Sru				int curf;
287146515Sru				fromlen = sizeof(from);
288146515Sru				if (mcaddr != INADDR_NONE)
289146515Sru					curf = mf;
290146515Sru				else
291146515Sru					curf = f;
292146515Sru				n = recvfrom(curf, ackbuf, sizeof(ackbuf), 0,
293146515Sru				    (struct sockaddr *)(void *)&from, &fromlen);
294146515Sru			} while (n <= 0);
295146515Sru			(void)alarm(0);
296146515Sru			if (n < 0) {
297146515Sru				warn("recvfrom");
298146515Sru				goto abort;
299146515Sru			}
300146515Sru			if (!serv.ss_family)
301146515Sru				serv = from;
302146515Sru			else if (!cmpport((struct sockaddr *)(void *)&serv,
303146515Sru			    (struct sockaddr *)(void *)&from)) {
304146515Sru				warn("server port mismatch");
305146515Sru				goto abort;
306146515Sru			}
307146515Sru			peer = from;
308146515Sru			if (trace)
309146515Sru				tpacket("received", ap, n);
310146515Sru			/* should verify packet came from server */
311146515Sru			ap->th_opcode = ntohs(ap->th_opcode);
312146515Sru			if (ap->th_opcode == ERROR) {
313146515Sru				(void)printf("Error code %d: %s\n",
314146515Sru				    ntohs(ap->th_code), ap->th_msg);
315146515Sru				goto abort;
316146515Sru			}
317146515Sru			if (ap->th_opcode == ACK) {
318146515Sru				ap->th_block = ntohs(ap->th_block);
31956160Sru
320146515Sru				if (ap->th_block == 0) {
32156160Sru					/*
322146515Sru					 * If the extended options are enabled,
323146515Sru					 * the server just refused 'em all.
324146515Sru					 * The only one that _really_
325116525Sru					 * matters is blksize, but we'll
326116525Sru					 * clear timeout and mcaddr, too.
327116525Sru					 */
328116525Sru					blksize = def_blksize;
329116525Sru					rexmtval = def_rexmtval;
33056160Sru					mcaddr = INADDR_NONE;
33156160Sru				}
33256160Sru				if (ap->th_block == block) {
333146515Sru					break;
33456160Sru				}
33556160Sru				/* On an error, try to synchronize
33656160Sru				 * both sides.
337146515Sru				 */
33856160Sru				j = synchnet(f, blksize+4);
339146515Sru				if (j && trace) {
340146515Sru					(void)printf("discarded %d packets\n",
341146515Sru							j);
34256160Sru				}
343146515Sru				if (ap->th_block == (block-1)) {
344146515Sru					goto send_data;
345146515Sru				}
346146515Sru			}
347146515Sru			if (ap->th_opcode == OACK) {
348146515Sru				if (block == 0) {
349146515Sru					blksize = def_blksize;
350146515Sru					rexmtval = def_rexmtval;
351146515Sru					mcaddr = INADDR_NONE;
352146515Sru					get_options(ap, n);
353146515Sru					break;
35456160Sru				}
35556160Sru			}
356146515Sru		}
35756160Sru		if (block > 0)
358146515Sru			amount += size;
359146515Sru		block++;
360146515Sru	} while ((size_t)size == blksize || block == 1);
361146515Sruabort:
362146515Sru	(void)fclose(file);
363146515Sru	stopclock();
364146515Sru	if (amount > 0)
365146515Sru		printstats("Sent", amount);
366146515Sru}
367146515Sru
368116525Sru/*
369146515Sru * Receive a file.
370146515Sru */
371146515Sruvoid
372146515Srurecvfile(int fd, const char *name, const char *mode)
373146515Sru{
374146515Sru	struct tftphdr *ap;
375146515Sru	struct tftphdr *dp;
376146515Sru	int j, n;
377146515Sru	volatile int oack = 0;
378146515Sru	volatile unsigned int block;
379146515Sru	volatile int size, firsttrip;
380146515Sru	volatile unsigned long amount;
381146515Sru	struct sockaddr_storage from;
382146515Sru	socklen_t fromlen;
383146515Sru	volatile size_t readlen;
384146515Sru	FILE *file;
385146515Sru	volatile int convert;		/* true if converting crlf -> lf */
386146515Sru	struct sockaddr_storage peer;
387146515Sru	struct sockaddr_storage serv;	/* valid server port number */
388146515Sru
389146515Sru	startclock();
390146515Sru	dp = w_init();
391146515Sru	ap = (struct tftphdr *)(void *)ackbuf;
392146515Sru	file = fdopen(fd, "w");
393146515Sru	convert = !strcmp(mode, "netascii");
394146515Sru	block = 1;
395146515Sru	firsttrip = 1;
396146515Sru	amount = 0;
397146515Sru	(void)memcpy(&peer, &peeraddr, (size_t)peeraddr.ss_len);
398146515Sru	(void)memset(&serv, 0, sizeof(serv));
399146515Sru
400146515Sru	(void)signal(SIGALRM, timer);
401146515Sru	do {
402114472Sru		if (firsttrip) {
403146515Sru			size = makerequest(RRQ, name, ap, mode, (off_t)0);
404146515Sru			readlen = PKTSIZE;
405146515Sru			firsttrip = 0;
406146515Sru		} else {
407146515Sru			ap->th_opcode = htons((u_short)ACK);
408146515Sru			ap->th_block = htons((u_short)(block));
409146515Sru			readlen = blksize+4;
410146515Sru			size = 4;
411146515Sru			block++;
412146515Sru		}
413146515Sru		timeout = 0;
41456160Sru		(void) setjmp(timeoutbuf);
415146515Srusend_ack:
416146515Sru		if (trace)
417146515Sru			tpacket("sent", ap, size);
418116525Sru		if (sendto(f, ackbuf, (socklen_t)size, 0,
419146515Sru		    (struct sockaddr *)(void *)&peer,
420114472Sru		    (socklen_t)peer.ss_len) != size) {
421146515Sru			(void)alarm(0);
422146515Sru			warn("sendto");
423146515Sru			goto abort;
424146515Sru		}
425146515Sruskip_ack:
42656160Sru		if (write_behind(file, convert) == -1)
42756160Sru			goto abort;
42856160Sru		for ( ; ; ) {
42956160Sru			(void)alarm(rexmtval);
43056160Sru			do  {
43156160Sru				int readfd;
43256160Sru				if (mf > 0)
433146515Sru					readfd = mf;
43456160Sru				else
43556160Sru					readfd = f;
43656160Sru				fromlen = sizeof(from);
43756160Sru				n = recvfrom(readfd, dp, readlen, 0,
43856160Sru				    (struct sockaddr *)(void *)&from, &fromlen);
43956160Sru			} while (n <= 0);
440146515Sru			(void)alarm(0);
44156160Sru			if (n < 0) {
44256160Sru				warn("recvfrom");
44356160Sru				goto abort;
44456160Sru			}
44556160Sru			if (!serv.ss_family)
44656160Sru				serv = from;
44756160Sru			else if (!cmpport((struct sockaddr *)(void *)&serv,
44856160Sru			    (struct sockaddr *)(void *)&from)) {
44956160Sru				warn("server port mismatch");
45056160Sru				goto abort;
451146515Sru			}
452146515Sru			peer = from;
453146515Sru			if (trace)
454146515Sru				tpacket("received", dp, n);
455146515Sru			/* should verify client address */
45656160Sru			dp->th_opcode = ntohs(dp->th_opcode);
45756160Sru			if (dp->th_opcode == ERROR) {
458146515Sru				(void)printf("Error code %d: %s\n",
459146515Sru				    ntohs(dp->th_code), dp->th_msg);
460146515Sru				goto abort;
461146515Sru			}
462146515Sru			if (dp->th_opcode == DATA) {
46356160Sru				dp->th_block = ntohs(dp->th_block);
46456160Sru
46556160Sru				if (dp->th_block == 1 && !oack) {
46656160Sru					/* no OACK, revert to defaults */
467146515Sru					blksize = def_blksize;
46856160Sru					rexmtval = def_rexmtval;
46956160Sru				}
47056160Sru				if (dp->th_block == block) {
47156160Sru					break;		/* have next packet */
47256160Sru				}
47356160Sru				/* On an error, try to synchronize
47456160Sru				 * both sides.
47556160Sru				 */
476146515Sru				j = synchnet(f, blksize);
47756160Sru				if (j && trace) {
47856160Sru					(void)printf("discarded %d packets\n",
47956160Sru					    j);
48056160Sru				}
48156160Sru				if (dp->th_block == (block-1)) {
48256160Sru					goto send_ack;	/* resend ack */
48356160Sru				}
48456160Sru			}
48556160Sru			if (dp->th_opcode == OACK) {
48656160Sru				if (block == 1) {
48756160Sru					oack = 1;
48856160Sru					blksize = def_blksize;
48956160Sru					rexmtval = def_rexmtval;
49056160Sru					get_options(dp, n);
49156160Sru					ap->th_opcode = htons(ACK);
49256160Sru					ap->th_block = 0;
49356160Sru					readlen = blksize+4;
49456160Sru					size = 4;
49556160Sru					if (mcaddr != INADDR_NONE) {
49656160Sru						mf = tftp_igmp_join();
49756160Sru						if (mf < 0)
49856160Sru							goto abort;
49956160Sru						if (mcmasterslave == 0)
50056160Sru							goto skip_ack;
50156160Sru					}
50256160Sru					goto send_ack;
50356160Sru				}
50456160Sru			}
50556160Sru		}
50656160Sru	/*	size = write(fd, dp->th_data, n - 4); */
50756160Sru		size = writeit(file, &dp, n - 4, convert);
50856160Sru		if (size < 0) {
50956160Sru			nak(errno + 100, (struct sockaddr *)(void *)&peer);
51056160Sru			break;
511116525Sru		}
51256160Sru		amount += size;
513146515Sru	} while ((size_t)size == blksize);
51456160Sruabort:						/* ok to ack, since user */
51556160Sru	ap->th_opcode = htons((u_short)ACK);	/* has seen err msg */
51656160Sru	ap->th_block = htons((u_short)block);
51756160Sru	if (mcaddr != INADDR_NONE && mf >= 0) {
51856160Sru		tftp_igmp_leave(mf);
51956160Sru		mf = -1;
52056160Sru	}
52156160Sru	(void) sendto(f, ackbuf, 4, 0, (struct sockaddr *)(void *)&peer,
52256160Sru	    (socklen_t)peer.ss_len);
52356160Sru	/*
52456160Sru	 * flush last buffer
52556160Sru	 * We do not check for failure because last buffer
526146515Sru	 * can be empty, thus returning an error.
527146515Sru	 * XXX maybe we should fix 'write_behind' instead.
52856160Sru	 */
52956160Sru	(void)write_behind(file, convert);
53056160Sru	(void)fclose(file);
53156160Sru	stopclock();
53256160Sru	if (amount > 0)
53393139Sru		printstats("Received", amount);
53493139Sru}
53556160Sru
53693139Srustatic int
53793139Srumakerequest(int request, const char *name, struct tftphdr *tp, const char *mode,
53856160Sru	off_t filesize)
53993139Sru{
540114472Sru	char *cp;
541114472Sru
542116525Sru	tp->th_opcode = htons((u_short)request);
543114472Sru#ifndef __SVR4
544114472Sru	cp = tp->th_stuff;
54593139Sru#else
54656160Sru	cp = (void *)&tp->th_stuff;
54756160Sru#endif
54856160Sru	(void)strcpy(cp, name);
54956160Sru	cp += strlen(name);
55056160Sru	*cp++ = '\0';
55156160Sru	(void)strcpy(cp, mode);
55256160Sru	cp += strlen(mode);
55356160Sru	*cp++ = '\0';
554146515Sru	if (tsize) {
555146515Sru		(void)strcpy(cp, "tsize");
556146515Sru		cp += strlen(cp);
557146515Sru		*cp++ = '\0';
55856160Sru		(void)sprintf(cp, "%lu", (unsigned long) filesize);
55956160Sru		cp += strlen(cp);
56056160Sru		*cp++ = '\0';
56156160Sru	}
56256160Sru	if (tout) {
56356160Sru		(void)strcpy(cp, "timeout");
56456160Sru		cp += strlen(cp);
56556160Sru		*cp++ = '\0';
56656160Sru		(void)sprintf(cp, "%d", rexmtval);
567146515Sru		cp += strlen(cp);
568146515Sru		*cp++ = '\0';
569146515Sru	}
570146515Sru	if (blksize != SEGSIZE) {
571146515Sru		(void)strcpy(cp, "blksize");
57256160Sru		cp += strlen(cp);
57356160Sru		*cp++ = '\0';
57456160Sru		(void)sprintf(cp, "%zd", blksize);
57556160Sru		cp += strlen(cp);
57656160Sru		*cp++ = '\0';
57756160Sru	}
57856160Sru	return (cp - (char *)(void *)tp);
57956160Sru}
58056160Sru
58156160Sruconst struct errmsg {
58256160Sru	int	e_code;
58356160Sru	const char *e_msg;
58456160Sru} errmsgs[] = {
58556160Sru	{ EUNDEF,	"Undefined error code" },
58656160Sru	{ ENOTFOUND,	"File not found" },
58756160Sru	{ EACCESS,	"Access violation" },
58856160Sru	{ ENOSPACE,	"Disk full or allocation exceeded" },
58956160Sru	{ EBADOP,	"Illegal TFTP operation" },
59093139Sru	{ EBADID,	"Unknown transfer ID" },
59156160Sru	{ EEXISTS,	"File already exists" },
59256160Sru	{ ENOUSER,	"No such user" },
59356160Sru	{ EOPTNEG,	"Option negotiation failed" },
59456160Sru	{ -1,		0 }
59556160Sru};
59693139Sru
59756160Sru/*
59856160Sru * Send a nak packet (error message).
59956160Sru * Error code passed in is one of the
60056160Sru * standard TFTP codes, or a UNIX errno
60156160Sru * offset by 100.
60256160Sru */
60356160Srustatic void
60456160Srunak(int error, struct sockaddr *peer)
605146515Sru{
60656160Sru	const struct errmsg *pe;
60756160Sru	struct tftphdr *tp;
60856160Sru	int length;
60956160Sru	size_t msglen;
61056160Sru
61156160Sru	tp = (struct tftphdr *)(void *)ackbuf;
61256160Sru	tp->th_opcode = htons((u_short)ERROR);
613146515Sru	msglen = sizeof(ackbuf) - (&tp->th_msg[0] - ackbuf);
61456160Sru	for (pe = errmsgs; pe->e_code >= 0; pe++)
61556160Sru		if (pe->e_code == error)
61656160Sru			break;
61756160Sru	if (pe->e_code < 0) {
61856160Sru		tp->th_code = EUNDEF;
61956160Sru		(void)strlcpy(tp->th_msg, strerror(error - 100), msglen);
62056160Sru	} else {
621146515Sru		tp->th_code = htons((u_short)error);
62256160Sru		(void)strlcpy(tp->th_msg, pe->e_msg, msglen);
62356160Sru	}
62456160Sru	length = strlen(tp->th_msg);
62556160Sru	msglen = &tp->th_msg[length + 1] - ackbuf;
62656160Sru	if (trace)
62756160Sru		tpacket("sent", tp, (int)msglen);
62856160Sru	if ((size_t)sendto(f, ackbuf, msglen, 0, peer, (socklen_t)peer->sa_len) != msglen)
62956160Sru		warn("nak");
63056160Sru}
63156160Sru
632146515Srustatic void
63356160Srutpacket(const char *s, struct tftphdr *tp, int n)
63456160Sru{
63556160Sru	static const char *opcodes[] =
63656160Sru	   { "#0", "RRQ", "WRQ", "DATA", "ACK", "ERROR", "OACK" };
63756160Sru	char *cp, *file, *endp, *opt = NULL;
63856160Sru	const char *spc;
63956160Sru	u_short op = ntohs(tp->th_opcode);
64056160Sru	int i, o;
64156160Sru
64256160Sru	if (op < RRQ || op > OACK)
64356160Sru		(void)printf("%s opcode=%x ", s, op);
64456160Sru	else
64556160Sru		(void)printf("%s %s ", s, opcodes[op]);
64693139Sru	switch (op) {
647114472Sru
64856160Sru	case RRQ:
64956160Sru	case WRQ:
65056160Sru		n -= 2;
65156160Sru#ifndef __SVR4
65256160Sru		cp = tp->th_stuff;
65356160Sru#else
65456160Sru		cp = (void *) &tp->th_stuff;
65556160Sru#endif
65656160Sru		endp = cp + n - 1;
65793139Sru		if (*endp != '\0') {	/* Shouldn't happen, but... */
65856160Sru			*endp = '\0';
65956160Sru		}
66056160Sru		file = cp;
66156160Sru		cp = strchr(cp, '\0') + 1;
66256160Sru		(void)printf("<file=%s, mode=%s", file, cp);
66356160Sru		cp = strchr(cp, '\0') + 1;
66456160Sru		o = 0;
66556160Sru		while (cp < endp) {
66656160Sru			i = strlen(cp) + 1;
66756160Sru			if (o) {
66856160Sru				(void)printf(", %s=%s", opt, cp);
66956160Sru			} else {
67056160Sru				opt = cp;
67156160Sru			}
67256160Sru			o = (o+1) % 2;
67356160Sru			cp += i;
67456160Sru		}
67556160Sru		(void)printf(">\n");
67656160Sru		break;
67756160Sru
67856160Sru	case DATA:
67956160Sru		(void)printf("<block=%d, %d bytes>\n", ntohs(tp->th_block),
68056160Sru		    n - 4);
68156160Sru		break;
68256160Sru
68356160Sru	case ACK:
68456160Sru		(void)printf("<block=%d>\n", ntohs(tp->th_block));
68556160Sru		break;
68656160Sru
68756160Sru	case ERROR:
68856160Sru		(void)printf("<code=%d, msg=%s>\n", ntohs(tp->th_code),
68956160Sru		    tp->th_msg);
69056160Sru		break;
69156160Sru
69256160Sru	case OACK:
69356160Sru		o = 0;
69456160Sru		n -= 2;
69556160Sru		cp = tp->th_stuff;
696146515Sru		endp = cp + n - 1;
697146515Sru		if (*endp != '\0') {	/* Shouldn't happen, but... */
69856160Sru			*endp = '\0';
69956160Sru		}
70056160Sru		(void)printf("<");
70156160Sru		spc = "";
70256160Sru		while (cp < endp) {
70356160Sru			i = strlen(cp) + 1;
70456160Sru			if (o) {
70556160Sru				(void)printf("%s%s=%s", spc, opt, cp);
70656160Sru				spc = ", ";
70756160Sru			} else {
70856160Sru				opt = cp;
70956160Sru			}
71056160Sru			o = (o+1) % 2;
711146515Sru			cp += i;
71256160Sru		}
713146515Sru		(void)printf(">\n");
71456160Sru		break;
71556160Sru	}
71656160Sru}
71756160Sru
71856160Srustruct timeval tstart;
719146515Srustruct timeval tstop;
72056160Sru
72156160Srustatic void
72256160Srustartclock(void)
72356160Sru{
72456160Sru
72556160Sru	(void)gettimeofday(&tstart, NULL);
726146515Sru}
72756160Sru
72856160Srustatic void
72956160Srustopclock(void)
73056160Sru{
73156160Sru
73256160Sru	(void)gettimeofday(&tstop, NULL);
733146515Sru}
73456160Sru
73556160Srustatic void
73656160Sruprintstats(const char *direction, unsigned long amount)
73756160Sru{
73856160Sru	double delta;
73956160Sru
740146515Sru	/* compute delta in 1/10's second units */
74156160Sru	delta = ((tstop.tv_sec*10.)+(tstop.tv_usec/100000)) -
742146515Sru		((tstart.tv_sec*10.)+(tstart.tv_usec/100000));
74356160Sru	delta = delta/10.;      /* back to seconds */
74456160Sru	(void)printf("%s %ld bytes in %.1f seconds", direction, amount, delta);
74556160Sru	if (verbose)
74656160Sru		(void)printf(" [%.0f bits/sec]", (amount*8.)/delta);
74756160Sru	(void)putchar('\n');
748146515Sru}
74956160Sru
75056160Srustatic void
75156160Sru/*ARGSUSED*/
75256160Srutimer(int sig)
75356160Sru{
75456160Sru
75556160Sru	timeout += rexmtval;
756146515Sru	if (timeout >= maxtimeout) {
75756160Sru		(void)printf("Transfer timed out.");
75856160Sru		longjmp(toplevel, -1);
75956160Sru	}
76056160Sru	longjmp(timeoutbuf, 1);
76156160Sru}
76256160Sru
76356160Srustatic int
764146515Srucmpport(struct sockaddr *sa, struct sockaddr *sb)
76556160Sru{
76656160Sru	char a[NI_MAXSERV], b[NI_MAXSERV];
76756160Sru
76856160Sru	if (getnameinfo(sa, (socklen_t)sa->sa_len, NULL, 0, a, sizeof(a),
76956160Sru	    NI_NUMERICSERV))
77056160Sru		return 0;
771146515Sru	if (getnameinfo(sb, (socklen_t)sb->sa_len, NULL, 0, b, sizeof(b),
77256160Sru	    NI_NUMERICSERV))
773146515Sru		return 0;
774146515Sru	if (strcmp(a, b) != 0)
775146515Sru		return 0;
776146515Sru
77756160Sru	return 1;
77856160Sru}
77956160Sru