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