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