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