xref: /openbsd-src/lib/libc/net/rcmd.c (revision f1a075da3e4bb4f84a5bb83783c6b1943dc3da49)
1 /*
2  * Copyright (c) 1995, 1996, 1998 Theo de Raadt.  All rights reserved.
3  * Copyright (c) 1983, 1993, 1994
4  *	The Regents of the University of California.  All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  * 3. All advertising materials mentioning features or use of this software
15  *    must display the following acknowledgement:
16  *	This product includes software developed by the University of
17  *	California, Berkeley and its contributors.
18  *	This product includes software developed by Theo de Raadt.
19  * 4. Neither the name of the University nor the names of its contributors
20  *    may be used to endorse or promote products derived from this software
21  *    without specific prior written permission.
22  *
23  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33  * SUCH DAMAGE.
34  */
35 
36 #if defined(LIBC_SCCS) && !defined(lint)
37 static char *rcsid = "$OpenBSD: rcmd.c,v 1.38 2001/06/27 00:58:55 lebel Exp $";
38 #endif /* LIBC_SCCS and not lint */
39 
40 #include <sys/param.h>
41 #include <sys/socket.h>
42 #include <sys/stat.h>
43 
44 #include <netinet/in.h>
45 #include <arpa/inet.h>
46 
47 #include <signal.h>
48 #include <fcntl.h>
49 #include <netdb.h>
50 #include <unistd.h>
51 #include <pwd.h>
52 #include <errno.h>
53 #include <stdio.h>
54 #include <ctype.h>
55 #include <string.h>
56 #include <syslog.h>
57 #include <stdlib.h>
58 #include <netgroup.h>
59 
60 int	__ivaliduser __P((FILE *, in_addr_t, const char *, const char *));
61 int	__ivaliduser_sa __P((FILE *, struct sockaddr *, socklen_t,
62 		const char *, const char *));
63 static int __icheckhost __P((struct sockaddr *, socklen_t, const char *));
64 static char *__gethostloop __P((struct sockaddr *, socklen_t));
65 
66 int
67 rcmd(ahost, rport, locuser, remuser, cmd, fd2p)
68 	char **ahost;
69 	in_port_t rport;
70 	const char *locuser, *remuser, *cmd;
71 	int *fd2p;
72 {
73 	return rcmd_af(ahost, rport, locuser, remuser, cmd, fd2p, AF_INET);
74 }
75 
76 int
77 rcmd_af(ahost, rport, locuser, remuser, cmd, fd2p, af)
78 	char **ahost;
79 	in_port_t rport;
80 	const char *locuser, *remuser, *cmd;
81 	int *fd2p;
82 	int af;
83 {
84 	static char hbuf[MAXHOSTNAMELEN];
85 	char pbuf[NI_MAXSERV];
86 	struct addrinfo hints, *res, *r;
87 	int error;
88 	struct sockaddr_storage from;
89 	fd_set *readsp = NULL;
90 	int oldmask;
91 	pid_t pid;
92 	int s, lport, timo;
93 	char c, *p;
94 	int refused;
95 
96 	/* call rcmdsh() with specified remote shell if appropriate. */
97 	if (!issetugid() && (p = getenv("RSH")) && *p) {
98 		struct servent *sp = getservbyname("shell", "tcp");
99 
100 		if (sp && sp->s_port == rport)
101 			return (rcmdsh(ahost, rport, locuser, remuser,
102 			    cmd, p));
103 	}
104 
105 	/* use rsh(1) if non-root and remote port is shell. */
106 	if (geteuid()) {
107 		struct servent *sp = getservbyname("shell", "tcp");
108 
109 		if (sp && sp->s_port == rport)
110 			return (rcmdsh(ahost, rport, locuser, remuser,
111 			    cmd, NULL));
112 	}
113 
114 	pid = getpid();
115 	snprintf(pbuf, sizeof(pbuf), "%u", ntohs(rport));
116 	memset(&hints, 0, sizeof(hints));
117 	hints.ai_family = af;
118 	hints.ai_socktype = SOCK_STREAM;
119 	hints.ai_flags = AI_CANONNAME;
120 	error = getaddrinfo(*ahost, pbuf, &hints, &res);
121 	if (error) {
122 #if 0
123 		warnx("%s: %s", *ahost, gai_strerror(error));
124 #endif
125 		return (-1);
126 	}
127 	if (res->ai_canonname) {
128 		strlcpy(hbuf, res->ai_canonname, sizeof(hbuf));
129 		*ahost = hbuf;
130 	} else
131 		; /*XXX*/
132 
133 	r = res;
134 	refused = 0;
135 	oldmask = sigblock(sigmask(SIGURG));
136 	for (timo = 1, lport = IPPORT_RESERVED - 1;;) {
137 		s = rresvport_af(&lport, r->ai_family);
138 		if (s < 0) {
139 			if (errno == EAGAIN)
140 				(void)fprintf(stderr,
141 				    "rcmd: socket: All ports in use\n");
142 			else
143 				(void)fprintf(stderr, "rcmd: socket: %s\n",
144 				    strerror(errno));
145 			if (r->ai_next) {
146 				r = r->ai_next;
147 				continue;
148 			} else {
149 				sigsetmask(oldmask);
150 				freeaddrinfo(res);
151 				return (-1);
152 			}
153 		}
154 		fcntl(s, F_SETOWN, pid);
155 		if (connect(s, r->ai_addr, r->ai_addrlen) >= 0)
156 			break;
157 		(void)close(s);
158 		if (errno == EADDRINUSE) {
159 			lport--;
160 			continue;
161 		}
162 		if (errno == ECONNREFUSED)
163 			refused++;
164 		if (r->ai_next) {
165 			int oerrno = errno;
166 			char hbuf[NI_MAXHOST];
167 #ifdef NI_WITHSCOPEID
168 			const int niflags = NI_NUMERICHOST | NI_WITHSCOPEID;
169 #else
170 			const int niflags = NI_NUMERICHOST;
171 #endif
172 
173 			hbuf[0] = '\0';
174 			if (getnameinfo(r->ai_addr, r->ai_addrlen,
175 			    hbuf, sizeof(hbuf), NULL, 0, niflags) != 0)
176 				strcpy(hbuf, "(invalid)");
177 			(void)fprintf(stderr, "connect to address %s: ", hbuf);
178 			errno = oerrno;
179 			perror(0);
180 			r = r->ai_next;
181 			hbuf[0] = '\0';
182 			if (getnameinfo(r->ai_addr, r->ai_addrlen,
183 			    hbuf, sizeof(hbuf), NULL, 0, niflags) != 0)
184 				strcpy(hbuf, "(invalid)");
185 			(void)fprintf(stderr, "Trying %s...\n", hbuf);
186 			continue;
187 		}
188 		if (refused && timo <= 16) {
189 			(void)sleep(timo);
190 			timo *= 2;
191 			r = res;
192 			refused = 0;
193 			continue;
194 		}
195 		(void)fprintf(stderr, "%s: %s\n", res->ai_canonname,
196 		    strerror(errno));
197 		sigsetmask(oldmask);
198 		freeaddrinfo(res);
199 		return (-1);
200 	}
201 	/* given "af" can be PF_UNSPEC, we need the real af for "s" */
202 	af = r->ai_family;
203 	freeaddrinfo(res);
204 #if 0
205 	/*
206 	 * try to rresvport() to the same port. This will make rresvport()
207 	 * fail it's first bind, resulting in it choosing a random port.
208 	 */
209 	lport--;
210 #endif
211 	if (fd2p == 0) {
212 		write(s, "", 1);
213 		lport = 0;
214 	} else {
215 		char num[8];
216 		int s2 = rresvport_af(&lport, af), s3;
217 		int len = sizeof(from);
218 		int fdssize = howmany(MAX(s, s2)+1, NFDBITS) * sizeof(fd_mask);
219 
220 		if (s2 < 0)
221 			goto bad;
222 		readsp = (fd_set *)malloc(fdssize);
223 		if (readsp == NULL)
224 			goto bad;
225 		listen(s2, 1);
226 		(void)snprintf(num, sizeof(num), "%d", lport);
227 		if (write(s, num, strlen(num)+1) != strlen(num)+1) {
228 			(void)fprintf(stderr,
229 			    "rcmd: write (setting up stderr): %s\n",
230 			    strerror(errno));
231 			(void)close(s2);
232 			goto bad;
233 		}
234 again:
235 		bzero(readsp, fdssize);
236 		FD_SET(s, readsp);
237 		FD_SET(s2, readsp);
238 		errno = 0;
239 		if (select(MAX(s, s2) + 1, readsp, 0, 0, 0) < 1 ||
240 		    !FD_ISSET(s2, readsp)) {
241 			if (errno != 0)
242 				(void)fprintf(stderr,
243 				    "rcmd: select (setting up stderr): %s\n",
244 				    strerror(errno));
245 			else
246 				(void)fprintf(stderr,
247 				"select: protocol failure in circuit setup\n");
248 			(void)close(s2);
249 			goto bad;
250 		}
251 		s3 = accept(s2, (struct sockaddr *)&from, &len);
252 		/*
253 		 * XXX careful for ftp bounce attacks. If discovered, shut them
254 		 * down and check for the real auxiliary channel to connect.
255 		 */
256 		switch (from.ss_family) {
257 		case AF_INET:
258 		case AF_INET6:
259 			if (getnameinfo((struct sockaddr *)&from, len,
260 			    NULL, 0, num, sizeof(num), NI_NUMERICSERV) == 0 &&
261 			    atoi(num) != 20) {
262 				break;
263 			}
264 			close(s3);
265 			goto again;
266 		default:
267 			break;
268 		}
269 		(void)close(s2);
270 		if (s3 < 0) {
271 			(void)fprintf(stderr,
272 			    "rcmd: accept: %s\n", strerror(errno));
273 			lport = 0;
274 			goto bad;
275 		}
276 		*fd2p = s3;
277 		switch (from.ss_family) {
278 		case AF_INET:
279 		case AF_INET6:
280 			if (getnameinfo((struct sockaddr *)&from, len,
281 			    NULL, 0, num, sizeof(num), NI_NUMERICSERV) != 0 ||
282 			    (atoi(num) >= IPPORT_RESERVED ||
283 			     atoi(num) < IPPORT_RESERVED / 2)) {
284 				(void)fprintf(stderr,
285 				    "socket: protocol failure in circuit setup.\n");
286 				goto bad2;
287 			}
288 			break;
289 		default:
290 			break;
291 		}
292 	}
293 	(void)write(s, locuser, strlen(locuser)+1);
294 	(void)write(s, remuser, strlen(remuser)+1);
295 	(void)write(s, cmd, strlen(cmd)+1);
296 	if (read(s, &c, 1) != 1) {
297 		(void)fprintf(stderr,
298 		    "rcmd: %s: %s\n", *ahost, strerror(errno));
299 		goto bad2;
300 	}
301 	if (c != 0) {
302 		while (read(s, &c, 1) == 1) {
303 			(void)write(STDERR_FILENO, &c, 1);
304 			if (c == '\n')
305 				break;
306 		}
307 		goto bad2;
308 	}
309 	sigsetmask(oldmask);
310 	free(readsp);
311 	return (s);
312 bad2:
313 	if (lport)
314 		(void)close(*fd2p);
315 bad:
316 	if (readsp)
317 		free(readsp);
318 	(void)close(s);
319 	sigsetmask(oldmask);
320 	return (-1);
321 }
322 
323 int	__check_rhosts_file = 1;
324 char	*__rcmd_errstr;
325 
326 int
327 ruserok(rhost, superuser, ruser, luser)
328 	const char *rhost, *ruser, *luser;
329 	int superuser;
330 {
331 	struct addrinfo hints, *res, *r;
332 	int error;
333 
334 	memset(&hints, 0, sizeof(hints));
335 	hints.ai_family = PF_UNSPEC;
336 	hints.ai_socktype = SOCK_DGRAM;	/*dummy*/
337 	error = getaddrinfo(rhost, "0", &hints, &res);
338 	if (error)
339 		return (-1);
340 
341 	for (r = res; r; r = r->ai_next) {
342 		if (iruserok_sa(r->ai_addr, r->ai_addrlen, superuser, ruser,
343 		    luser) == 0) {
344 			freeaddrinfo(res);
345 			return (0);
346 		}
347 	}
348 	freeaddrinfo(res);
349 	return (-1);
350 }
351 
352 /*
353  * New .rhosts strategy: We are passed an ip address. We spin through
354  * hosts.equiv and .rhosts looking for a match. When the .rhosts only
355  * has ip addresses, we don't have to trust a nameserver.  When it
356  * contains hostnames, we spin through the list of addresses the nameserver
357  * gives us and look for a match.
358  *
359  * Returns 0 if ok, -1 if not ok.
360  */
361 int
362 iruserok(raddr, superuser, ruser, luser)
363 	u_int32_t raddr;
364 	int superuser;
365 	const char *ruser, *luser;
366 {
367 	struct sockaddr_in sin;
368 
369 	memset(&sin, 0, sizeof(sin));
370 	sin.sin_family = AF_INET;
371 	sin.sin_len = sizeof(struct sockaddr_in);
372 	memcpy(&sin.sin_addr, &raddr, sizeof(sin.sin_addr));
373 	return iruserok_sa(&sin, sizeof(struct sockaddr_in), superuser, ruser,
374 		    luser);
375 }
376 
377 int
378 iruserok_sa(raddr, rlen, superuser, ruser, luser)
379 	const void *raddr;
380 	int rlen;
381 	int superuser;
382 	const char *ruser, *luser;
383 {
384 	struct sockaddr *sa;
385 	register char *cp;
386 	struct stat sbuf;
387 	struct passwd *pwd;
388 	FILE *hostf;
389 	uid_t uid;
390 	int first;
391 	char pbuf[MAXPATHLEN];
392 
393 	sa = (struct sockaddr *)raddr;
394 	first = 1;
395 	hostf = superuser ? NULL : fopen(_PATH_HEQUIV, "r");
396 again:
397 	if (hostf) {
398 		if (__ivaliduser_sa(hostf, sa, rlen, luser, ruser) == 0) {
399 			(void)fclose(hostf);
400 			return (0);
401 		}
402 		(void)fclose(hostf);
403 	}
404 	if (first == 1 && (__check_rhosts_file || superuser)) {
405 		first = 0;
406 		if ((pwd = getpwnam(luser)) == NULL)
407 			return (-1);
408 		(void)strcpy(pbuf, pwd->pw_dir);
409 		(void)strcat(pbuf, "/.rhosts");
410 
411 		/*
412 		 * Change effective uid while opening .rhosts.  If root and
413 		 * reading an NFS mounted file system, can't read files that
414 		 * are protected read/write owner only.
415 		 */
416 		uid = geteuid();
417 		(void)seteuid(pwd->pw_uid);
418 		hostf = fopen(pbuf, "r");
419 		(void)seteuid(uid);
420 
421 		if (hostf == NULL)
422 			return (-1);
423 		/*
424 		 * If not a regular file, or is owned by someone other than
425 		 * user or root or if writeable by anyone but the owner, quit.
426 		 */
427 		cp = NULL;
428 		if (lstat(pbuf, &sbuf) < 0)
429 			cp = ".rhosts lstat failed";
430 		else if (!S_ISREG(sbuf.st_mode))
431 			cp = ".rhosts not regular file";
432 		else if (fstat(fileno(hostf), &sbuf) < 0)
433 			cp = ".rhosts fstat failed";
434 		else if (sbuf.st_uid && sbuf.st_uid != pwd->pw_uid)
435 			cp = "bad .rhosts owner";
436 		else if (sbuf.st_mode & (S_IWGRP|S_IWOTH))
437 			cp = ".rhosts writeable by other than owner";
438 		/* If there were any problems, quit. */
439 		if (cp) {
440 			__rcmd_errstr = cp;
441 			(void)fclose(hostf);
442 			return (-1);
443 		}
444 		goto again;
445 	}
446 	return (-1);
447 }
448 
449 /*
450  * XXX
451  * Don't make static, used by lpd(8).
452  *
453  * Returns 0 if ok, -1 if not ok.
454  */
455 int
456 __ivaliduser(hostf, raddrl, luser, ruser)
457 	FILE *hostf;
458 	in_addr_t raddrl;
459 	const char *luser, *ruser;
460 {
461 	struct sockaddr_in sin;
462 
463 	memset(&sin, 0, sizeof(sin));
464 	sin.sin_family = AF_INET;
465 	sin.sin_len = sizeof(struct sockaddr_in);
466 	memcpy(&sin.sin_addr, &raddrl, sizeof(sin.sin_addr));
467 	return __ivaliduser_sa(hostf, (struct sockaddr *)&sin, sin.sin_len,
468 		    luser, ruser);
469 }
470 
471 int
472 __ivaliduser_sa(hostf, raddr, salen, luser, ruser)
473 	FILE *hostf;
474 	struct sockaddr *raddr;
475 	socklen_t salen;
476 	const char *luser, *ruser;
477 {
478 	register char *user, *p;
479 	char *buf;
480 	const char *auser, *ahost;
481 	int hostok, userok;
482 	char *rhost = (char *)-1;
483 	char domain[MAXHOSTNAMELEN];
484 	size_t buflen;
485 
486 	getdomainname(domain, sizeof(domain));
487 
488 	while ((buf = fgetln(hostf, &buflen))) {
489 		p = buf;
490 		if (*p == '#')
491 			continue;
492 		while (*p != '\n' && *p != ' ' && *p != '\t' && p < buf + buflen) {
493 			if (!isprint(*p))
494 				goto bail;
495 			*p = isupper(*p) ? tolower(*p) : *p;
496 			p++;
497 		}
498 		if (p >= buf + buflen)
499 			continue;
500 		if (*p == ' ' || *p == '\t') {
501 			*p++ = '\0';
502 			while ((*p == ' ' || *p == '\t') && p < buf + buflen)
503 				p++;
504 			if (p >= buf + buflen)
505 				continue;
506 			user = p;
507 			while (*p != '\n' && *p != ' ' &&
508 			    *p != '\t' && p < buf + buflen) {
509 				if (!isprint(*p))
510 					goto bail;
511 				p++;
512 			}
513 		} else
514 			user = p;
515 		*p = '\0';
516 
517 		if (p == buf)
518 			continue;
519 
520 		auser = *user ? user : luser;
521 		ahost = buf;
522 
523 		if (strlen(ahost) >= MAXHOSTNAMELEN)
524 			continue;
525 
526 		/*
527 		 * innetgr() must lookup a hostname (we do not attempt
528 		 * to change the semantics so that netgroups may have
529 		 * #.#.#.# addresses in the list.)
530 		 */
531 		if (ahost[0] == '+')
532 			switch (ahost[1]) {
533 			case '\0':
534 				hostok = 1;
535 				break;
536 			case '@':
537 				if (rhost == (char *)-1)
538 					rhost = __gethostloop(raddr, salen);
539 				hostok = 0;
540 				if (rhost)
541 					hostok = innetgr(&ahost[2], rhost,
542 					    NULL, domain);
543 				break;
544 			default:
545 				hostok = __icheckhost(raddr, salen, &ahost[1]);
546 				break;
547 			}
548 		else if (ahost[0] == '-')
549 			switch (ahost[1]) {
550 			case '\0':
551 				hostok = -1;
552 				break;
553 			case '@':
554 				if (rhost == (char *)-1)
555 					rhost = __gethostloop(raddr, salen);
556 				hostok = 0;
557 				if (rhost)
558 					hostok = -innetgr(&ahost[2], rhost,
559 					    NULL, domain);
560 				break;
561 			default:
562 				hostok = -__icheckhost(raddr, salen, &ahost[1]);
563 				break;
564 			}
565 		else
566 			hostok = __icheckhost(raddr, salen, ahost);
567 
568 
569 		if (auser[0] == '+')
570 			switch (auser[1]) {
571 			case '\0':
572 				userok = 1;
573 				break;
574 			case '@':
575 				userok = innetgr(&auser[2], NULL, ruser,
576 				    domain);
577 				break;
578 			default:
579 				userok = strcmp(ruser, &auser[1]) ? 0 : 1;
580 				break;
581 			}
582 		else if (auser[0] == '-')
583 			switch (auser[1]) {
584 			case '\0':
585 				userok = -1;
586 				break;
587 			case '@':
588 				userok = -innetgr(&auser[2], NULL, ruser,
589 				    domain);
590 				break;
591 			default:
592 				userok = strcmp(ruser, &auser[1]) ? 0 : -1;
593 				break;
594 			}
595 		else
596 			userok = strcmp(ruser, auser) ? 0 : 1;
597 
598 		/* Check if one component did not match */
599 		if (hostok == 0 || userok == 0)
600 			continue;
601 
602 		/* Check if we got a forbidden pair */
603 		if (userok <= -1 || hostok <= -1)
604 			return (-1);
605 
606 		/* Check if we got a valid pair */
607 		if (hostok >= 1 && userok >= 1)
608 			return (0);
609 	}
610 bail:
611 	return (-1);
612 }
613 
614 /*
615  * Returns "true" if match, 0 if no match.  If we do not find any
616  * semblance of an A->PTR->A loop, allow a simple #.#.#.# match to work.
617  *
618  * NI_WITHSCOPEID is useful for comparing sin6_scope_id portion
619  * if af == AF_INET6.
620  */
621 static int
622 __icheckhost(raddr, salen, lhost)
623 	struct sockaddr *raddr;
624 	socklen_t salen;
625 	const char *lhost;
626 {
627 	struct addrinfo hints, *res, *r;
628 	char h1[NI_MAXHOST], h2[NI_MAXHOST];
629 	int error;
630 #ifdef NI_WITHSCOPEID
631 	const int niflags = NI_NUMERICHOST | NI_WITHSCOPEID;
632 #else
633 	const int niflags = NI_NUMERICHOST;
634 #endif
635 
636 	h1[0] = '\0';
637 	if (getnameinfo(raddr, salen, h1, sizeof(h1), NULL, 0,
638 	    niflags) != 0)
639 		return (0);
640 
641 	/* Resolve laddr into sockaddr */
642 	memset(&hints, 0, sizeof(hints));
643 	hints.ai_family = raddr->sa_family;
644 	hints.ai_socktype = SOCK_DGRAM;	/*dummy*/
645 	res = NULL;
646 	error = getaddrinfo(lhost, "0", &hints, &res);
647 	if (error)
648 		return (0);
649 
650 	/*
651 	 * Try string comparisons between raddr and laddr.
652 	 */
653 	for (r = res; r; r = r->ai_next) {
654 		h2[0] = '\0';
655 		if (getnameinfo(r->ai_addr, r->ai_addrlen, h2, sizeof(h2),
656 		    NULL, 0, niflags) != 0)
657 			continue;
658 		if (strcmp(h1, h2) == 0) {
659 			freeaddrinfo(res);
660 			return (1);
661 		}
662 	}
663 
664 	/* No match. */
665 	freeaddrinfo(res);
666 	return (0);
667 }
668 
669 /*
670  * Return the hostname associated with the supplied address.
671  * Do a reverse lookup as well for security. If a loop cannot
672  * be found, pack the result of inet_ntoa() into the string.
673  *
674  * NI_WITHSCOPEID is useful for comparing sin6_scope_id portion
675  * if af == AF_INET6.
676  */
677 static char *
678 __gethostloop(raddr, salen)
679 	struct sockaddr *raddr;
680 	socklen_t salen;
681 {
682 	static char remotehost[NI_MAXHOST];
683 	char h1[NI_MAXHOST], h2[NI_MAXHOST];
684 	struct addrinfo hints, *res, *r;
685 	int error;
686 #ifdef NI_WITHSCOPEID
687 	const int niflags = NI_NUMERICHOST | NI_WITHSCOPEID;
688 #else
689 	const int niflags = NI_NUMERICHOST;
690 #endif
691 
692 	h1[0] = remotehost[0] = '\0';
693 	if (getnameinfo(raddr, salen, remotehost, sizeof(remotehost),
694 	    NULL, 0, NI_NAMEREQD) != 0)
695 		return (NULL);
696 	if (getnameinfo(raddr, salen, h1, sizeof(h1), NULL, 0,
697 	    niflags) != 0)
698 		return (NULL);
699 
700 	/*
701 	 * Look up the name and check that the supplied
702 	 * address is in the list
703 	 */
704 	memset(&hints, 0, sizeof(hints));
705 	hints.ai_family = raddr->sa_family;
706 	hints.ai_socktype = SOCK_DGRAM;	/*dummy*/
707 	hints.ai_flags = AI_CANONNAME;
708 	res = NULL;
709 	error = getaddrinfo(remotehost, "0", &hints, &res);
710 	if (error)
711 		return (NULL);
712 
713 	for (r = res; r; r = r->ai_next) {
714 		h2[0] = '\0';
715 		if (getnameinfo(r->ai_addr, r->ai_addrlen, h2, sizeof(h2),
716 		    NULL, 0, niflags) != 0)
717 			continue;
718 		if (strcmp(h1, h2) == 0) {
719 			freeaddrinfo(res);
720 			return (remotehost);
721 		}
722 	}
723 
724 	/*
725 	 * either the DNS adminstrator has made a configuration
726 	 * mistake, or someone has attempted to spoof us
727 	 */
728 	syslog(LOG_NOTICE, "rcmd: address %s not listed for host %s",
729 	    h1, res->ai_canonname ? res->ai_canonname : remotehost);
730 	freeaddrinfo(res);
731 	return (NULL);
732 }
733