xref: /freebsd/crypto/openssh/auth-rhosts.c (revision 23f282aa31e9b6fceacd449020e936e98d6f2298)
1 /*
2  *
3  * auth-rhosts.c
4  *
5  * Author: Tatu Ylonen <ylo@cs.hut.fi>
6  *
7  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
8  *                    All rights reserved
9  *
10  * Created: Fri Mar 17 05:12:18 1995 ylo
11  *
12  * Rhosts authentication.  This file contains code to check whether to admit
13  * the login based on rhosts authentication.  This file also processes
14  * /etc/hosts.equiv.
15  *
16  */
17 
18 #include "includes.h"
19 RCSID("$Id: auth-rhosts.c,v 1.12 1999/12/27 10:46:11 markus Exp $");
20 
21 #include "packet.h"
22 #include "ssh.h"
23 #include "xmalloc.h"
24 #include "uidswap.h"
25 #include "servconf.h"
26 
27 /*
28  * This function processes an rhosts-style file (.rhosts, .shosts, or
29  * /etc/hosts.equiv).  This returns true if authentication can be granted
30  * based on the file, and returns zero otherwise.
31  */
32 
33 int
34 check_rhosts_file(const char *filename, const char *hostname,
35 		  const char *ipaddr, const char *client_user,
36 		  const char *server_user)
37 {
38 	FILE *f;
39 	char buf[1024];	/* Must not be larger than host, user, dummy below. */
40 
41 	/* Open the .rhosts file, deny if unreadable */
42 	f = fopen(filename, "r");
43 	if (!f)
44 		return 0;
45 
46 	while (fgets(buf, sizeof(buf), f)) {
47 		/* All three must be at least as big as buf to avoid overflows. */
48 		char hostbuf[1024], userbuf[1024], dummy[1024], *host, *user, *cp;
49 		int negated;
50 
51 		for (cp = buf; *cp == ' ' || *cp == '\t'; cp++)
52 			;
53 		if (*cp == '#' || *cp == '\n' || !*cp)
54 			continue;
55 
56 		/*
57 		 * NO_PLUS is supported at least on OSF/1.  We skip it (we
58 		 * don't ever support the plus syntax).
59 		 */
60 		if (strncmp(cp, "NO_PLUS", 7) == 0)
61 			continue;
62 
63 		/*
64 		 * This should be safe because each buffer is as big as the
65 		 * whole string, and thus cannot be overwritten.
66 		 */
67 		switch (sscanf(buf, "%s %s %s", hostbuf, userbuf, dummy)) {
68 		case 0:
69 			packet_send_debug("Found empty line in %.100s.", filename);
70 			continue;
71 		case 1:
72 			/* Host name only. */
73 			strlcpy(userbuf, server_user, sizeof(userbuf));
74 			break;
75 		case 2:
76 			/* Got both host and user name. */
77 			break;
78 		case 3:
79 			packet_send_debug("Found garbage in %.100s.", filename);
80 			continue;
81 		default:
82 			/* Weird... */
83 			continue;
84 		}
85 
86 		host = hostbuf;
87 		user = userbuf;
88 		negated = 0;
89 
90 		/* Process negated host names, or positive netgroups. */
91 		if (host[0] == '-') {
92 			negated = 1;
93 			host++;
94 		} else if (host[0] == '+')
95 			host++;
96 
97 		if (user[0] == '-') {
98 			negated = 1;
99 			user++;
100 		} else if (user[0] == '+')
101 			user++;
102 
103 		/* Check for empty host/user names (particularly '+'). */
104 		if (!host[0] || !user[0]) {
105 			/* We come here if either was '+' or '-'. */
106 			packet_send_debug("Ignoring wild host/user names in %.100s.",
107 					  filename);
108 			continue;
109 		}
110 		/* Verify that host name matches. */
111 		if (host[0] == '@') {
112 			if (!innetgr(host + 1, hostname, NULL, NULL) &&
113 			    !innetgr(host + 1, ipaddr, NULL, NULL))
114 				continue;
115 		} else if (strcasecmp(host, hostname) && strcmp(host, ipaddr) != 0)
116 			continue;	/* Different hostname. */
117 
118 		/* Verify that user name matches. */
119 		if (user[0] == '@') {
120 			if (!innetgr(user + 1, NULL, client_user, NULL))
121 				continue;
122 		} else if (strcmp(user, client_user) != 0)
123 			continue;	/* Different username. */
124 
125 		/* Found the user and host. */
126 		fclose(f);
127 
128 		/* If the entry was negated, deny access. */
129 		if (negated) {
130 			packet_send_debug("Matched negative entry in %.100s.",
131 					  filename);
132 			return 0;
133 		}
134 		/* Accept authentication. */
135 		return 1;
136 	}
137 
138 	/* Authentication using this file denied. */
139 	fclose(f);
140 	return 0;
141 }
142 
143 /*
144  * Tries to authenticate the user using the .shosts or .rhosts file. Returns
145  * true if authentication succeeds.  If ignore_rhosts is true, only
146  * /etc/hosts.equiv will be considered (.rhosts and .shosts are ignored).
147  */
148 
149 int
150 auth_rhosts(struct passwd *pw, const char *client_user)
151 {
152 	extern ServerOptions options;
153 	char buf[1024];
154 	const char *hostname, *ipaddr;
155 	struct stat st;
156 	static const char *rhosts_files[] = {".shosts", ".rhosts", NULL};
157 	unsigned int rhosts_file_index;
158 
159 	/* Switch to the user's uid. */
160 	temporarily_use_uid(pw->pw_uid);
161 	/*
162 	 * Quick check: if the user has no .shosts or .rhosts files, return
163 	 * failure immediately without doing costly lookups from name
164 	 * servers.
165 	 */
166 	for (rhosts_file_index = 0; rhosts_files[rhosts_file_index];
167 	     rhosts_file_index++) {
168 		/* Check users .rhosts or .shosts. */
169 		snprintf(buf, sizeof buf, "%.500s/%.100s",
170 			 pw->pw_dir, rhosts_files[rhosts_file_index]);
171 		if (stat(buf, &st) >= 0)
172 			break;
173 	}
174 	/* Switch back to privileged uid. */
175 	restore_uid();
176 
177 	/* Deny if The user has no .shosts or .rhosts file and there are no system-wide files. */
178 	if (!rhosts_files[rhosts_file_index] &&
179 	    stat("/etc/hosts.equiv", &st) < 0 &&
180 	    stat(SSH_HOSTS_EQUIV, &st) < 0)
181 		return 0;
182 
183 	hostname = get_canonical_hostname();
184 	ipaddr = get_remote_ipaddr();
185 
186 	/* If not logging in as superuser, try /etc/hosts.equiv and shosts.equiv. */
187 	if (pw->pw_uid != 0) {
188 		if (check_rhosts_file("/etc/hosts.equiv", hostname, ipaddr, client_user,
189 				      pw->pw_name)) {
190 			packet_send_debug("Accepted for %.100s [%.100s] by /etc/hosts.equiv.",
191 					  hostname, ipaddr);
192 			return 1;
193 		}
194 		if (check_rhosts_file(SSH_HOSTS_EQUIV, hostname, ipaddr, client_user,
195 				      pw->pw_name)) {
196 			packet_send_debug("Accepted for %.100s [%.100s] by %.100s.",
197 				      hostname, ipaddr, SSH_HOSTS_EQUIV);
198 			return 1;
199 		}
200 	}
201 	/*
202 	 * Check that the home directory is owned by root or the user, and is
203 	 * not group or world writable.
204 	 */
205 	if (stat(pw->pw_dir, &st) < 0) {
206 		log("Rhosts authentication refused for %.100s: no home directory %.200s",
207 		    pw->pw_name, pw->pw_dir);
208 		packet_send_debug("Rhosts authentication refused for %.100s: no home directory %.200s",
209 				  pw->pw_name, pw->pw_dir);
210 		return 0;
211 	}
212 	if (options.strict_modes &&
213 	    ((st.st_uid != 0 && st.st_uid != pw->pw_uid) ||
214 	     (st.st_mode & 022) != 0)) {
215 		log("Rhosts authentication refused for %.100s: bad ownership or modes for home directory.",
216 		    pw->pw_name);
217 		packet_send_debug("Rhosts authentication refused for %.100s: bad ownership or modes for home directory.",
218 				  pw->pw_name);
219 		return 0;
220 	}
221 	/* Temporarily use the user's uid. */
222 	temporarily_use_uid(pw->pw_uid);
223 
224 	/* Check all .rhosts files (currently .shosts and .rhosts). */
225 	for (rhosts_file_index = 0; rhosts_files[rhosts_file_index];
226 	     rhosts_file_index++) {
227 		/* Check users .rhosts or .shosts. */
228 		snprintf(buf, sizeof buf, "%.500s/%.100s",
229 			 pw->pw_dir, rhosts_files[rhosts_file_index]);
230 		if (stat(buf, &st) < 0)
231 			continue;
232 
233 		/*
234 		 * Make sure that the file is either owned by the user or by
235 		 * root, and make sure it is not writable by anyone but the
236 		 * owner.  This is to help avoid novices accidentally
237 		 * allowing access to their account by anyone.
238 		 */
239 		if (options.strict_modes &&
240 		    ((st.st_uid != 0 && st.st_uid != pw->pw_uid) ||
241 		     (st.st_mode & 022) != 0)) {
242 			log("Rhosts authentication refused for %.100s: bad modes for %.200s",
243 			    pw->pw_name, buf);
244 			packet_send_debug("Bad file modes for %.200s", buf);
245 			continue;
246 		}
247 		/* Check if we have been configured to ignore .rhosts and .shosts files. */
248 		if (options.ignore_rhosts) {
249 			packet_send_debug("Server has been configured to ignore %.100s.",
250 					  rhosts_files[rhosts_file_index]);
251 			continue;
252 		}
253 		/* Check if authentication is permitted by the file. */
254 		if (check_rhosts_file(buf, hostname, ipaddr, client_user, pw->pw_name)) {
255 			packet_send_debug("Accepted by %.100s.",
256 					  rhosts_files[rhosts_file_index]);
257 			/* Restore the privileged uid. */
258 			restore_uid();
259 			return 1;
260 		}
261 	}
262 
263 	/* Restore the privileged uid. */
264 	restore_uid();
265 	return 0;
266 }
267