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. Neither the name of the University nor the names of its contributors
15 *    may be used to endorse or promote products derived from this software
16 *    without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28 * SUCH DAMAGE.
29 */
30
31#include <sys/socket.h>
32#include <sys/stat.h>
33
34#include <netinet/in.h>
35#include <arpa/inet.h>
36
37#include <ctype.h>
38#include <errno.h>
39#include <fcntl.h>
40#include <limits.h>
41#include <netdb.h>
42#include <netgroup.h>
43#include <pwd.h>
44#include <signal.h>
45#include <stdio.h>
46#include <stdlib.h>
47#include <string.h>
48#include <syslog.h>
49#include <unistd.h>
50
51static int __ivaliduser_sa(FILE *, struct sockaddr *, socklen_t,
52	    const char *, const char *);
53static int __icheckhost(struct sockaddr *, socklen_t, const char *);
54static char *__gethostloop(struct sockaddr *, socklen_t);
55static int iruserok_sa(const void *, int, int, const char *, const char *);
56
57int
58ruserok(const char *rhost, int superuser, const char *ruser, const char *luser)
59{
60	struct addrinfo hints, *res, *r;
61	int error;
62
63	memset(&hints, 0, sizeof(hints));
64	hints.ai_family = PF_UNSPEC;
65	hints.ai_socktype = SOCK_DGRAM;	/*dummy*/
66	error = getaddrinfo(rhost, "0", &hints, &res);
67	if (error)
68		return (-1);
69
70	for (r = res; r; r = r->ai_next) {
71		if (iruserok_sa(r->ai_addr, r->ai_addrlen, superuser, ruser,
72		    luser) == 0) {
73			freeaddrinfo(res);
74			return (0);
75		}
76	}
77	freeaddrinfo(res);
78	return (-1);
79}
80
81int
82iruserok_sa(const void *raddr, int rlen, int superuser, const char *ruser,
83    const char *luser)
84{
85	struct sockaddr *sa;
86	char *cp;
87	struct stat sbuf;
88	struct passwd pwstore, *pwd;
89	FILE *hostf;
90	uid_t uid;
91	int first;
92	char pbuf[PATH_MAX], pwbuf[_PW_BUF_LEN];
93
94	sa = (struct sockaddr *)raddr;
95	first = 1;
96	hostf = superuser ? NULL : fopen(_PATH_HEQUIV, "re");
97again:
98	if (hostf) {
99		if (__ivaliduser_sa(hostf, sa, rlen, luser, ruser) == 0) {
100			(void)fclose(hostf);
101			return (0);
102		}
103		(void)fclose(hostf);
104	}
105	if (first == 1) {
106		int len;
107
108		first = 0;
109		pwd = NULL;
110		getpwnam_r(luser, &pwstore, pwbuf, sizeof(pwbuf), &pwd);
111		if (pwd == NULL)
112			return (-1);
113		len = snprintf(pbuf, sizeof pbuf, "%s/.rhosts", pwd->pw_dir);
114		if (len < 0 || len >= sizeof pbuf)
115			return (-1);
116
117		/*
118		 * Change effective uid while opening .rhosts.  If root and
119		 * reading an NFS mounted file system, can't read files that
120		 * are protected read/write owner only.
121		 */
122		uid = geteuid();
123		(void)seteuid(pwd->pw_uid);
124		hostf = fopen(pbuf, "re");
125		(void)seteuid(uid);
126
127		if (hostf == NULL)
128			return (-1);
129		/*
130		 * If not a regular file, or is owned by someone other than
131		 * user or root or if writeable by anyone but the owner, quit.
132		 */
133		cp = NULL;
134		if (lstat(pbuf, &sbuf) == -1)
135			cp = ".rhosts lstat failed";
136		else if (!S_ISREG(sbuf.st_mode))
137			cp = ".rhosts not regular file";
138		else if (fstat(fileno(hostf), &sbuf) == -1)
139			cp = ".rhosts fstat failed";
140		else if (sbuf.st_uid && sbuf.st_uid != pwd->pw_uid)
141			cp = "bad .rhosts owner";
142		else if (sbuf.st_mode & (S_IWGRP|S_IWOTH))
143			cp = ".rhosts writable by other than owner";
144		/* If there were any problems, quit. */
145		if (cp) {
146			(void)fclose(hostf);
147			return (-1);
148		}
149		goto again;
150	}
151	return (-1);
152}
153
154int
155__ivaliduser_sa(FILE *hostf, struct sockaddr *raddr, socklen_t salen,
156    const char *luser, const char *ruser)
157{
158	char *user, *p;
159	char *buf;
160	const char *auser, *ahost;
161	int hostok, userok;
162	char *rhost = (char *)-1;
163	char domain[HOST_NAME_MAX+1];
164	size_t buflen;
165
166	getdomainname(domain, sizeof(domain));
167
168	while ((buf = fgetln(hostf, &buflen))) {
169		p = buf;
170		if (*p == '#')
171			continue;
172		while (p < buf + buflen && *p != '\n' && *p != ' ' && *p != '\t') {
173			if (!isprint((unsigned char)*p))
174				goto bail;
175			*p = isupper((unsigned char)*p) ?
176			    tolower((unsigned char)*p) : *p;
177			p++;
178		}
179		if (p >= buf + buflen)
180			continue;
181		if (*p == ' ' || *p == '\t') {
182			*p++ = '\0';
183			while (p < buf + buflen && (*p == ' ' || *p == '\t'))
184				p++;
185			if (p >= buf + buflen)
186				continue;
187			user = p;
188			while (p < buf + buflen && *p != '\n' && *p != ' ' &&
189			    *p != '\t') {
190				if (!isprint((unsigned char)*p))
191					goto bail;
192				p++;
193			}
194		} else
195			user = p;
196		*p = '\0';
197
198		if (p == buf)
199			continue;
200
201		auser = *user ? user : luser;
202		ahost = buf;
203
204		if (strlen(ahost) > HOST_NAME_MAX)
205			continue;
206
207		/*
208		 * innetgr() must lookup a hostname (we do not attempt
209		 * to change the semantics so that netgroups may have
210		 * #.#.#.# addresses in the list.)
211		 */
212		if (ahost[0] == '+')
213			switch (ahost[1]) {
214			case '\0':
215				hostok = 1;
216				break;
217			case '@':
218				if (rhost == (char *)-1)
219					rhost = __gethostloop(raddr, salen);
220				hostok = 0;
221				if (rhost)
222					hostok = innetgr(&ahost[2], rhost,
223					    NULL, domain);
224				break;
225			default:
226				hostok = __icheckhost(raddr, salen, &ahost[1]);
227				break;
228			}
229		else if (ahost[0] == '-')
230			switch (ahost[1]) {
231			case '\0':
232				hostok = -1;
233				break;
234			case '@':
235				if (rhost == (char *)-1)
236					rhost = __gethostloop(raddr, salen);
237				hostok = 0;
238				if (rhost)
239					hostok = -innetgr(&ahost[2], rhost,
240					    NULL, domain);
241				break;
242			default:
243				hostok = -__icheckhost(raddr, salen, &ahost[1]);
244				break;
245			}
246		else
247			hostok = __icheckhost(raddr, salen, ahost);
248
249
250		if (auser[0] == '+')
251			switch (auser[1]) {
252			case '\0':
253				userok = 1;
254				break;
255			case '@':
256				userok = innetgr(&auser[2], NULL, ruser,
257				    domain);
258				break;
259			default:
260				userok = strcmp(ruser, &auser[1]) ? 0 : 1;
261				break;
262			}
263		else if (auser[0] == '-')
264			switch (auser[1]) {
265			case '\0':
266				userok = -1;
267				break;
268			case '@':
269				userok = -innetgr(&auser[2], NULL, ruser,
270				    domain);
271				break;
272			default:
273				userok = strcmp(ruser, &auser[1]) ? 0 : -1;
274				break;
275			}
276		else
277			userok = strcmp(ruser, auser) ? 0 : 1;
278
279		/* Check if one component did not match */
280		if (hostok == 0 || userok == 0)
281			continue;
282
283		/* Check if we got a forbidden pair */
284		if (userok <= -1 || hostok <= -1)
285			return (-1);
286
287		/* Check if we got a valid pair */
288		if (hostok >= 1 && userok >= 1)
289			return (0);
290	}
291bail:
292	return (-1);
293}
294
295/*
296 * Returns "true" if match, 0 if no match.  If we do not find any
297 * semblance of an A->PTR->A loop, allow a simple #.#.#.# match to work.
298 */
299static int
300__icheckhost(struct sockaddr *raddr, socklen_t salen, const char *lhost)
301{
302	struct addrinfo hints, *res, *r;
303	char h1[NI_MAXHOST], h2[NI_MAXHOST];
304	int error;
305	const int niflags = NI_NUMERICHOST;
306
307	h1[0] = '\0';
308	if (getnameinfo(raddr, salen, h1, sizeof(h1), NULL, 0,
309	    niflags) != 0)
310		return (0);
311
312	/* Resolve laddr into sockaddr */
313	memset(&hints, 0, sizeof(hints));
314	hints.ai_family = raddr->sa_family;
315	hints.ai_socktype = SOCK_DGRAM;	/*dummy*/
316	res = NULL;
317	error = getaddrinfo(lhost, "0", &hints, &res);
318	if (error)
319		return (0);
320
321	/*
322	 * Try string comparisons between raddr and laddr.
323	 */
324	for (r = res; r; r = r->ai_next) {
325		h2[0] = '\0';
326		if (getnameinfo(r->ai_addr, r->ai_addrlen, h2, sizeof(h2),
327		    NULL, 0, niflags) != 0)
328			continue;
329		if (strcmp(h1, h2) == 0) {
330			freeaddrinfo(res);
331			return (1);
332		}
333	}
334
335	/* No match. */
336	freeaddrinfo(res);
337	return (0);
338}
339
340/*
341 * Return the hostname associated with the supplied address.
342 * Do a reverse lookup as well for security. If a loop cannot
343 * be found, pack the result of inet_ntoa() into the string.
344 */
345static char *
346__gethostloop(struct sockaddr *raddr, socklen_t salen)
347{
348	static char remotehost[NI_MAXHOST];
349	char h1[NI_MAXHOST], h2[NI_MAXHOST];
350	struct addrinfo hints, *res, *r;
351	int error;
352	const int niflags = NI_NUMERICHOST;
353
354	h1[0] = remotehost[0] = '\0';
355	if (getnameinfo(raddr, salen, remotehost, sizeof(remotehost),
356	    NULL, 0, NI_NAMEREQD) != 0)
357		return (NULL);
358	if (getnameinfo(raddr, salen, h1, sizeof(h1), NULL, 0,
359	    niflags) != 0)
360		return (NULL);
361
362	/*
363	 * Look up the name and check that the supplied
364	 * address is in the list
365	 */
366	memset(&hints, 0, sizeof(hints));
367	hints.ai_family = raddr->sa_family;
368	hints.ai_socktype = SOCK_DGRAM;	/*dummy*/
369	hints.ai_flags = AI_CANONNAME;
370	res = NULL;
371	error = getaddrinfo(remotehost, "0", &hints, &res);
372	if (error)
373		return (NULL);
374
375	for (r = res; r; r = r->ai_next) {
376		h2[0] = '\0';
377		if (getnameinfo(r->ai_addr, r->ai_addrlen, h2, sizeof(h2),
378		    NULL, 0, niflags) != 0)
379			continue;
380		if (strcmp(h1, h2) == 0) {
381			freeaddrinfo(res);
382			return (remotehost);
383		}
384	}
385
386	/*
387	 * either the DNS administrator has made a configuration
388	 * mistake, or someone has attempted to spoof us
389	 */
390	freeaddrinfo(res);
391	return (NULL);
392}
393