Annotation of src/usr.bin/ssh/auth-rhosts.c, Revision 1.42
1.42 ! djm 1: /* $OpenBSD: auth-rhosts.c,v 1.41 2006/08/03 03:34:41 deraadt Exp $ */
1.1 deraadt 2: /*
1.10 deraadt 3: * Author: Tatu Ylonen <ylo@cs.hut.fi>
4: * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
5: * All rights reserved
6: * Rhosts authentication. This file contains code to check whether to admit
7: * the login based on rhosts authentication. This file also processes
8: * /etc/hosts.equiv.
1.13 markus 9: *
1.15 deraadt 10: * As far as I am concerned, the code I have written for this software
11: * can be used freely for any purpose. Any derived versions of this
12: * software must be clearly marked as such, and if the derived work is
13: * incompatible with the protocol description in the RFC file, it must be
14: * called by a name other than "ssh" or "Secure Shell".
1.10 deraadt 15: */
1.1 deraadt 16:
1.35 stevesk 17: #include <sys/types.h>
18: #include <sys/stat.h>
1.34 stevesk 19:
1.42 ! djm 20: #include <fcntl.h>
1.34 stevesk 21: #include <netgroup.h>
1.38 stevesk 22: #include <pwd.h>
1.40 stevesk 23: #include <stdio.h>
1.39 stevesk 24: #include <string.h>
1.41 deraadt 25: #include <stdarg.h>
1.1 deraadt 26:
27: #include "packet.h"
1.41 deraadt 28: #include "buffer.h"
1.1 deraadt 29: #include "uidswap.h"
1.19 markus 30: #include "pathnames.h"
31: #include "log.h"
1.6 markus 32: #include "servconf.h"
1.19 markus 33: #include "canohost.h"
1.41 deraadt 34: #include "key.h"
35: #include "hostfile.h"
1.21 itojun 36: #include "auth.h"
1.42 ! djm 37: #include "misc.h"
1.1 deraadt 38:
1.23 markus 39: /* import */
40: extern ServerOptions options;
1.28 markus 41: extern int use_privsep;
1.23 markus 42:
1.11 markus 43: /*
44: * This function processes an rhosts-style file (.rhosts, .shosts, or
45: * /etc/hosts.equiv). This returns true if authentication can be granted
46: * based on the file, and returns zero otherwise.
47: */
1.1 deraadt 48:
1.24 itojun 49: static int
1.9 markus 50: check_rhosts_file(const char *filename, const char *hostname,
51: const char *ipaddr, const char *client_user,
52: const char *server_user)
1.1 deraadt 53: {
1.9 markus 54: FILE *f;
55: char buf[1024]; /* Must not be larger than host, user, dummy below. */
1.42 ! djm 56: int fd;
! 57: struct stat st;
1.1 deraadt 58:
1.9 markus 59: /* Open the .rhosts file, deny if unreadable */
1.42 ! djm 60: if ((fd = open(filename, O_RDONLY|O_NONBLOCK)) == -1)
1.9 markus 61: return 0;
1.42 ! djm 62: if (fstat(fd, &st) == -1) {
! 63: close(fd);
! 64: return 0;
! 65: }
! 66: if (!S_ISREG(st.st_mode)) {
! 67: logit("User %s hosts file %s is not a regular file",
! 68: server_user, filename);
! 69: close(fd);
! 70: return 0;
! 71: }
! 72: unset_nonblock(fd);
! 73: if ((f = fdopen(fd, "r")) == NULL) {
! 74: close(fd);
! 75: return 0;
! 76: }
1.9 markus 77: while (fgets(buf, sizeof(buf), f)) {
78: /* All three must be at least as big as buf to avoid overflows. */
79: char hostbuf[1024], userbuf[1024], dummy[1024], *host, *user, *cp;
80: int negated;
81:
82: for (cp = buf; *cp == ' ' || *cp == '\t'; cp++)
83: ;
84: if (*cp == '#' || *cp == '\n' || !*cp)
85: continue;
86:
1.11 markus 87: /*
88: * NO_PLUS is supported at least on OSF/1. We skip it (we
89: * don't ever support the plus syntax).
90: */
1.9 markus 91: if (strncmp(cp, "NO_PLUS", 7) == 0)
92: continue;
93:
1.11 markus 94: /*
95: * This should be safe because each buffer is as big as the
96: * whole string, and thus cannot be overwritten.
97: */
1.30 itojun 98: switch (sscanf(buf, "%1023s %1023s %1023s", hostbuf, userbuf,
99: dummy)) {
1.9 markus 100: case 0:
1.28 markus 101: auth_debug_add("Found empty line in %.100s.", filename);
1.9 markus 102: continue;
103: case 1:
104: /* Host name only. */
105: strlcpy(userbuf, server_user, sizeof(userbuf));
106: break;
107: case 2:
108: /* Got both host and user name. */
109: break;
110: case 3:
1.28 markus 111: auth_debug_add("Found garbage in %.100s.", filename);
1.9 markus 112: continue;
113: default:
114: /* Weird... */
115: continue;
116: }
117:
118: host = hostbuf;
119: user = userbuf;
120: negated = 0;
121:
122: /* Process negated host names, or positive netgroups. */
123: if (host[0] == '-') {
124: negated = 1;
125: host++;
126: } else if (host[0] == '+')
127: host++;
128:
129: if (user[0] == '-') {
130: negated = 1;
131: user++;
132: } else if (user[0] == '+')
133: user++;
134:
135: /* Check for empty host/user names (particularly '+'). */
136: if (!host[0] || !user[0]) {
137: /* We come here if either was '+' or '-'. */
1.28 markus 138: auth_debug_add("Ignoring wild host/user names in %.100s.",
139: filename);
1.9 markus 140: continue;
141: }
142: /* Verify that host name matches. */
143: if (host[0] == '@') {
144: if (!innetgr(host + 1, hostname, NULL, NULL) &&
145: !innetgr(host + 1, ipaddr, NULL, NULL))
146: continue;
147: } else if (strcasecmp(host, hostname) && strcmp(host, ipaddr) != 0)
148: continue; /* Different hostname. */
149:
150: /* Verify that user name matches. */
151: if (user[0] == '@') {
152: if (!innetgr(user + 1, NULL, client_user, NULL))
153: continue;
154: } else if (strcmp(user, client_user) != 0)
155: continue; /* Different username. */
156:
157: /* Found the user and host. */
158: fclose(f);
159:
160: /* If the entry was negated, deny access. */
161: if (negated) {
1.28 markus 162: auth_debug_add("Matched negative entry in %.100s.",
1.33 djm 163: filename);
1.9 markus 164: return 0;
165: }
166: /* Accept authentication. */
167: return 1;
168: }
169:
170: /* Authentication using this file denied. */
171: fclose(f);
172: return 0;
1.1 deraadt 173: }
174:
1.11 markus 175: /*
176: * Tries to authenticate the user using the .shosts or .rhosts file. Returns
177: * true if authentication succeeds. If ignore_rhosts is true, only
178: * /etc/hosts.equiv will be considered (.rhosts and .shosts are ignored).
179: */
1.1 deraadt 180:
1.13 markus 181: int
1.9 markus 182: auth_rhosts(struct passwd *pw, const char *client_user)
1.1 deraadt 183: {
1.23 markus 184: const char *hostname, *ipaddr;
185:
1.31 markus 186: hostname = get_canonical_hostname(options.use_dns);
1.23 markus 187: ipaddr = get_remote_ipaddr();
1.28 markus 188: return auth_rhosts2(pw, client_user, hostname, ipaddr);
1.23 markus 189: }
190:
1.28 markus 191: static int
192: auth_rhosts2_raw(struct passwd *pw, const char *client_user, const char *hostname,
1.23 markus 193: const char *ipaddr)
194: {
1.9 markus 195: char buf[1024];
196: struct stat st;
197: static const char *rhosts_files[] = {".shosts", ".rhosts", NULL};
1.17 markus 198: u_int rhosts_file_index;
1.9 markus 199:
1.23 markus 200: debug2("auth_rhosts2: clientuser %s hostname %s ipaddr %s",
201: client_user, hostname, ipaddr);
202:
1.9 markus 203: /* Switch to the user's uid. */
1.22 markus 204: temporarily_use_uid(pw);
1.11 markus 205: /*
206: * Quick check: if the user has no .shosts or .rhosts files, return
207: * failure immediately without doing costly lookups from name
208: * servers.
209: */
1.9 markus 210: for (rhosts_file_index = 0; rhosts_files[rhosts_file_index];
1.25 deraadt 211: rhosts_file_index++) {
1.9 markus 212: /* Check users .rhosts or .shosts. */
213: snprintf(buf, sizeof buf, "%.500s/%.100s",
214: pw->pw_dir, rhosts_files[rhosts_file_index]);
215: if (stat(buf, &st) >= 0)
216: break;
217: }
218: /* Switch back to privileged uid. */
219: restore_uid();
220:
221: /* Deny if The user has no .shosts or .rhosts file and there are no system-wide files. */
222: if (!rhosts_files[rhosts_file_index] &&
1.18 markus 223: stat(_PATH_RHOSTS_EQUIV, &st) < 0 &&
224: stat(_PATH_SSH_HOSTS_EQUIV, &st) < 0)
1.9 markus 225: return 0;
226:
227: /* If not logging in as superuser, try /etc/hosts.equiv and shosts.equiv. */
228: if (pw->pw_uid != 0) {
1.25 deraadt 229: if (check_rhosts_file(_PATH_RHOSTS_EQUIV, hostname, ipaddr,
230: client_user, pw->pw_name)) {
1.28 markus 231: auth_debug_add("Accepted for %.100s [%.100s] by /etc/hosts.equiv.",
1.25 deraadt 232: hostname, ipaddr);
1.9 markus 233: return 1;
234: }
1.25 deraadt 235: if (check_rhosts_file(_PATH_SSH_HOSTS_EQUIV, hostname, ipaddr,
236: client_user, pw->pw_name)) {
1.28 markus 237: auth_debug_add("Accepted for %.100s [%.100s] by %.100s.",
1.25 deraadt 238: hostname, ipaddr, _PATH_SSH_HOSTS_EQUIV);
1.9 markus 239: return 1;
240: }
241: }
1.11 markus 242: /*
243: * Check that the home directory is owned by root or the user, and is
244: * not group or world writable.
245: */
1.9 markus 246: if (stat(pw->pw_dir, &st) < 0) {
1.29 itojun 247: logit("Rhosts authentication refused for %.100s: "
1.28 markus 248: "no home directory %.200s", pw->pw_name, pw->pw_dir);
249: auth_debug_add("Rhosts authentication refused for %.100s: "
250: "no home directory %.200s", pw->pw_name, pw->pw_dir);
1.9 markus 251: return 0;
252: }
253: if (options.strict_modes &&
254: ((st.st_uid != 0 && st.st_uid != pw->pw_uid) ||
1.25 deraadt 255: (st.st_mode & 022) != 0)) {
1.29 itojun 256: logit("Rhosts authentication refused for %.100s: "
1.28 markus 257: "bad ownership or modes for home directory.", pw->pw_name);
258: auth_debug_add("Rhosts authentication refused for %.100s: "
259: "bad ownership or modes for home directory.", pw->pw_name);
1.9 markus 260: return 0;
261: }
262: /* Temporarily use the user's uid. */
1.22 markus 263: temporarily_use_uid(pw);
1.9 markus 264:
265: /* Check all .rhosts files (currently .shosts and .rhosts). */
266: for (rhosts_file_index = 0; rhosts_files[rhosts_file_index];
1.25 deraadt 267: rhosts_file_index++) {
1.9 markus 268: /* Check users .rhosts or .shosts. */
269: snprintf(buf, sizeof buf, "%.500s/%.100s",
270: pw->pw_dir, rhosts_files[rhosts_file_index]);
271: if (stat(buf, &st) < 0)
272: continue;
273:
1.11 markus 274: /*
275: * Make sure that the file is either owned by the user or by
276: * root, and make sure it is not writable by anyone but the
277: * owner. This is to help avoid novices accidentally
278: * allowing access to their account by anyone.
279: */
1.9 markus 280: if (options.strict_modes &&
281: ((st.st_uid != 0 && st.st_uid != pw->pw_uid) ||
1.25 deraadt 282: (st.st_mode & 022) != 0)) {
1.29 itojun 283: logit("Rhosts authentication refused for %.100s: bad modes for %.200s",
1.9 markus 284: pw->pw_name, buf);
1.28 markus 285: auth_debug_add("Bad file modes for %.200s", buf);
1.9 markus 286: continue;
287: }
288: /* Check if we have been configured to ignore .rhosts and .shosts files. */
289: if (options.ignore_rhosts) {
1.28 markus 290: auth_debug_add("Server has been configured to ignore %.100s.",
291: rhosts_files[rhosts_file_index]);
1.9 markus 292: continue;
293: }
294: /* Check if authentication is permitted by the file. */
295: if (check_rhosts_file(buf, hostname, ipaddr, client_user, pw->pw_name)) {
1.28 markus 296: auth_debug_add("Accepted by %.100s.",
297: rhosts_files[rhosts_file_index]);
1.9 markus 298: /* Restore the privileged uid. */
299: restore_uid();
1.28 markus 300: auth_debug_add("Accepted host %s ip %s client_user %s server_user %s",
301: hostname, ipaddr, client_user, pw->pw_name);
1.9 markus 302: return 1;
303: }
304: }
305:
306: /* Restore the privileged uid. */
307: restore_uid();
308: return 0;
1.28 markus 309: }
310:
311: int
312: auth_rhosts2(struct passwd *pw, const char *client_user, const char *hostname,
313: const char *ipaddr)
314: {
315: int ret;
316:
317: auth_debug_reset();
318: ret = auth_rhosts2_raw(pw, client_user, hostname, ipaddr);
319: if (!use_privsep)
320: auth_debug_send();
321: return ret;
1.1 deraadt 322: }