auth.c 14.6 KB
Newer Older
1
/* $OpenBSD: auth.c,v 1.75 2006/08/03 03:34:41 deraadt Exp $ */
2
/*
3
 * Copyright (c) 2000 Markus Friedl.  All rights reserved.
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 25 26
 */

#include "includes.h"
27 28 29

#include <sys/types.h>
#include <sys/stat.h>
30
#include <sys/param.h>
31

32
#include <errno.h>
33
#ifdef HAVE_PATHS_H
Damien Miller's avatar
Damien Miller committed
34
# include <paths.h>
35
#endif
36
#include <pwd.h>
37 38 39
#ifdef HAVE_LOGIN_H
#include <login.h>
#endif
40
#ifdef USE_SHADOW
41
#include <shadow.h>
42
#endif
43
#ifdef HAVE_LIBGEN_H
44
#include <libgen.h>
45
#endif
46
#include <stdarg.h>
47
#include <stdio.h>
48
#include <string.h>
49

50 51 52 53
#include "xmalloc.h"
#include "match.h"
#include "groupaccess.h"
#include "log.h"
54
#include "buffer.h"
55
#include "servconf.h"
56 57
#include "key.h"
#include "hostfile.h"
58
#include "auth.h"
59
#include "auth-options.h"
60
#include "canohost.h"
61
#include "uidswap.h"
62
#include "misc.h"
63 64
#include "bufaux.h"
#include "packet.h"
65
#include "loginrec.h"
66 67 68
#ifdef GSSAPI
#include "ssh-gss.h"
#endif
69
#include "monitor_wrap.h"
70

71 72
/* import */
extern ServerOptions options;
73
extern int use_privsep;
74
extern Buffer loginmsg;
75

76 77 78 79
/* Debugging messages */
Buffer auth_debug;
int auth_debug_init;

80
/*
Kevin Steves's avatar
Kevin Steves committed
81 82 83 84 85
 * Check if the user is allowed to log in via ssh. If user is listed
 * in DenyUsers or one of user's groups is listed in DenyGroups, false
 * will be returned. If AllowUsers isn't empty and user isn't listed
 * there, or if AllowGroups isn't empty and one of user's groups isn't
 * listed there, false will be returned.
86
 * If the user's shell is not executable, false will be returned.
87
 * Otherwise true is returned.
88
 */
89
int
90 91 92
allowed_user(struct passwd * pw)
{
	struct stat st;
93
	const char *hostname = NULL, *ipaddr = NULL, *passwd = NULL;
94
	char *shell;
95
	u_int i;
96
#ifdef USE_SHADOW
97
	struct spwd *spw = NULL;
98
#endif
99 100

	/* Shouldn't be called if pw is NULL, but better safe than sorry... */
Kevin Steves's avatar
Kevin Steves committed
101
	if (!pw || !pw->pw_name)
102 103
		return 0;

104
#ifdef USE_SHADOW
105 106 107
	if (!options.use_pam)
		spw = getspnam(pw->pw_name);
#ifdef HAS_SHADOW_EXPIRE
108 109
	if (!options.use_pam && spw != NULL && auth_shadow_acctexpired(spw))
		return 0;
110
#endif /* HAS_SHADOW_EXPIRE */
111
#endif /* USE_SHADOW */
112

113
	/* grab passwd field for locked account check */
114
#ifdef USE_SHADOW
115
	if (spw != NULL)
116
#if defined(HAVE_LIBIAF)  &&  !defined(BROKEN_LIBIAF)
117 118
		passwd = get_iaf_password(pw);
#else
119
		passwd = spw->sp_pwdp;
120
#endif /* HAVE_LIBIAF  && !BROKEN_LIBIAF */
121 122
#else
	passwd = pw->pw_passwd;
123 124
#endif

125
	/* check for locked account */
126
	if (!options.use_pam && passwd && *passwd) {
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
		int locked = 0;

#ifdef LOCKED_PASSWD_STRING
		if (strcmp(passwd, LOCKED_PASSWD_STRING) == 0)
			 locked = 1;
#endif
#ifdef LOCKED_PASSWD_PREFIX
		if (strncmp(passwd, LOCKED_PASSWD_PREFIX,
		    strlen(LOCKED_PASSWD_PREFIX)) == 0)
			 locked = 1;
#endif
#ifdef LOCKED_PASSWD_SUBSTR
		if (strstr(passwd, LOCKED_PASSWD_SUBSTR))
			locked = 1;
#endif
142 143 144
#if defined(HAVE_LIBIAF)  &&  !defined(BROKEN_LIBIAF)
		free(passwd);
#endif /* HAVE_LIBIAF  && !BROKEN_LIBIAF */
145 146 147 148 149 150 151
		if (locked) {
			logit("User %.100s not allowed because account is locked",
			    pw->pw_name);
			return 0;
		}
	}

152 153 154 155 156 157 158
	/*
	 * Get the shell from the password data.  An empty shell field is
	 * legal, and means /bin/sh.
	 */
	shell = (pw->pw_shell[0] == '\0') ? _PATH_BSHELL : pw->pw_shell;

	/* deny if shell does not exists or is not executable */
159
	if (stat(shell, &st) != 0) {
160
		logit("User %.100s not allowed because shell %.100s does not exist",
161
		    pw->pw_name, shell);
162
		return 0;
163
	}
164 165
	if (S_ISREG(st.st_mode) == 0 ||
	    (st.st_mode & (S_IXOTH|S_IXUSR|S_IXGRP)) == 0) {
166
		logit("User %.100s not allowed because shell %.100s is not executable",
167
		    pw->pw_name, shell);
168
		return 0;
169
	}
170

171 172
	if (options.num_deny_users > 0 || options.num_allow_users > 0 ||
	    options.num_deny_groups > 0 || options.num_allow_groups > 0) {
173
		hostname = get_canonical_hostname(options.use_dns);
174 175 176
		ipaddr = get_remote_ipaddr();
	}

177 178 179
	/* Return false if user is listed in DenyUsers */
	if (options.num_deny_users > 0) {
		for (i = 0; i < options.num_deny_users; i++)
180
			if (match_user(pw->pw_name, hostname, ipaddr,
181
			    options.deny_users[i])) {
182 183 184
				logit("User %.100s from %.100s not allowed "
				    "because listed in DenyUsers",
				    pw->pw_name, hostname);
185
				return 0;
186
			}
187 188 189 190
	}
	/* Return false if AllowUsers isn't empty and user isn't listed there */
	if (options.num_allow_users > 0) {
		for (i = 0; i < options.num_allow_users; i++)
191
			if (match_user(pw->pw_name, hostname, ipaddr,
192
			    options.allow_users[i]))
193 194
				break;
		/* i < options.num_allow_users iff we break for loop */
195
		if (i >= options.num_allow_users) {
196 197
			logit("User %.100s from %.100s not allowed because "
			    "not listed in AllowUsers", pw->pw_name, hostname);
198
			return 0;
199
		}
200 201
	}
	if (options.num_deny_groups > 0 || options.num_allow_groups > 0) {
Kevin Steves's avatar
Kevin Steves committed
202
		/* Get the user's group access list (primary and supplementary) */
203
		if (ga_init(pw->pw_name, pw->pw_gid) == 0) {
204 205
			logit("User %.100s from %.100s not allowed because "
			    "not in any group", pw->pw_name, hostname);
206
			return 0;
207
		}
208

Kevin Steves's avatar
Kevin Steves committed
209 210 211 212 213
		/* Return false if one of user's groups is listed in DenyGroups */
		if (options.num_deny_groups > 0)
			if (ga_match(options.deny_groups,
			    options.num_deny_groups)) {
				ga_free();
214 215 216
				logit("User %.100s from %.100s not allowed "
				    "because a group is listed in DenyGroups",
				    pw->pw_name, hostname);
217
				return 0;
Kevin Steves's avatar
Kevin Steves committed
218
			}
219
		/*
Kevin Steves's avatar
Kevin Steves committed
220
		 * Return false if AllowGroups isn't empty and one of user's groups
221 222
		 * isn't listed there
		 */
Kevin Steves's avatar
Kevin Steves committed
223 224 225 226
		if (options.num_allow_groups > 0)
			if (!ga_match(options.allow_groups,
			    options.num_allow_groups)) {
				ga_free();
227 228 229
				logit("User %.100s from %.100s not allowed "
				    "because none of user's groups are listed "
				    "in AllowGroups", pw->pw_name, hostname);
230
				return 0;
Kevin Steves's avatar
Kevin Steves committed
231 232
			}
		ga_free();
233 234
	}

235
#ifdef CUSTOM_SYS_AUTH_ALLOWED_USER
236
	if (!sys_auth_allowed_user(pw, &loginmsg))
237 238
		return 0;
#endif
239 240 241 242

	/* We found no reason not to let this user try to log on... */
	return 1;
}
243 244 245 246 247 248 249

void
auth_log(Authctxt *authctxt, int authenticated, char *method, char *info)
{
	void (*authlog) (const char *fmt,...) = verbose;
	char *authmsg;

250 251 252
	if (use_privsep && !mm_is_monitor() && !authctxt->postponed)
		return;

253 254 255
	/* Raise logging level */
	if (authenticated == 1 ||
	    !authctxt->valid ||
256
	    authctxt->failures >= options.max_authtries / 2 ||
257
	    strcmp(method, "password") == 0)
258
		authlog = logit;
259 260 261 262 263 264 265 266 267

	if (authctxt->postponed)
		authmsg = "Postponed";
	else
		authmsg = authenticated ? "Accepted" : "Failed";

	authlog("%s %s for %s%.100s from %.200s port %d%s",
	    authmsg,
	    method,
268
	    authctxt->valid ? "" : "invalid user ",
269
	    authctxt->user,
270 271 272
	    get_remote_ipaddr(),
	    get_remote_port(),
	    info);
273

274
#ifdef CUSTOM_FAILED_LOGIN
275 276
	if (authenticated == 0 && !authctxt->postponed &&
	    (strcmp(method, "password") == 0 ||
277 278
	    strncmp(method, "keyboard-interactive", 20) == 0 ||
	    strcmp(method, "challenge-response") == 0))
279 280
		record_failed_login(authctxt->user,
		    get_canonical_hostname(options.use_dns), "ssh");
281
#endif
282
#ifdef SSH_AUDIT_EVENTS
283 284
	if (authenticated == 0 && !authctxt->postponed)
		audit_event(audit_classify_auth(method));
285
#endif
286 287 288
}

/*
289
 * Check whether root logins are disallowed.
290 291
 */
int
292
auth_root_allowed(char *method)
293
{
294 295
	switch (options.permit_root_login) {
	case PERMIT_YES:
296
		return 1;
297 298 299 300 301 302
	case PERMIT_NO_PASSWD:
		if (strcmp(method, "password") != 0)
			return 1;
		break;
	case PERMIT_FORCED_ONLY:
		if (forced_command) {
303
			logit("Root login accepted for forced command.");
304 305 306
			return 1;
		}
		break;
307
	}
308
	logit("ROOT LOGIN REFUSED FROM %.200s", get_remote_ipaddr());
309
	return 0;
310
}
311 312 313 314 315 316 317 318 319


/*
 * Given a template and a passwd structure, build a filename
 * by substituting % tokenised options. Currently, %% becomes '%',
 * %h becomes the home directory and %u the username.
 *
 * This returns a buffer allocated by xmalloc.
 */
320 321
static char *
expand_authorized_keys(const char *filename, struct passwd *pw)
322
{
323 324
	char *file, ret[MAXPATHLEN];
	int i;
325

326 327
	file = percent_expand(filename, "h", pw->pw_dir,
	    "u", pw->pw_name, (char *)NULL);
328 329 330 331 332

	/*
	 * Ensure that filename starts anchored. If not, be backward
	 * compatible and prepend the '%h/'
	 */
333 334 335
	if (*file == '/')
		return (file);

336 337
	i = snprintf(ret, sizeof(ret), "%s/%s", pw->pw_dir, file);
	if (i < 0 || (size_t)i >= sizeof(ret))
338 339
		fatal("expand_authorized_keys: path too long");
	xfree(file);
340
	return (xstrdup(ret));
341 342 343 344 345
}

char *
authorized_keys_file(struct passwd *pw)
{
346
	return expand_authorized_keys(options.authorized_keys_file, pw);
347 348 349 350 351
}

char *
authorized_keys_file2(struct passwd *pw)
{
352
	return expand_authorized_keys(options.authorized_keys_file2, pw);
353 354
}

355 356 357 358 359 360 361 362
/* return ok if key exists in sysfile or userfile */
HostStatus
check_key_in_hostfiles(struct passwd *pw, Key *key, const char *host,
    const char *sysfile, const char *userfile)
{
	Key *found;
	char *user_hostfile;
	struct stat st;
363
	HostStatus host_status;
364 365 366 367 368 369 370 371 372 373

	/* Check if we know the host and its host key. */
	found = key_new(key->type);
	host_status = check_host_in_hostfile(sysfile, host, key, found, NULL);

	if (host_status != HOST_OK && userfile != NULL) {
		user_hostfile = tilde_expand_filename(userfile, pw->pw_uid);
		if (options.strict_modes &&
		    (stat(user_hostfile, &st) == 0) &&
		    ((st.st_uid != 0 && st.st_uid != pw->pw_uid) ||
374
		    (st.st_mode & 022) != 0)) {
375
			logit("Authentication refused for %.100s: "
376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393
			    "bad owner or modes for %.200s",
			    pw->pw_name, user_hostfile);
		} else {
			temporarily_use_uid(pw);
			host_status = check_host_in_hostfile(user_hostfile,
			    host, key, found, NULL);
			restore_uid();
		}
		xfree(user_hostfile);
	}
	key_free(found);

	debug2("check_key_in_hostfiles: key %s for %s", host_status == HOST_OK ?
	    "ok" : "not found", host);
	return host_status;
}


394 395
/*
 * Check a given file for security. This is defined as all components
396
 * of the path to the file must be owned by either the owner of
397
 * of the file or root and no directories must be group or world writable.
398 399 400 401 402 403 404 405 406
 *
 * XXX Should any specific check be done for sym links ?
 *
 * Takes an open file descriptor, the file name, a uid and and
 * error buffer plus max size as arguments.
 *
 * Returns 0 on success and -1 on failure
 */
int
407 408
secure_filename(FILE *f, const char *file, struct passwd *pw,
    char *err, size_t errlen)
409
{
410
	uid_t uid = pw->pw_uid;
411
	char buf[MAXPATHLEN], homedir[MAXPATHLEN];
412
	char *cp;
413
	int comparehome = 0;
414 415 416 417 418 419 420
	struct stat st;

	if (realpath(file, buf) == NULL) {
		snprintf(err, errlen, "realpath %s failed: %s", file,
		    strerror(errno));
		return -1;
	}
421 422
	if (realpath(pw->pw_dir, homedir) != NULL)
		comparehome = 1;
423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444

	/* check the open file to avoid races */
	if (fstat(fileno(f), &st) < 0 ||
	    (st.st_uid != 0 && st.st_uid != uid) ||
	    (st.st_mode & 022) != 0) {
		snprintf(err, errlen, "bad ownership or modes for file %s",
		    buf);
		return -1;
	}

	/* for each component of the canonical path, walking upwards */
	for (;;) {
		if ((cp = dirname(buf)) == NULL) {
			snprintf(err, errlen, "dirname() failed");
			return -1;
		}
		strlcpy(buf, cp, sizeof(buf));

		debug3("secure_filename: checking '%s'", buf);
		if (stat(buf, &st) < 0 ||
		    (st.st_uid != 0 && st.st_uid != uid) ||
		    (st.st_mode & 022) != 0) {
445
			snprintf(err, errlen,
446 447 448 449
			    "bad ownership or modes for directory %s", buf);
			return -1;
		}

450
		/* If are passed the homedir then we can stop */
451
		if (comparehome && strcmp(homedir, buf) == 0) {
452 453 454 455
			debug3("secure_filename: terminating check at '%s'",
			    buf);
			break;
		}
456 457 458 459 460 461 462 463 464
		/*
		 * dirname should always complete with a "/" path,
		 * but we can be paranoid and check for "." too
		 */
		if ((strcmp("/", buf) == 0) || (strcmp(".", buf) == 0))
			break;
	}
	return 0;
}
465 466 467 468

struct passwd *
getpwnamallow(const char *user)
{
469 470 471 472 473 474
#ifdef HAVE_LOGIN_CAP
	extern login_cap_t *lc;
#ifdef BSD_AUTH
	auth_session_t *as;
#endif
#endif
475 476
	struct passwd *pw;

477 478 479
	parse_server_match_config(&options, user,
	    get_canonical_hostname(options.use_dns), get_remote_ipaddr());

480
	pw = getpwnam(user);
481
	if (pw == NULL) {
482
		logit("Invalid user %.100s from %.100s",
483
		    user, get_remote_ipaddr());
484
#ifdef CUSTOM_FAILED_LOGIN
485 486
		record_failed_login(user,
		    get_canonical_hostname(options.use_dns), "ssh");
487
#endif
488 489 490
#ifdef SSH_AUDIT_EVENTS
		audit_event(SSH_INVALID_USER);
#endif /* SSH_AUDIT_EVENTS */
491 492 493
		return (NULL);
	}
	if (!allowed_user(pw))
494 495 496 497 498 499 500 501
		return (NULL);
#ifdef HAVE_LOGIN_CAP
	if ((lc = login_getclass(pw->pw_class)) == NULL) {
		debug("unable to get login class: %s", user);
		return (NULL);
	}
#ifdef BSD_AUTH
	if ((as = auth_open()) == NULL || auth_setpwd(as, pw) != 0 ||
Damien Miller's avatar
Damien Miller committed
502
	    auth_approval(as, lc, pw->pw_name, "ssh") <= 0) {
503
		debug("Approval failure for %s", user);
504
		pw = NULL;
505 506 507 508 509
	}
	if (as != NULL)
		auth_close(as);
#endif
#endif
510 511 512
	if (pw != NULL)
		return (pwcopy(pw));
	return (NULL);
513
}
514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553

void
auth_debug_add(const char *fmt,...)
{
	char buf[1024];
	va_list args;

	if (!auth_debug_init)
		return;

	va_start(args, fmt);
	vsnprintf(buf, sizeof(buf), fmt, args);
	va_end(args);
	buffer_put_cstring(&auth_debug, buf);
}

void
auth_debug_send(void)
{
	char *msg;

	if (!auth_debug_init)
		return;
	while (buffer_len(&auth_debug)) {
		msg = buffer_get_string(&auth_debug, NULL);
		packet_send_debug("%s", msg);
		xfree(msg);
	}
}

void
auth_debug_reset(void)
{
	if (auth_debug_init)
		buffer_clear(&auth_debug);
	else {
		buffer_init(&auth_debug);
		auth_debug_init = 1;
	}
}
554 555 556 557 558 559 560 561 562

struct passwd *
fakepw(void)
{
	static struct passwd fake;

	memset(&fake, 0, sizeof(fake));
	fake.pw_name = "NOUSER";
	fake.pw_passwd =
563
	    "$2a$06$r3.juUaHZDlIbQaO2dS9FuYxL1W9M81R1Tc92PoSNmzvpEqLkLGrK";
564
	fake.pw_gecos = "NOUSER";
565 566
	fake.pw_uid = (uid_t)-1;
	fake.pw_gid = (gid_t)-1;
567 568 569 570 571 572 573 574
#ifdef HAVE_PW_CLASS_IN_PASSWD
	fake.pw_class = "";
#endif
	fake.pw_dir = "/nonexist";
	fake.pw_shell = "/nonexist";

	return (&fake);
}