Annotation of src/usr.bin/ssh/auth-rhosts.c, Revision 1.3
1.1 deraadt 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"
1.3 ! deraadt 19: RCSID("$Id: auth-rhosts.c,v 1.2 1999/09/28 04:45:35 provos Exp $");
1.1 deraadt 20:
21: #include "packet.h"
22: #include "ssh.h"
23: #include "xmalloc.h"
24: #include "uidswap.h"
25:
26: /* Returns true if the strings are equal, ignoring case (a-z only). */
27:
28: static int casefold_equal(const char *a, const char *b)
29: {
30: unsigned char cha, chb;
31: for (; *a; a++, b++)
32: {
33: cha = *a;
34: chb = *b;
35: if (!chb)
36: return 0;
37: if (cha >= 'a' && cha <= 'z')
38: cha -= 32;
39: if (chb >= 'a' && chb <= 'z')
40: chb -= 32;
41: if (cha != chb)
42: return 0;
43: }
44: return !*b;
45: }
46:
47: /* This function processes an rhosts-style file (.rhosts, .shosts, or
48: /etc/hosts.equiv). This returns true if authentication can be granted
49: based on the file, and returns zero otherwise. */
50:
51: int check_rhosts_file(const char *filename, const char *hostname,
52: const char *ipaddr, const char *client_user,
53: const char *server_user)
54: {
55: FILE *f;
56: char buf[1024]; /* Must not be larger than host, user, dummy below. */
57:
58: /* Open the .rhosts file. */
59: f = fopen(filename, "r");
60: if (!f)
61: return 0; /* Cannot read the .rhosts - deny access. */
62:
63: /* Go through the file, checking every entry. */
64: while (fgets(buf, sizeof(buf), f))
65: {
66: /* All three must be at least as big as buf to avoid overflows. */
67: char hostbuf[1024], userbuf[1024], dummy[1024], *host, *user, *cp;
68: int negated;
69:
70: for (cp = buf; *cp == ' ' || *cp == '\t'; cp++)
71: ;
72: if (*cp == '#' || *cp == '\n' || !*cp)
73: continue;
74:
75: /* NO_PLUS is supported at least on OSF/1. We skip it (we don't ever
76: support the plus syntax). */
77: if (strncmp(cp, "NO_PLUS", 7) == 0)
78: continue;
79:
80: /* This should be safe because each buffer is as big as the whole
81: string, and thus cannot be overwritten. */
82: switch (sscanf(buf, "%s %s %s", hostbuf, userbuf, dummy))
83: {
84: case 0:
85: packet_send_debug("Found empty line in %.100s.", filename);
86: continue; /* Empty line? */
87: case 1:
88: /* Host name only. */
1.3 ! deraadt 89: strlcpy(userbuf, server_user, sizeof(userbuf));
1.1 deraadt 90: break;
91: case 2:
92: /* Got both host and user name. */
93: break;
94: case 3:
95: packet_send_debug("Found garbage in %.100s.", filename);
96: continue; /* Extra garbage */
97: default:
98: continue; /* Weird... */
99: }
100:
101: host = hostbuf;
102: user = userbuf;
103: negated = 0;
104:
105: /* Process negated host names, or positive netgroups. */
106: if (host[0] == '-')
107: {
108: negated = 1;
109: host++;
110: }
111: else
112: if (host[0] == '+')
113: host++;
114:
115: if (user[0] == '-')
116: {
117: negated = 1;
118: user++;
119: }
120: else
121: if (user[0] == '+')
122: user++;
123:
124: /* Check for empty host/user names (particularly '+'). */
125: if (!host[0] || !user[0])
126: {
127: /* We come here if either was '+' or '-'. */
128: packet_send_debug("Ignoring wild host/user names in %.100s.",
129: filename);
130: continue;
131: }
132:
133: #ifdef HAVE_INNETGR
134:
135: /* Verify that host name matches. */
136: if (host[0] == '@')
137: {
138: if (!innetgr(host + 1, hostname, NULL, NULL) &&
139: !innetgr(host + 1, ipaddr, NULL, NULL))
140: continue;
141: }
142: else
143: if (!casefold_equal(host, hostname) && strcmp(host, ipaddr) != 0)
144: continue; /* Different hostname. */
145:
146: /* Verify that user name matches. */
147: if (user[0] == '@')
148: {
149: if (!innetgr(user + 1, NULL, client_user, NULL))
150: continue;
151: }
152: else
153: if (strcmp(user, client_user) != 0)
154: continue; /* Different username. */
155:
156: #else /* HAVE_INNETGR */
157:
158: if (!casefold_equal(host, hostname) && strcmp(host, ipaddr) != 0)
159: continue; /* Different hostname. */
160:
161: if (strcmp(user, client_user) != 0)
162: continue; /* Different username. */
163:
164: #endif /* HAVE_INNETGR */
165:
166: /* Found the user and host. */
167: fclose(f);
168:
169: /* If the entry was negated, deny access. */
170: if (negated)
171: {
172: packet_send_debug("Matched negative entry in %.100s.",
173: filename);
174: return 0;
175: }
176:
177: /* Accept authentication. */
178: return 1;
179: }
180:
181: /* Authentication using this file denied. */
182: fclose(f);
183: return 0;
184: }
185:
186: /* Tries to authenticate the user using the .shosts or .rhosts file.
187: Returns true if authentication succeeds. If ignore_rhosts is
188: true, only /etc/hosts.equiv will be considered (.rhosts and .shosts
189: are ignored). */
190:
191: int auth_rhosts(struct passwd *pw, const char *client_user,
192: int ignore_rhosts, int strict_modes)
193: {
194: char buf[1024];
195: const char *hostname, *ipaddr;
196: int port;
197: struct stat st;
198: static const char *rhosts_files[] = { ".shosts", ".rhosts", NULL };
199: unsigned int rhosts_file_index;
200:
201: /* Quick check: if the user has no .shosts or .rhosts files, return failure
202: immediately without doing costly lookups from name servers. */
203: /* Switch to the user's uid. */
204: temporarily_use_uid(pw->pw_uid);
205: for (rhosts_file_index = 0; rhosts_files[rhosts_file_index];
206: rhosts_file_index++)
207: {
208: /* Check users .rhosts or .shosts. */
1.3 ! deraadt 209: snprintf(buf, sizeof buf, "%.500s/%.100s",
1.1 deraadt 210: pw->pw_dir, rhosts_files[rhosts_file_index]);
211: if (stat(buf, &st) >= 0)
212: break;
213: }
214: /* Switch back to privileged uid. */
215: restore_uid();
216:
217: if (!rhosts_files[rhosts_file_index] && stat("/etc/hosts.equiv", &st) < 0 &&
218: stat(SSH_HOSTS_EQUIV, &st) < 0)
219: return 0; /* The user has no .shosts or .rhosts file and there are no
220: system-wide files. */
221:
222: /* Get the name, address, and port of the remote host. */
223: hostname = get_canonical_hostname();
224: ipaddr = get_remote_ipaddr();
225: port = get_remote_port();
226:
227: /* Check that the connection comes from a privileged port.
228: Rhosts authentication only makes sense for priviledged programs.
229: Of course, if the intruder has root access on his local machine,
230: he can connect from any port. So do not use .rhosts
231: authentication from machines that you do not trust. */
232: if (port >= IPPORT_RESERVED ||
233: port < IPPORT_RESERVED / 2)
234: {
235: log("Connection from %.100s from nonpriviledged port %d",
236: hostname, port);
237: packet_send_debug("Your ssh client is not running as root.");
238: return 0;
239: }
240:
241: /* If not logging in as superuser, try /etc/hosts.equiv and shosts.equiv. */
242: if (pw->pw_uid != 0)
243: {
244: if (check_rhosts_file("/etc/hosts.equiv", hostname, ipaddr, client_user,
245: pw->pw_name))
246: {
247: packet_send_debug("Accepted for %.100s [%.100s] by /etc/hosts.equiv.",
248: hostname, ipaddr);
249: return 1;
250: }
251: if (check_rhosts_file(SSH_HOSTS_EQUIV, hostname, ipaddr, client_user,
252: pw->pw_name))
253: {
254: packet_send_debug("Accepted for %.100s [%.100s] by %.100s.",
255: hostname, ipaddr, SSH_HOSTS_EQUIV);
256: return 1;
257: }
258: }
259:
260: /* Check that the home directory is owned by root or the user, and is not
261: group or world writable. */
262: if (stat(pw->pw_dir, &st) < 0)
263: {
264: log("Rhosts authentication refused for %.100: no home directory %.200s",
265: pw->pw_name, pw->pw_dir);
266: packet_send_debug("Rhosts authentication refused for %.100: no home directory %.200s",
267: pw->pw_name, pw->pw_dir);
268: return 0;
269: }
270: if (strict_modes &&
271: ((st.st_uid != 0 && st.st_uid != pw->pw_uid) ||
272: (st.st_mode & 022) != 0))
273: {
274: log("Rhosts authentication refused for %.100s: bad ownership or modes for home directory.",
275: pw->pw_name);
276: packet_send_debug("Rhosts authentication refused for %.100s: bad ownership or modes for home directory.",
277: pw->pw_name);
278: return 0;
279: }
280:
281: /* Check all .rhosts files (currently .shosts and .rhosts). */
282: /* Temporarily use the user's uid. */
283: temporarily_use_uid(pw->pw_uid);
284: for (rhosts_file_index = 0; rhosts_files[rhosts_file_index];
285: rhosts_file_index++)
286: {
287: /* Check users .rhosts or .shosts. */
1.3 ! deraadt 288: snprintf(buf, sizeof buf, "%.500s/%.100s",
1.1 deraadt 289: pw->pw_dir, rhosts_files[rhosts_file_index]);
290: if (stat(buf, &st) < 0)
291: continue; /* No such file. */
292:
293: /* Make sure that the file is either owned by the user or by root,
294: and make sure it is not writable by anyone but the owner. This is
295: to help avoid novices accidentally allowing access to their account
296: by anyone. */
297: if (strict_modes &&
298: ((st.st_uid != 0 && st.st_uid != pw->pw_uid) ||
299: (st.st_mode & 022) != 0))
300: {
301: log("Rhosts authentication refused for %.100s: bad modes for %.200s",
302: pw->pw_name, buf);
303: packet_send_debug("Bad file modes for %.200s", buf);
304: continue;
305: }
306:
307: /* Check if we have been configured to ignore .rhosts and .shosts
308: files. */
309: if (ignore_rhosts)
310: {
311: packet_send_debug("Server has been configured to ignore %.100s.",
312: rhosts_files[rhosts_file_index]);
313: continue;
314: }
315:
316: /* Check if authentication is permitted by the file. */
317: if (check_rhosts_file(buf, hostname, ipaddr, client_user, pw->pw_name))
318: {
319: packet_send_debug("Accepted by %.100s.",
320: rhosts_files[rhosts_file_index]);
321: /* Restore the privileged uid. */
322: restore_uid();
323: return 1;
324: }
325: }
326:
327: /* Rhosts authentication denied. */
328: /* Restore the privileged uid. */
329: restore_uid();
330: return 0;
331: }