1/*	$NetBSD: tftp.c,v 1.37 2023/01/06 17:18:56 christos Exp $	*/
2
3/*
4 * Copyright (c) 1983, 1993
5 *	The Regents of the University of California.  All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 * 3. Neither the name of the University nor the names of its contributors
16 *    may be used to endorse or promote products derived from this software
17 *    without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * SUCH DAMAGE.
30 */
31
32#include <sys/cdefs.h>
33#ifndef lint
34#if 0
35static char sccsid[] = "@(#)tftp.c	8.1 (Berkeley) 6/6/93";
36#else
37__RCSID("$NetBSD: tftp.c,v 1.37 2023/01/06 17:18:56 christos Exp $");
38#endif
39#endif /* not lint */
40
41/* Many bug fixes are from Jim Guyton <guyton@rand-unix> */
42
43/*
44 * TFTP User Program -- Protocol Machines
45 */
46#include <sys/types.h>
47#include <sys/param.h>
48#include <sys/socket.h>
49#include <sys/stat.h>
50#include <sys/time.h>
51
52#include <netinet/in.h>
53
54#include <arpa/tftp.h>
55#include <arpa/inet.h>
56
57#include <err.h>
58#include <errno.h>
59#include <setjmp.h>
60#include <signal.h>
61#include <stdio.h>
62#include <stdlib.h>
63#include <string.h>
64#include <unistd.h>
65#include <netdb.h>
66
67#include "extern.h"
68#include "tftpsubs.h"
69
70extern jmp_buf	toplevel;
71
72char    ackbuf[PKTSIZE];
73int	timeout;
74jmp_buf	timeoutbuf;
75
76static void nak(int, struct sockaddr *);
77static int makerequest(int, const char *, struct tftphdr *, const char *, off_t);
78static void printstats(const char *, unsigned long);
79static void startclock(void);
80static void stopclock(void);
81static __dead void timer(int);
82static void tpacket(const char *, struct tftphdr *, int);
83static int cmpport(struct sockaddr *, struct sockaddr *);
84
85static void get_options(struct tftphdr *, int);
86static int tftp_igmp_join(void);
87static void tftp_igmp_leave(int);
88
89static void
90get_options(struct tftphdr *ap, int size)
91{
92	unsigned long val;
93	char *opt, *endp, *nextopt, *valp;
94	int l;
95
96	size -= 2;	/* skip over opcode */
97	opt = ap->th_stuff;
98	endp = opt + size - 1;
99	*endp = '\0';
100
101	while (opt < endp) {
102		int ismulticast;
103		l = strlen(opt) + 1;
104		valp = opt + l;
105		ismulticast = !strcasecmp(opt, "multicast");
106		if (valp < endp) {
107			val = strtoul(valp, NULL, 10);
108			l = strlen(valp) + 1;
109			nextopt = valp + l;
110			if (!ismulticast) {
111				if (val == ULONG_MAX && errno == ERANGE) {
112					/* Report illegal value */
113					opt = nextopt;
114					continue;
115				}
116			}
117		} else {
118			/* Badly formed OACK */
119			break;
120		}
121		if (strcmp(opt, "tsize") == 0) {
122			/* cool, but we'll ignore it */
123		} else if (strcmp(opt, "timeout") == 0) {
124			if (val >= 1 && val <= 255) {
125				rexmtval = val;
126			} else {
127				/* Report error? */
128			}
129		} else if (strcmp(opt, "blksize") == 0) {
130			if (val >= 8 && val <= MAXSEGSIZE) {
131				blksize = val;
132			} else {
133				/* Report error? */
134			}
135		} else if (ismulticast) {
136			char multicast[24];
137			char *pmulticast;
138			char *addr;
139
140			strlcpy(multicast, valp, sizeof(multicast));
141			pmulticast = multicast;
142			addr = strsep(&pmulticast, ",");
143			if (pmulticast == NULL)
144				continue; /* Report error? */
145			mcport = atoi(strsep(&pmulticast, ","));
146			if (pmulticast == NULL)
147				continue; /* Report error? */
148			mcmasterslave = atoi(pmulticast);
149			mcaddr = inet_addr(addr);
150			if (mcaddr == INADDR_NONE)
151				continue; /* Report error? */
152		} else {
153			/* unknown option */
154		}
155		opt = nextopt;
156	}
157}
158
159static int
160tftp_igmp_join(void)
161{
162	struct ip_mreq req;
163	struct sockaddr_in s;
164	int fd, rv;
165
166	memset(&req, 0, sizeof(struct ip_mreq));
167	req.imr_multiaddr.s_addr = mcaddr;
168	req.imr_interface.s_addr = INADDR_ANY;
169
170	fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
171	if (fd < 0) {
172		perror("socket");
173		return fd;
174	}
175
176	memset(&s, 0, sizeof(struct sockaddr_in));
177	s.sin_family = AF_INET;
178	s.sin_port = htons(mcport);
179	s.sin_len = sizeof(struct sockaddr_in);
180	rv = bind(fd, (struct sockaddr *)&s, sizeof(struct sockaddr_in));
181	if (rv < 0) {
182		perror("bind");
183		close(fd);
184		return rv;
185	}
186
187	rv = setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &req,
188	    sizeof(struct ip_mreq));
189	if (rv < 0) {
190		perror("setsockopt");
191		close(fd);
192		return rv;
193	}
194
195	return fd;
196}
197
198static void
199tftp_igmp_leave(int fd)
200{
201	struct ip_mreq req;
202	int rv;
203
204	memset(&req, 0, sizeof(struct ip_mreq));
205	req.imr_multiaddr.s_addr = mcaddr;
206	req.imr_interface.s_addr = INADDR_ANY;
207
208	rv = setsockopt(fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &req,
209	    sizeof(struct ip_mreq));
210	if (rv < 0)
211		perror("setsockopt");
212
213	close(fd);
214
215	return;
216}
217
218/*
219 * Send the requested file.
220 */
221void
222sendfile(int fd, const char *name, const char *mode)
223{
224	struct tftphdr *ap;	   /* data and ack packets */
225	struct tftphdr *dp;
226	int j, n;
227	volatile unsigned int block;
228	volatile int size, convert;
229	volatile unsigned long amount;
230	struct sockaddr_storage from;
231	struct stat sbuf;
232	volatile off_t filesize = 0;
233	socklen_t fromlen;
234	FILE *file;
235	struct sockaddr_storage peer;
236	struct sockaddr_storage serv;	/* valid server port number */
237
238	startclock();		/* start stat's clock */
239	dp = r_init();		/* reset fillbuf/read-ahead code */
240	ap = (struct tftphdr *)(void *)ackbuf;
241	if (tsize) {
242		if (fstat(fd, &sbuf) == 0) {
243			filesize = sbuf.st_size;
244		} else {
245			filesize = -1ULL;
246		}
247	}
248	file = fdopen(fd, "r");
249	convert = !strcmp(mode, "netascii");
250	block = 0;
251	amount = 0;
252	(void)memcpy(&peer, &peeraddr, (size_t)peeraddr.ss_len);
253	(void)memset(&serv, 0, sizeof(serv));
254
255	(void)signal(SIGALRM, timer);
256	do {
257		if (block == 0)
258			size = makerequest(WRQ, name, dp, mode, filesize) - 4;
259		else {
260		/*	size = read(fd, dp->th_data, SEGSIZE);	 */
261			size = readit(file, &dp, blksize, convert);
262			if (size < 0) {
263				nak(errno + 100,
264				    (struct sockaddr *)(void *)&peer);
265				break;
266			}
267			dp->th_opcode = htons((u_short)DATA);
268			dp->th_block = htons((u_short)block);
269		}
270		timeout = 0;
271		(void) setjmp(timeoutbuf);
272send_data:
273		if (trace)
274			tpacket("sent", dp, size + 4);
275		n = sendto(f, dp, (socklen_t)(size + 4), 0,
276		    (struct sockaddr *)(void *)&peer, (socklen_t)peer.ss_len);
277		if (n != size + 4) {
278			warn("sendto");
279			goto abort;
280		}
281		if (block)
282			read_ahead(file, blksize, convert);
283		for ( ; ; ) {
284			(void)alarm(rexmtval);
285			do {
286				int curf;
287				fromlen = sizeof(from);
288				if (mcaddr != INADDR_NONE)
289					curf = mf;
290				else
291					curf = f;
292				n = recvfrom(curf, ackbuf, sizeof(ackbuf), 0,
293				    (struct sockaddr *)(void *)&from, &fromlen);
294			} while (n <= 0);
295			(void)alarm(0);
296			if (n < 0) {
297				warn("recvfrom");
298				goto abort;
299			}
300			if (!serv.ss_family)
301				serv = from;
302			else if (!cmpport((struct sockaddr *)(void *)&serv,
303			    (struct sockaddr *)(void *)&from)) {
304				warn("server port mismatch");
305				goto abort;
306			}
307			peer = from;
308			if (trace)
309				tpacket("received", ap, n);
310			/* should verify packet came from server */
311			ap->th_opcode = ntohs(ap->th_opcode);
312			if (ap->th_opcode == ERROR) {
313				(void)printf("Error code %d: %s\n",
314				    ntohs(ap->th_code), ap->th_msg);
315				goto abort;
316			}
317			if (ap->th_opcode == ACK) {
318				ap->th_block = ntohs(ap->th_block);
319
320				if (ap->th_block == 0) {
321					/*
322					 * If the extended options are enabled,
323					 * the server just refused 'em all.
324					 * The only one that _really_
325					 * matters is blksize, but we'll
326					 * clear timeout and mcaddr, too.
327					 */
328					blksize = def_blksize;
329					rexmtval = def_rexmtval;
330					mcaddr = INADDR_NONE;
331				}
332				if (ap->th_block == block) {
333					break;
334				}
335				/* On an error, try to synchronize
336				 * both sides.
337				 */
338				j = synchnet(f, blksize+4);
339				if (j && trace) {
340					(void)printf("discarded %d packets\n",
341							j);
342				}
343				if (ap->th_block == (block-1)) {
344					goto send_data;
345				}
346			}
347			if (ap->th_opcode == OACK) {
348				if (block == 0) {
349					blksize = def_blksize;
350					rexmtval = def_rexmtval;
351					mcaddr = INADDR_NONE;
352					get_options(ap, n);
353					break;
354				}
355			}
356		}
357		if (block > 0)
358			amount += size;
359		block++;
360	} while ((size_t)size == blksize || block == 1);
361abort:
362	(void)fclose(file);
363	stopclock();
364	if (amount > 0)
365		printstats("Sent", amount);
366}
367
368/*
369 * Receive a file.
370 */
371void
372recvfile(int fd, const char *name, const char *mode)
373{
374	struct tftphdr *ap;
375	struct tftphdr *dp;
376	int j, n;
377	volatile int oack = 0;
378	volatile unsigned int block;
379	volatile int size, firsttrip;
380	volatile unsigned long amount;
381	struct sockaddr_storage from;
382	socklen_t fromlen;
383	volatile size_t readlen;
384	FILE *file;
385	volatile int convert;		/* true if converting crlf -> lf */
386	struct sockaddr_storage peer;
387	struct sockaddr_storage serv;	/* valid server port number */
388
389	startclock();
390	dp = w_init();
391	ap = (struct tftphdr *)(void *)ackbuf;
392	file = fdopen(fd, "w");
393	convert = !strcmp(mode, "netascii");
394	block = 1;
395	firsttrip = 1;
396	amount = 0;
397	(void)memcpy(&peer, &peeraddr, (size_t)peeraddr.ss_len);
398	(void)memset(&serv, 0, sizeof(serv));
399
400	(void)signal(SIGALRM, timer);
401	do {
402		if (firsttrip) {
403			size = makerequest(RRQ, name, ap, mode, (off_t)0);
404			readlen = PKTSIZE;
405			firsttrip = 0;
406		} else {
407			ap->th_opcode = htons((u_short)ACK);
408			ap->th_block = htons((u_short)(block));
409			readlen = blksize+4;
410			size = 4;
411			block++;
412		}
413		timeout = 0;
414		(void) setjmp(timeoutbuf);
415send_ack:
416		if (trace)
417			tpacket("sent", ap, size);
418		if (sendto(f, ackbuf, (socklen_t)size, 0,
419		    (struct sockaddr *)(void *)&peer,
420		    (socklen_t)peer.ss_len) != size) {
421			(void)alarm(0);
422			warn("sendto");
423			goto abort;
424		}
425skip_ack:
426		if (write_behind(file, convert) == -1)
427			goto abort;
428		for ( ; ; ) {
429			(void)alarm(rexmtval);
430			do  {
431				int readfd;
432				if (mf > 0)
433					readfd = mf;
434				else
435					readfd = f;
436				fromlen = sizeof(from);
437				n = recvfrom(readfd, dp, readlen, 0,
438				    (struct sockaddr *)(void *)&from, &fromlen);
439			} while (n <= 0);
440			(void)alarm(0);
441			if (n < 0) {
442				warn("recvfrom");
443				goto abort;
444			}
445			if (!serv.ss_family)
446				serv = from;
447			else if (!cmpport((struct sockaddr *)(void *)&serv,
448			    (struct sockaddr *)(void *)&from)) {
449				warn("server port mismatch");
450				goto abort;
451			}
452			peer = from;
453			if (trace)
454				tpacket("received", dp, n);
455			/* should verify client address */
456			dp->th_opcode = ntohs(dp->th_opcode);
457			if (dp->th_opcode == ERROR) {
458				(void)printf("Error code %d: %s\n",
459				    ntohs(dp->th_code), dp->th_msg);
460				goto abort;
461			}
462			if (dp->th_opcode == DATA) {
463				dp->th_block = ntohs(dp->th_block);
464
465				if (dp->th_block == 1 && !oack) {
466					/* no OACK, revert to defaults */
467					blksize = def_blksize;
468					rexmtval = def_rexmtval;
469				}
470				if (dp->th_block == block) {
471					break;		/* have next packet */
472				}
473				/* On an error, try to synchronize
474				 * both sides.
475				 */
476				j = synchnet(f, blksize);
477				if (j && trace) {
478					(void)printf("discarded %d packets\n",
479					    j);
480				}
481				if (dp->th_block == (block-1)) {
482					goto send_ack;	/* resend ack */
483				}
484			}
485			if (dp->th_opcode == OACK) {
486				if (block == 1) {
487					oack = 1;
488					blksize = def_blksize;
489					rexmtval = def_rexmtval;
490					get_options(dp, n);
491					ap->th_opcode = htons(ACK);
492					ap->th_block = 0;
493					readlen = blksize+4;
494					size = 4;
495					if (mcaddr != INADDR_NONE) {
496						mf = tftp_igmp_join();
497						if (mf < 0)
498							goto abort;
499						if (mcmasterslave == 0)
500							goto skip_ack;
501					}
502					goto send_ack;
503				}
504			}
505		}
506	/*	size = write(fd, dp->th_data, n - 4); */
507		size = writeit(file, &dp, n - 4, convert);
508		if (size < 0) {
509			nak(errno + 100, (struct sockaddr *)(void *)&peer);
510			break;
511		}
512		amount += size;
513	} while ((size_t)size == blksize);
514abort:						/* ok to ack, since user */
515	ap->th_opcode = htons((u_short)ACK);	/* has seen err msg */
516	ap->th_block = htons((u_short)block);
517	if (mcaddr != INADDR_NONE && mf >= 0) {
518		tftp_igmp_leave(mf);
519		mf = -1;
520	}
521	(void) sendto(f, ackbuf, 4, 0, (struct sockaddr *)(void *)&peer,
522	    (socklen_t)peer.ss_len);
523	/*
524	 * flush last buffer
525	 * We do not check for failure because last buffer
526	 * can be empty, thus returning an error.
527	 * XXX maybe we should fix 'write_behind' instead.
528	 */
529	(void)write_behind(file, convert);
530	(void)fclose(file);
531	stopclock();
532	if (amount > 0)
533		printstats("Received", amount);
534}
535
536static int
537makerequest(int request, const char *name, struct tftphdr *tp, const char *mode,
538	off_t filesize)
539{
540	char *cp;
541
542	tp->th_opcode = htons((u_short)request);
543#ifndef __SVR4
544	cp = tp->th_stuff;
545#else
546	cp = (void *)&tp->th_stuff;
547#endif
548	(void)strcpy(cp, name);
549	cp += strlen(name);
550	*cp++ = '\0';
551	(void)strcpy(cp, mode);
552	cp += strlen(mode);
553	*cp++ = '\0';
554	if (tsize) {
555		(void)strcpy(cp, "tsize");
556		cp += strlen(cp);
557		*cp++ = '\0';
558		(void)sprintf(cp, "%lu", (unsigned long) filesize);
559		cp += strlen(cp);
560		*cp++ = '\0';
561	}
562	if (tout) {
563		(void)strcpy(cp, "timeout");
564		cp += strlen(cp);
565		*cp++ = '\0';
566		(void)sprintf(cp, "%d", rexmtval);
567		cp += strlen(cp);
568		*cp++ = '\0';
569	}
570	if (blksize != SEGSIZE) {
571		(void)strcpy(cp, "blksize");
572		cp += strlen(cp);
573		*cp++ = '\0';
574		(void)sprintf(cp, "%zd", blksize);
575		cp += strlen(cp);
576		*cp++ = '\0';
577	}
578	return (cp - (char *)(void *)tp);
579}
580
581const struct errmsg {
582	int	e_code;
583	const char *e_msg;
584} errmsgs[] = {
585	{ EUNDEF,	"Undefined error code" },
586	{ ENOTFOUND,	"File not found" },
587	{ EACCESS,	"Access violation" },
588	{ ENOSPACE,	"Disk full or allocation exceeded" },
589	{ EBADOP,	"Illegal TFTP operation" },
590	{ EBADID,	"Unknown transfer ID" },
591	{ EEXISTS,	"File already exists" },
592	{ ENOUSER,	"No such user" },
593	{ EOPTNEG,	"Option negotiation failed" },
594	{ -1,		0 }
595};
596
597/*
598 * Send a nak packet (error message).
599 * Error code passed in is one of the
600 * standard TFTP codes, or a UNIX errno
601 * offset by 100.
602 */
603static void
604nak(int error, struct sockaddr *peer)
605{
606	const struct errmsg *pe;
607	struct tftphdr *tp;
608	int length;
609	size_t msglen;
610
611	tp = (struct tftphdr *)(void *)ackbuf;
612	tp->th_opcode = htons((u_short)ERROR);
613	msglen = sizeof(ackbuf) - (&tp->th_msg[0] - ackbuf);
614	for (pe = errmsgs; pe->e_code >= 0; pe++)
615		if (pe->e_code == error)
616			break;
617	if (pe->e_code < 0) {
618		tp->th_code = EUNDEF;
619		(void)strlcpy(tp->th_msg, strerror(error - 100), msglen);
620	} else {
621		tp->th_code = htons((u_short)error);
622		(void)strlcpy(tp->th_msg, pe->e_msg, msglen);
623	}
624	length = strlen(tp->th_msg);
625	msglen = &tp->th_msg[length + 1] - ackbuf;
626	if (trace)
627		tpacket("sent", tp, (int)msglen);
628	if ((size_t)sendto(f, ackbuf, msglen, 0, peer, (socklen_t)peer->sa_len) != msglen)
629		warn("nak");
630}
631
632static void
633tpacket(const char *s, struct tftphdr *tp, int n)
634{
635	static const char *opcodes[] =
636	   { "#0", "RRQ", "WRQ", "DATA", "ACK", "ERROR", "OACK" };
637	char *cp, *file, *endp, *opt = NULL;
638	const char *spc;
639	u_short op = ntohs(tp->th_opcode);
640	int i, o;
641
642	if (op < RRQ || op > OACK)
643		(void)printf("%s opcode=%x ", s, op);
644	else
645		(void)printf("%s %s ", s, opcodes[op]);
646	switch (op) {
647
648	case RRQ:
649	case WRQ:
650		n -= 2;
651#ifndef __SVR4
652		cp = tp->th_stuff;
653#else
654		cp = (void *) &tp->th_stuff;
655#endif
656		endp = cp + n - 1;
657		if (*endp != '\0') {	/* Shouldn't happen, but... */
658			*endp = '\0';
659		}
660		file = cp;
661		cp = strchr(cp, '\0') + 1;
662		(void)printf("<file=%s, mode=%s", file, cp);
663		cp = strchr(cp, '\0') + 1;
664		o = 0;
665		while (cp < endp) {
666			i = strlen(cp) + 1;
667			if (o) {
668				(void)printf(", %s=%s", opt, cp);
669			} else {
670				opt = cp;
671			}
672			o = (o+1) % 2;
673			cp += i;
674		}
675		(void)printf(">\n");
676		break;
677
678	case DATA:
679		(void)printf("<block=%d, %d bytes>\n", ntohs(tp->th_block),
680		    n - 4);
681		break;
682
683	case ACK:
684		(void)printf("<block=%d>\n", ntohs(tp->th_block));
685		break;
686
687	case ERROR:
688		(void)printf("<code=%d, msg=%s>\n", ntohs(tp->th_code),
689		    tp->th_msg);
690		break;
691
692	case OACK:
693		o = 0;
694		n -= 2;
695		cp = tp->th_stuff;
696		endp = cp + n - 1;
697		if (*endp != '\0') {	/* Shouldn't happen, but... */
698			*endp = '\0';
699		}
700		(void)printf("<");
701		spc = "";
702		while (cp < endp) {
703			i = strlen(cp) + 1;
704			if (o) {
705				(void)printf("%s%s=%s", spc, opt, cp);
706				spc = ", ";
707			} else {
708				opt = cp;
709			}
710			o = (o+1) % 2;
711			cp += i;
712		}
713		(void)printf(">\n");
714		break;
715	}
716}
717
718struct timeval tstart;
719struct timeval tstop;
720
721static void
722startclock(void)
723{
724
725	(void)gettimeofday(&tstart, NULL);
726}
727
728static void
729stopclock(void)
730{
731
732	(void)gettimeofday(&tstop, NULL);
733}
734
735static void
736printstats(const char *direction, unsigned long amount)
737{
738	double delta;
739
740	/* compute delta in 1/10's second units */
741	delta = ((tstop.tv_sec*10.)+(tstop.tv_usec/100000)) -
742		((tstart.tv_sec*10.)+(tstart.tv_usec/100000));
743	delta = delta/10.;      /* back to seconds */
744	(void)printf("%s %ld bytes in %.1f seconds", direction, amount, delta);
745	if (verbose)
746		(void)printf(" [%.0f bits/sec]", (amount*8.)/delta);
747	(void)putchar('\n');
748}
749
750static void
751/*ARGSUSED*/
752timer(int sig)
753{
754
755	timeout += rexmtval;
756	if (timeout >= maxtimeout) {
757		(void)printf("Transfer timed out.");
758		longjmp(toplevel, -1);
759	}
760	longjmp(timeoutbuf, 1);
761}
762
763static int
764cmpport(struct sockaddr *sa, struct sockaddr *sb)
765{
766	char a[NI_MAXSERV], b[NI_MAXSERV];
767
768	if (getnameinfo(sa, (socklen_t)sa->sa_len, NULL, 0, a, sizeof(a),
769	    NI_NUMERICSERV))
770		return 0;
771	if (getnameinfo(sb, (socklen_t)sb->sa_len, NULL, 0, b, sizeof(b),
772	    NI_NUMERICSERV))
773		return 0;
774	if (strcmp(a, b) != 0)
775		return 0;
776
777	return 1;
778}
779