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