]>
Commit | Line | Data |
---|---|---|
a6183e25 | 1 | /* $OpenBSD: auth-rhosts.c,v 1.57 2022/12/09 00:17:40 dtucker Exp $ */ |
d4a8b7e3 | 2 | /* |
95def098 | 3 | * Author: Tatu Ylonen <ylo@cs.hut.fi> |
95def098 DM |
4 | * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland |
5 | * All rights reserved | |
95def098 DM |
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. | |
4af51306 | 9 | * |
e4340be5 DM |
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". | |
95def098 | 15 | */ |
d4a8b7e3 DM |
16 | |
17 | #include "includes.h" | |
f17883e6 DM |
18 | |
19 | #include <sys/types.h> | |
20 | #include <sys/stat.h> | |
015cd79a | 21 | |
a6183e25 | 22 | #include <errno.h> |
3383b2ca | 23 | #include <fcntl.h> |
015cd79a DM |
24 | #ifdef HAVE_NETGROUP_H |
25 | # include <netgroup.h> | |
26 | #endif | |
9f2abc47 | 27 | #include <pwd.h> |
a7a73ee3 | 28 | #include <stdio.h> |
e3476ed0 | 29 | #include <string.h> |
d7834353 | 30 | #include <stdarg.h> |
3383b2ca | 31 | #include <stdlib.h> |
d9526a5e | 32 | #include <unistd.h> |
d4a8b7e3 DM |
33 | |
34 | #include "packet.h" | |
d4a8b7e3 | 35 | #include "uidswap.h" |
226cfa03 BL |
36 | #include "pathnames.h" |
37 | #include "log.h" | |
7acefbbc | 38 | #include "misc.h" |
bf114d6f | 39 | #include "xmalloc.h" |
c7d39ac8 | 40 | #include "sshbuf.h" |
41 | #include "sshkey.h" | |
6d7b2cd1 | 42 | #include "servconf.h" |
226cfa03 | 43 | #include "canohost.h" |
d7834353 | 44 | #include "hostfile.h" |
31ca54aa | 45 | #include "auth.h" |
d4a8b7e3 | 46 | |
5eabda30 BL |
47 | /* import */ |
48 | extern ServerOptions options; | |
bdde330d | 49 | extern int use_privsep; |
5eabda30 | 50 | |
5428f646 DM |
51 | /* |
52 | * This function processes an rhosts-style file (.rhosts, .shosts, or | |
53 | * /etc/hosts.equiv). This returns true if authentication can be granted | |
54 | * based on the file, and returns zero otherwise. | |
55 | */ | |
d4a8b7e3 | 56 | |
bba81213 | 57 | static int |
95def098 DM |
58 | check_rhosts_file(const char *filename, const char *hostname, |
59 | const char *ipaddr, const char *client_user, | |
60 | const char *server_user) | |
d4a8b7e3 | 61 | { |
95def098 | 62 | FILE *f; |
5191df92 | 63 | #define RBUFLN 1024 |
64 | char buf[RBUFLN];/* Must not be larger than host, user, dummy below. */ | |
06db584e DT |
65 | int fd; |
66 | struct stat st; | |
95def098 DM |
67 | |
68 | /* Open the .rhosts file, deny if unreadable */ | |
06db584e | 69 | if ((fd = open(filename, O_RDONLY|O_NONBLOCK)) == -1) |
95def098 | 70 | return 0; |
06db584e DT |
71 | if (fstat(fd, &st) == -1) { |
72 | close(fd); | |
73 | return 0; | |
74 | } | |
75 | if (!S_ISREG(st.st_mode)) { | |
76 | logit("User %s hosts file %s is not a regular file", | |
77 | server_user, filename); | |
78 | close(fd); | |
79 | return 0; | |
80 | } | |
81 | unset_nonblock(fd); | |
82 | if ((f = fdopen(fd, "r")) == NULL) { | |
83 | close(fd); | |
84 | return 0; | |
85 | } | |
95def098 | 86 | while (fgets(buf, sizeof(buf), f)) { |
5191df92 | 87 | /* All three must have length >= buf to avoid overflows. */ |
88 | char hostbuf[RBUFLN], userbuf[RBUFLN], dummy[RBUFLN]; | |
89 | char *host, *user, *cp; | |
95def098 DM |
90 | int negated; |
91 | ||
92 | for (cp = buf; *cp == ' ' || *cp == '\t'; cp++) | |
93 | ; | |
94 | if (*cp == '#' || *cp == '\n' || !*cp) | |
95 | continue; | |
96 | ||
5428f646 DM |
97 | /* |
98 | * NO_PLUS is supported at least on OSF/1. We skip it (we | |
99 | * don't ever support the plus syntax). | |
100 | */ | |
95def098 DM |
101 | if (strncmp(cp, "NO_PLUS", 7) == 0) |
102 | continue; | |
103 | ||
5428f646 DM |
104 | /* |
105 | * This should be safe because each buffer is as big as the | |
106 | * whole string, and thus cannot be overwritten. | |
107 | */ | |
a9825785 DM |
108 | switch (sscanf(buf, "%1023s %1023s %1023s", hostbuf, userbuf, |
109 | dummy)) { | |
95def098 | 110 | case 0: |
bdde330d | 111 | auth_debug_add("Found empty line in %.100s.", filename); |
95def098 DM |
112 | continue; |
113 | case 1: | |
114 | /* Host name only. */ | |
115 | strlcpy(userbuf, server_user, sizeof(userbuf)); | |
116 | break; | |
117 | case 2: | |
118 | /* Got both host and user name. */ | |
119 | break; | |
120 | case 3: | |
bdde330d | 121 | auth_debug_add("Found garbage in %.100s.", filename); |
95def098 DM |
122 | continue; |
123 | default: | |
124 | /* Weird... */ | |
125 | continue; | |
126 | } | |
127 | ||
128 | host = hostbuf; | |
129 | user = userbuf; | |
130 | negated = 0; | |
131 | ||
132 | /* Process negated host names, or positive netgroups. */ | |
133 | if (host[0] == '-') { | |
134 | negated = 1; | |
135 | host++; | |
136 | } else if (host[0] == '+') | |
137 | host++; | |
138 | ||
139 | if (user[0] == '-') { | |
140 | negated = 1; | |
141 | user++; | |
142 | } else if (user[0] == '+') | |
143 | user++; | |
144 | ||
145 | /* Check for empty host/user names (particularly '+'). */ | |
146 | if (!host[0] || !user[0]) { | |
147 | /* We come here if either was '+' or '-'. */ | |
5191df92 | 148 | auth_debug_add("Ignoring wild host/user names " |
149 | "in %.100s.", filename); | |
95def098 DM |
150 | continue; |
151 | } | |
152 | /* Verify that host name matches. */ | |
153 | if (host[0] == '@') { | |
154 | if (!innetgr(host + 1, hostname, NULL, NULL) && | |
155 | !innetgr(host + 1, ipaddr, NULL, NULL)) | |
156 | continue; | |
5191df92 | 157 | } else if (strcasecmp(host, hostname) && |
158 | strcmp(host, ipaddr) != 0) | |
95def098 DM |
159 | continue; /* Different hostname. */ |
160 | ||
161 | /* Verify that user name matches. */ | |
162 | if (user[0] == '@') { | |
163 | if (!innetgr(user + 1, NULL, client_user, NULL)) | |
164 | continue; | |
165 | } else if (strcmp(user, client_user) != 0) | |
166 | continue; /* Different username. */ | |
167 | ||
168 | /* Found the user and host. */ | |
169 | fclose(f); | |
170 | ||
171 | /* If the entry was negated, deny access. */ | |
172 | if (negated) { | |
bdde330d | 173 | auth_debug_add("Matched negative entry in %.100s.", |
0dc1bef1 | 174 | filename); |
95def098 DM |
175 | return 0; |
176 | } | |
177 | /* Accept authentication. */ | |
178 | return 1; | |
d4a8b7e3 | 179 | } |
d4a8b7e3 | 180 | |
95def098 DM |
181 | /* Authentication using this file denied. */ |
182 | fclose(f); | |
183 | return 0; | |
d4a8b7e3 DM |
184 | } |
185 | ||
5428f646 DM |
186 | /* |
187 | * Tries to authenticate the user using the .shosts or .rhosts file. Returns | |
188 | * true if authentication succeeds. If ignore_rhosts is true, only | |
189 | * /etc/hosts.equiv will be considered (.rhosts and .shosts are ignored). | |
190 | */ | |
4af51306 | 191 | int |
6cb6dcff | 192 | auth_rhosts2(struct passwd *pw, const char *client_user, const char *hostname, |
5eabda30 BL |
193 | const char *ipaddr) |
194 | { | |
bf114d6f | 195 | char *path = NULL; |
95def098 | 196 | struct stat st; |
541667fe | 197 | static const char * const rhosts_files[] = {".shosts", ".rhosts", NULL}; |
46c16220 | 198 | u_int rhosts_file_index; |
bf114d6f | 199 | int r; |
95def098 | 200 | |
bf114d6f | 201 | debug2_f("clientuser %s hostname %s ipaddr %s", |
5eabda30 BL |
202 | client_user, hostname, ipaddr); |
203 | ||
95def098 | 204 | /* Switch to the user's uid. */ |
3fcf1a22 | 205 | temporarily_use_uid(pw); |
5428f646 | 206 | /* |
5191df92 | 207 | * Quick check: if the user has no .shosts or .rhosts files and |
208 | * no system hosts.equiv/shosts.equiv files exist then return | |
5428f646 DM |
209 | * failure immediately without doing costly lookups from name |
210 | * servers. | |
211 | */ | |
95def098 | 212 | for (rhosts_file_index = 0; rhosts_files[rhosts_file_index]; |
9f0f5c64 | 213 | rhosts_file_index++) { |
95def098 | 214 | /* Check users .rhosts or .shosts. */ |
bf114d6f | 215 | xasprintf(&path, "%s/%s", |
216 | pw->pw_dir, rhosts_files[rhosts_file_index]); | |
217 | r = stat(path, &st); | |
218 | free(path); | |
219 | if (r >= 0) | |
95def098 | 220 | break; |
d4a8b7e3 | 221 | } |
95def098 DM |
222 | /* Switch back to privileged uid. */ |
223 | restore_uid(); | |
224 | ||
5191df92 | 225 | /* |
226 | * Deny if The user has no .shosts or .rhosts file and there | |
227 | * are no system-wide files. | |
228 | */ | |
95def098 | 229 | if (!rhosts_files[rhosts_file_index] && |
4d28fa78 | 230 | stat(_PATH_RHOSTS_EQUIV, &st) == -1 && |
231 | stat(_PATH_SSH_HOSTS_EQUIV, &st) == -1) { | |
816036f1 | 232 | debug3_f("no hosts access files exist"); |
95def098 | 233 | return 0; |
5191df92 | 234 | } |
95def098 | 235 | |
5191df92 | 236 | /* |
237 | * If not logging in as superuser, try /etc/hosts.equiv and | |
238 | * shosts.equiv. | |
239 | */ | |
240 | if (pw->pw_uid == 0) | |
816036f1 | 241 | debug3_f("root user, ignoring system hosts files"); |
5191df92 | 242 | else { |
9f0f5c64 DM |
243 | if (check_rhosts_file(_PATH_RHOSTS_EQUIV, hostname, ipaddr, |
244 | client_user, pw->pw_name)) { | |
5191df92 | 245 | auth_debug_add("Accepted for %.100s [%.100s] by " |
246 | "/etc/hosts.equiv.", hostname, ipaddr); | |
95def098 DM |
247 | return 1; |
248 | } | |
9f0f5c64 DM |
249 | if (check_rhosts_file(_PATH_SSH_HOSTS_EQUIV, hostname, ipaddr, |
250 | client_user, pw->pw_name)) { | |
5191df92 | 251 | auth_debug_add("Accepted for %.100s [%.100s] by " |
252 | "%.100s.", hostname, ipaddr, _PATH_SSH_HOSTS_EQUIV); | |
95def098 DM |
253 | return 1; |
254 | } | |
d4a8b7e3 | 255 | } |
5191df92 | 256 | |
5428f646 DM |
257 | /* |
258 | * Check that the home directory is owned by root or the user, and is | |
259 | * not group or world writable. | |
260 | */ | |
4d28fa78 | 261 | if (stat(pw->pw_dir, &st) == -1) { |
996acd24 | 262 | logit("Rhosts authentication refused for %.100s: " |
bdde330d BL |
263 | "no home directory %.200s", pw->pw_name, pw->pw_dir); |
264 | auth_debug_add("Rhosts authentication refused for %.100s: " | |
265 | "no home directory %.200s", pw->pw_name, pw->pw_dir); | |
95def098 | 266 | return 0; |
d4a8b7e3 | 267 | } |
95def098 DM |
268 | if (options.strict_modes && |
269 | ((st.st_uid != 0 && st.st_uid != pw->pw_uid) || | |
9f0f5c64 | 270 | (st.st_mode & 022) != 0)) { |
996acd24 | 271 | logit("Rhosts authentication refused for %.100s: " |
bdde330d BL |
272 | "bad ownership or modes for home directory.", pw->pw_name); |
273 | auth_debug_add("Rhosts authentication refused for %.100s: " | |
274 | "bad ownership or modes for home directory.", pw->pw_name); | |
95def098 | 275 | return 0; |
d4a8b7e3 | 276 | } |
95def098 | 277 | /* Temporarily use the user's uid. */ |
3fcf1a22 | 278 | temporarily_use_uid(pw); |
95def098 DM |
279 | |
280 | /* Check all .rhosts files (currently .shosts and .rhosts). */ | |
281 | for (rhosts_file_index = 0; rhosts_files[rhosts_file_index]; | |
9f0f5c64 | 282 | rhosts_file_index++) { |
95def098 | 283 | /* Check users .rhosts or .shosts. */ |
bf114d6f | 284 | xasprintf(&path, "%s/%s", |
285 | pw->pw_dir, rhosts_files[rhosts_file_index]); | |
286 | if (stat(path, &st) == -1) { | |
a6183e25 | 287 | debug3_f("stat %s: %s", path, strerror(errno)); |
bf114d6f | 288 | free(path); |
95def098 | 289 | continue; |
bf114d6f | 290 | } |
95def098 | 291 | |
5428f646 DM |
292 | /* |
293 | * Make sure that the file is either owned by the user or by | |
294 | * root, and make sure it is not writable by anyone but the | |
295 | * owner. This is to help avoid novices accidentally | |
296 | * allowing access to their account by anyone. | |
297 | */ | |
95def098 DM |
298 | if (options.strict_modes && |
299 | ((st.st_uid != 0 && st.st_uid != pw->pw_uid) || | |
9f0f5c64 | 300 | (st.st_mode & 022) != 0)) { |
bf114d6f | 301 | logit("Rhosts authentication refused for %.100s: " |
302 | "bad modes for %.200s", pw->pw_name, path); | |
303 | auth_debug_add("Bad file modes for %.200s", path); | |
304 | free(path); | |
95def098 DM |
305 | continue; |
306 | } | |
5191df92 | 307 | /* |
308 | * Check if we have been configured to ignore .rhosts | |
309 | * and .shosts files. | |
310 | */ | |
c90f72d2 | 311 | if (options.ignore_rhosts == IGNORE_RHOSTS_YES || |
312 | (options.ignore_rhosts == IGNORE_RHOSTS_SHOSTS && | |
313 | strcmp(rhosts_files[rhosts_file_index], ".shosts") != 0)) { | |
5191df92 | 314 | auth_debug_add("Server has been configured to " |
315 | "ignore %.100s.", rhosts_files[rhosts_file_index]); | |
bf114d6f | 316 | free(path); |
95def098 DM |
317 | continue; |
318 | } | |
319 | /* Check if authentication is permitted by the file. */ | |
bf114d6f | 320 | if (check_rhosts_file(path, hostname, ipaddr, |
5191df92 | 321 | client_user, pw->pw_name)) { |
bdde330d BL |
322 | auth_debug_add("Accepted by %.100s.", |
323 | rhosts_files[rhosts_file_index]); | |
95def098 DM |
324 | /* Restore the privileged uid. */ |
325 | restore_uid(); | |
5191df92 | 326 | auth_debug_add("Accepted host %s ip %s client_user " |
327 | "%s server_user %s", hostname, ipaddr, | |
328 | client_user, pw->pw_name); | |
bf114d6f | 329 | free(path); |
95def098 DM |
330 | return 1; |
331 | } | |
bf114d6f | 332 | free(path); |
d4a8b7e3 | 333 | } |
d4a8b7e3 | 334 | |
95def098 DM |
335 | /* Restore the privileged uid. */ |
336 | restore_uid(); | |
337 | return 0; | |
d4a8b7e3 | 338 | } |