auth2.c 21.4 KB
Newer Older
1
/* $OpenBSD: auth2.c,v 1.149 2018/07/11 18:53:29 markus Exp $ */
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
/*
 * Copyright (c) 2000 Markus Friedl.  All rights reserved.
 *
 * 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.
 */
25

26 27
#include "includes.h"

28
#include <sys/types.h>
29 30
#include <sys/stat.h>
#include <sys/uio.h>
31

32
#include <fcntl.h>
djm@openbsd.org's avatar
djm@openbsd.org committed
33
#include <limits.h>
34
#include <pwd.h>
35
#include <stdarg.h>
36
#include <string.h>
37
#include <unistd.h>
38

39
#include "atomicio.h"
40
#include "xmalloc.h"
41
#include "ssh2.h"
42
#include "packet.h"
43
#include "log.h"
44
#include "sshbuf.h"
45
#include "misc.h"
46 47
#include "servconf.h"
#include "compat.h"
48
#include "sshkey.h"
49
#include "hostfile.h"
50 51
#include "auth.h"
#include "dispatch.h"
52
#include "pathnames.h"
53 54
#include "sshbuf.h"
#include "ssherr.h"
55

56 57 58
#ifdef GSSAPI
#include "ssh-gss.h"
#endif
59
#include "monitor_wrap.h"
djm@openbsd.org's avatar
djm@openbsd.org committed
60
#include "ssherr.h"
61
#include "digest.h"
62

63 64
/* import */
extern ServerOptions options;
65
extern u_char *session_id2;
66
extern u_int session_id2_len;
67
extern struct sshbuf *loginmsg;
68

69 70 71 72 73 74 75
/* methods */

extern Authmethod method_none;
extern Authmethod method_pubkey;
extern Authmethod method_passwd;
extern Authmethod method_kbdint;
extern Authmethod method_hostbased;
76
#ifdef GSSAPI
77
extern Authmethod method_gsskeyex;
78 79
extern Authmethod method_gssapi;
#endif
80 81 82 83

Authmethod *authmethods[] = {
	&method_none,
	&method_pubkey,
84
#ifdef GSSAPI
85
	&method_gsskeyex,
86 87
	&method_gssapi,
#endif
88 89 90 91
	&method_passwd,
	&method_kbdint,
	&method_hostbased,
	NULL
92 93
};

94 95
/* protocol */

markus@openbsd.org's avatar
markus@openbsd.org committed
96 97
static int input_service_request(int, u_int32_t, struct ssh *);
static int input_userauth_request(int, u_int32_t, struct ssh *);
98 99

/* helper */
100 101
static Authmethod *authmethod_lookup(Authctxt *, const char *);
static char *authmethods_get(Authctxt *authctxt);
102 103 104 105 106 107

#define MATCH_NONE	0	/* method or submethod mismatch */
#define MATCH_METHOD	1	/* method matches (no submethod specified) */
#define MATCH_BOTH	2	/* method and submethod match */
#define MATCH_PARTIAL	3	/* method matches, submethod can't be checked */
static int list_starts_with(const char *, const char *, const char *);
108

109 110 111 112 113 114 115 116 117 118 119 120 121 122
char *
auth2_read_banner(void)
{
	struct stat st;
	char *banner = NULL;
	size_t len, n;
	int fd;

	if ((fd = open(options.banner, O_RDONLY)) == -1)
		return (NULL);
	if (fstat(fd, &st) == -1) {
		close(fd);
		return (NULL);
	}
123
	if (st.st_size <= 0 || st.st_size > 1*1024*1024) {
124 125 126 127 128 129 130 131 132 133
		close(fd);
		return (NULL);
	}

	len = (size_t)st.st_size;		/* truncate */
	banner = xmalloc(len + 1);
	n = atomicio(read, fd, banner, len);
	close(fd);

	if (n != len) {
134
		free(banner);
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
		return (NULL);
	}
	banner[n] = '\0';

	return (banner);
}

void
userauth_send_banner(const char *msg)
{
	packet_start(SSH2_MSG_USERAUTH_BANNER);
	packet_put_cstring(msg);
	packet_put_cstring("");		/* language, unused */
	packet_send();
	debug("%s: sent", __func__);
}

static void
userauth_banner(void)
{
	char *banner = NULL;

djm@openbsd.org's avatar
djm@openbsd.org committed
157
	if (options.banner == NULL)
158 159 160 161 162 163 164
		return;

	if ((banner = PRIVSEP(auth2_read_banner())) == NULL)
		goto done;
	userauth_send_banner(banner);

done:
165
	free(banner);
166 167
}

168
/*
169
 * loop until authctxt->success == TRUE
170
 */
171 172
void
do_authentication2(Authctxt *authctxt)
173
{
markus@openbsd.org's avatar
markus@openbsd.org committed
174 175
	struct ssh *ssh = active_state;		/* XXX */
	ssh->authctxt = authctxt;		/* XXX move to caller */
markus@openbsd.org's avatar
markus@openbsd.org committed
176 177
	ssh_dispatch_init(ssh, &dispatch_protocol_error);
	ssh_dispatch_set(ssh, SSH2_MSG_SERVICE_REQUEST, &input_service_request);
markus@openbsd.org's avatar
markus@openbsd.org committed
178
	ssh_dispatch_run_fatal(ssh, DISPATCH_BLOCK, &authctxt->success);
markus@openbsd.org's avatar
markus@openbsd.org committed
179
	ssh->authctxt = NULL;
180 181
}

182
/*ARGSUSED*/
markus@openbsd.org's avatar
markus@openbsd.org committed
183
static int
markus@openbsd.org's avatar
markus@openbsd.org committed
184
input_service_request(int type, u_int32_t seq, struct ssh *ssh)
185
{
markus@openbsd.org's avatar
markus@openbsd.org committed
186
	Authctxt *authctxt = ssh->authctxt;
187
	u_int len;
188
	int acceptit = 0;
189
	char *service = packet_get_cstring(&len);
190
	packet_check_eom();
191

192 193 194
	if (authctxt == NULL)
		fatal("input_service_request: no authctxt");

195
	if (strcmp(service, "ssh-userauth") == 0) {
196
		if (!authctxt->success) {
197
			acceptit = 1;
198
			/* now we can handle user-auth requests */
markus@openbsd.org's avatar
markus@openbsd.org committed
199
			ssh_dispatch_set(ssh, SSH2_MSG_USERAUTH_REQUEST, &input_userauth_request);
200 201 202 203
		}
	}
	/* XXX all other service requests are denied */

204
	if (acceptit) {
205 206 207 208 209 210 211 212
		packet_start(SSH2_MSG_SERVICE_ACCEPT);
		packet_put_cstring(service);
		packet_send();
		packet_write_wait();
	} else {
		debug("bad service request %s", service);
		packet_disconnect("bad service request %s", service);
	}
213
	free(service);
markus@openbsd.org's avatar
markus@openbsd.org committed
214
	return 0;
215 216
}

217 218 219 220 221 222 223 224 225
#define MIN_FAIL_DELAY_SECONDS 0.005
static double
user_specific_delay(const char *user)
{
	char b[512];
	size_t len = ssh_digest_bytes(SSH_DIGEST_SHA512);
	u_char *hash = xmalloc(len);
	double delay;

226 227
	(void)snprintf(b, sizeof b, "%llu%s",
	     (unsigned long long)options.timing_secret, user);
228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253
	if (ssh_digest_memory(SSH_DIGEST_SHA512, b, strlen(b), hash, len) != 0)
		fatal("%s: ssh_digest_memory", __func__);
	/* 0-4.2 ms of delay */
	delay = (double)PEEK_U32(hash) / 1000 / 1000 / 1000 / 1000;
	freezero(hash, len);
	debug3("%s: user specific delay %0.3lfms", __func__, delay/1000);
	return MIN_FAIL_DELAY_SECONDS + delay;
}

static void
ensure_minimum_time_since(double start, double seconds)
{
	struct timespec ts;
	double elapsed = monotime_double() - start, req = seconds, remain;

	/* if we've already passed the requested time, scale up */
	while ((remain = seconds - elapsed) < 0.0)
		seconds *= 2;

	ts.tv_sec = remain;
	ts.tv_nsec = (remain - ts.tv_sec) * 1000000000;
	debug3("%s: elapsed %0.3lfms, delaying %0.3lfms (requested %0.3lfms)",
	    __func__, elapsed*1000, remain*1000, req*1000);
	nanosleep(&ts, NULL);
}

254
/*ARGSUSED*/
markus@openbsd.org's avatar
markus@openbsd.org committed
255
static int
markus@openbsd.org's avatar
markus@openbsd.org committed
256
input_userauth_request(int type, u_int32_t seq, struct ssh *ssh)
257
{
markus@openbsd.org's avatar
markus@openbsd.org committed
258
	Authctxt *authctxt = ssh->authctxt;
259
	Authmethod *m = NULL;
260
	char *user, *service, *method, *style = NULL;
261
	int authenticated = 0;
262
	double tstart = monotime_double();
263

264 265
	if (authctxt == NULL)
		fatal("input_userauth_request: no authctxt");
266

267 268 269
	user = packet_get_cstring(NULL);
	service = packet_get_cstring(NULL);
	method = packet_get_cstring(NULL);
270
	debug("userauth-request for user %s service %s method %s", user, service, method);
271
	debug("attempt %d failures %d", authctxt->attempt, authctxt->failures);
272

273 274 275
	if ((style = strchr(user, ':')) != NULL)
		*style++ = 0;

276
	if (authctxt->attempt++ == 0) {
277
		/* setup auth context */
278
		authctxt->pw = PRIVSEP(getpwnamallow(user));
279
		authctxt->user = xstrdup(user);
280
		if (authctxt->pw && strcmp(service, "ssh-connection")==0) {
281
			authctxt->valid = 1;
djm@openbsd.org's avatar
djm@openbsd.org committed
282 283
			debug2("%s: setting up authctxt for %s",
			    __func__, user);
284
		} else {
djm@openbsd.org's avatar
djm@openbsd.org committed
285
			/* Invalid user, fake password information */
286
			authctxt->pw = fakepw();
287 288
#ifdef SSH_AUDIT_EVENTS
			PRIVSEP(audit_event(SSH_INVALID_USER));
289
#endif
290
		}
291 292 293 294
#ifdef USE_PAM
		if (options.use_pam)
			PRIVSEP(start_pam(authctxt));
#endif
djm@openbsd.org's avatar
djm@openbsd.org committed
295 296
		ssh_packet_set_log_preamble(ssh, "%suser %s",
		    authctxt->valid ? "authenticating " : "invalid ", user);
297
		setproctitle("%s%s", authctxt->valid ? user : "unknown",
298
		    use_privsep ? " [net]" : "");
299
		authctxt->service = xstrdup(service);
300
		authctxt->style = style ? xstrdup(style) : NULL;
301 302
		if (use_privsep)
			mm_inform_authserv(service, style);
303
		userauth_banner();
304 305
		if (auth2_setup_methods_lists(authctxt) != 0)
			packet_disconnect("no authentication methods enabled");
306 307 308 309 310
	} else if (strcmp(user, authctxt->user) != 0 ||
	    strcmp(service, authctxt->service) != 0) {
		packet_disconnect("Change of username or service not allowed: "
		    "(%s,%s) -> (%s,%s)",
		    authctxt->user, authctxt->service, user, service);
311
	}
312
	/* reset state */
markus@openbsd.org's avatar
markus@openbsd.org committed
313
	auth2_challenge_stop(ssh);
314 315

#ifdef GSSAPI
316
	/* XXX move to auth2_gssapi_stop() */
markus@openbsd.org's avatar
markus@openbsd.org committed
317 318
	ssh_dispatch_set(ssh, SSH2_MSG_USERAUTH_GSSAPI_TOKEN, NULL);
	ssh_dispatch_set(ssh, SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE, NULL);
319 320
#endif

djm@openbsd.org's avatar
djm@openbsd.org committed
321
	auth2_authctxt_reset_info(authctxt);
322
	authctxt->postponed = 0;
Damien Miller's avatar
Damien Miller committed
323
	authctxt->server_caused_failure = 0;
324

325
	/* try to authenticate user */
326
	m = authmethod_lookup(authctxt, method);
327
	if (m != NULL && authctxt->failures < options.max_authtries) {
328
		debug2("input_userauth_request: try method %s", method);
markus@openbsd.org's avatar
markus@openbsd.org committed
329
		authenticated =	m->userauth(ssh);
330
	}
331 332 333
	if (!authctxt->authenticated)
		ensure_minimum_time_since(tstart,
		    user_specific_delay(authctxt->user));
markus@openbsd.org's avatar
markus@openbsd.org committed
334
	userauth_finish(ssh, authenticated, method, NULL);
Damien Miller's avatar
Damien Miller committed
335

336 337 338
	free(service);
	free(user);
	free(method);
markus@openbsd.org's avatar
markus@openbsd.org committed
339
	return 0;
Damien Miller's avatar
Damien Miller committed
340 341 342
}

void
markus@openbsd.org's avatar
markus@openbsd.org committed
343
userauth_finish(struct ssh *ssh, int authenticated, const char *method,
344
    const char *submethod)
Damien Miller's avatar
Damien Miller committed
345
{
markus@openbsd.org's avatar
markus@openbsd.org committed
346
	Authctxt *authctxt = ssh->authctxt;
347
	char *methods;
348
	int partial = 0;
349

350 351 352
	if (!authctxt->valid && authenticated)
		fatal("INTERNAL ERROR: authenticated invalid user %s",
		    authctxt->user);
353 354
	if (authenticated && authctxt->postponed)
		fatal("INTERNAL ERROR: authenticated and postponed");
355

356
	/* Special handling for root */
357
	if (authenticated && authctxt->pw->pw_uid == 0 &&
358
	    !auth_root_allowed(ssh, method)) {
359
		authenticated = 0;
360 361
#ifdef SSH_AUDIT_EVENTS
		PRIVSEP(audit_event(SSH_LOGIN_ROOT_DENIED));
362 363
#endif
	}
364

365
	if (authenticated && options.num_auth_methods != 0) {
366
		if (!auth2_update_methods_lists(authctxt, method, submethod)) {
367 368 369 370 371 372
			authenticated = 0;
			partial = 1;
		}
	}

	/* Log before sending the reply */
373
	auth_log(authctxt, authenticated, partial, method, submethod);
374

djm@openbsd.org's avatar
djm@openbsd.org committed
375 376 377 378
	/* Update information exposed to session */
	if (authenticated || partial)
		auth2_update_session_info(authctxt, method, submethod);

379 380 381
	if (authctxt->postponed)
		return;

382
#ifdef USE_PAM
383
	if (options.use_pam && authenticated) {
384 385
		int r;

386 387
		if (!PRIVSEP(do_pam_account())) {
			/* if PAM returned a message, send it to the user */
388 389 390 391 392
			if (sshbuf_len(loginmsg) > 0) {
				if ((r = sshbuf_put(loginmsg, "\0", 1)) != 0)
					fatal("%s: buffer error: %s",
					    __func__, ssh_err(r));
				userauth_send_banner(sshbuf_ptr(loginmsg));
393
				packet_write_wait();
394
			}
395
			fatal("Access denied for user %s by PAM account "
396
			    "configuration", authctxt->user);
397 398
		}
	}
399 400
#endif

401 402
	if (authenticated == 1) {
		/* turn off userauth */
markus@openbsd.org's avatar
markus@openbsd.org committed
403
		ssh_dispatch_set(ssh, SSH2_MSG_USERAUTH_REQUEST, &dispatch_protocol_ignore);
404 405 406 407 408
		packet_start(SSH2_MSG_USERAUTH_SUCCESS);
		packet_send();
		packet_write_wait();
		/* now we can break out */
		authctxt->success = 1;
djm@openbsd.org's avatar
djm@openbsd.org committed
409
		ssh_packet_set_log_preamble(ssh, "user %s", authctxt->user);
410
	} else {
411
		/* Allow initial try of "none" auth without failure penalty */
djm@openbsd.org's avatar
djm@openbsd.org committed
412
		if (!partial && !authctxt->server_caused_failure &&
Damien Miller's avatar
Damien Miller committed
413
		    (authctxt->attempt > 1 || strcmp(method, "none") != 0))
414 415
			authctxt->failures++;
		if (authctxt->failures >= options.max_authtries) {
416 417
#ifdef SSH_AUDIT_EVENTS
			PRIVSEP(audit_event(SSH_LOGIN_EXCEED_MAXTRIES));
418
#endif
419
			auth_maxtries_exceeded(authctxt);
420
		}
421 422 423
		methods = authmethods_get(authctxt);
		debug3("%s: failure partial=%d next methods=\"%s\"", __func__,
		    partial, methods);
424 425
		packet_start(SSH2_MSG_USERAUTH_FAILURE);
		packet_put_cstring(methods);
426
		packet_put_char(partial);
427 428
		packet_send();
		packet_write_wait();
429
		free(methods);
430
	}
431 432
}

433 434 435 436 437
/*
 * Checks whether method is allowed by at least one AuthenticationMethods
 * methods list. Returns 1 if allowed, or no methods lists configured.
 * 0 otherwise.
 */
438 439 440
int
auth2_method_allowed(Authctxt *authctxt, const char *method,
    const char *submethod)
441 442 443 444 445 446 447 448 449 450
{
	u_int i;

	/*
	 * NB. authctxt->num_auth_methods might be zero as a result of
	 * auth2_setup_methods_lists(), so check the configuration.
	 */
	if (options.num_auth_methods == 0)
		return 1;
	for (i = 0; i < authctxt->num_auth_methods; i++) {
451 452
		if (list_starts_with(authctxt->auth_methods[i], method,
		    submethod) != MATCH_NONE)
453 454 455 456 457
			return 1;
	}
	return 0;
}

458
static char *
459
authmethods_get(Authctxt *authctxt)
460
{
461
	struct sshbuf *b;
462
	char *list;
463
	int i, r;
464

465 466
	if ((b = sshbuf_new()) == NULL)
		fatal("%s: sshbuf_new failed", __func__);
467 468
	for (i = 0; authmethods[i] != NULL; i++) {
		if (strcmp(authmethods[i]->name, "none") == 0)
469
			continue;
470 471 472
		if (authmethods[i]->enabled == NULL ||
		    *(authmethods[i]->enabled) == 0)
			continue;
473 474
		if (!auth2_method_allowed(authctxt, authmethods[i]->name,
		    NULL))
475
			continue;
476 477 478
		if ((r = sshbuf_putf(b, "%s%s", sshbuf_len(b) ? "," : "",
		    authmethods[i]->name)) != 0)
			fatal("%s: buffer error: %s", __func__, ssh_err(r));
479
	}
480
	if ((list = sshbuf_dup_string(b)) == NULL)
djm@openbsd.org's avatar
djm@openbsd.org committed
481
		fatal("%s: sshbuf_dup_string failed", __func__);
482
	sshbuf_free(b);
483 484 485
	return list;
}

486
static Authmethod *
487
authmethod_lookup(Authctxt *authctxt, const char *name)
488
{
489 490
	int i;

491
	if (name != NULL)
492 493 494
		for (i = 0; authmethods[i] != NULL; i++)
			if (authmethods[i]->enabled != NULL &&
			    *(authmethods[i]->enabled) != 0 &&
495
			    strcmp(name, authmethods[i]->name) == 0 &&
496 497
			    auth2_method_allowed(authctxt,
			    authmethods[i]->name, NULL))
498 499 500
				return authmethods[i];
	debug2("Unrecognized authentication method name: %s",
	    name ? name : "NULL");
501
	return NULL;
502
}
503

504 505 506 507 508 509 510 511
/*
 * Check a comma-separated list of methods for validity. Is need_enable is
 * non-zero, then also require that the methods are enabled.
 * Returns 0 on success or -1 if the methods list is invalid.
 */
int
auth2_methods_valid(const char *_methods, int need_enable)
{
512
	char *methods, *omethods, *method, *p;
513 514 515 516 517 518 519 520 521 522
	u_int i, found;
	int ret = -1;

	if (*_methods == '\0') {
		error("empty authentication method list");
		return -1;
	}
	omethods = methods = xstrdup(_methods);
	while ((method = strsep(&methods, ",")) != NULL) {
		for (found = i = 0; !found && authmethods[i] != NULL; i++) {
523 524
			if ((p = strchr(method, ':')) != NULL)
				*p = '\0';
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 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589
			if (strcmp(method, authmethods[i]->name) != 0)
				continue;
			if (need_enable) {
				if (authmethods[i]->enabled == NULL ||
				    *(authmethods[i]->enabled) == 0) {
					error("Disabled method \"%s\" in "
					    "AuthenticationMethods list \"%s\"",
					    method, _methods);
					goto out;
				}
			}
			found = 1;
			break;
		}
		if (!found) {
			error("Unknown authentication method \"%s\" in list",
			    method);
			goto out;
		}
	}
	ret = 0;
 out:
	free(omethods);
	return ret;
}

/*
 * Prune the AuthenticationMethods supplied in the configuration, removing
 * any methods lists that include disabled methods. Note that this might
 * leave authctxt->num_auth_methods == 0, even when multiple required auth
 * has been requested. For this reason, all tests for whether multiple is
 * enabled should consult options.num_auth_methods directly.
 */
int
auth2_setup_methods_lists(Authctxt *authctxt)
{
	u_int i;

	if (options.num_auth_methods == 0)
		return 0;
	debug3("%s: checking methods", __func__);
	authctxt->auth_methods = xcalloc(options.num_auth_methods,
	    sizeof(*authctxt->auth_methods));
	authctxt->num_auth_methods = 0;
	for (i = 0; i < options.num_auth_methods; i++) {
		if (auth2_methods_valid(options.auth_methods[i], 1) != 0) {
			logit("Authentication methods list \"%s\" contains "
			    "disabled method, skipping",
			    options.auth_methods[i]);
			continue;
		}
		debug("authentication methods list %d: %s",
		    authctxt->num_auth_methods, options.auth_methods[i]);
		authctxt->auth_methods[authctxt->num_auth_methods++] =
		    xstrdup(options.auth_methods[i]);
	}
	if (authctxt->num_auth_methods == 0) {
		error("No AuthenticationMethods left after eliminating "
		    "disabled methods");
		return -1;
	}
	return 0;
}

static int
590 591
list_starts_with(const char *methods, const char *method,
    const char *submethod)
592 593
{
	size_t l = strlen(method);
594 595
	int match;
	const char *p;
596 597

	if (strncmp(methods, method, l) != 0)
598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613
		return MATCH_NONE;
	p = methods + l;
	match = MATCH_METHOD;
	if (*p == ':') {
		if (!submethod)
			return MATCH_PARTIAL;
		l = strlen(submethod);
		p += 1;
		if (strncmp(submethod, p, l))
			return MATCH_NONE;
		p += l;
		match = MATCH_BOTH;
	}
	if (*p != ',' && *p != '\0')
		return MATCH_NONE;
	return match;
614 615 616 617 618 619 620 621
}

/*
 * Remove method from the start of a comma-separated list of methods.
 * Returns 0 if the list of methods did not start with that method or 1
 * if it did.
 */
static int
622
remove_method(char **methods, const char *method, const char *submethod)
623
{
624
	char *omethods = *methods, *p;
625
	size_t l = strlen(method);
626
	int match;
627

628 629
	match = list_starts_with(omethods, method, submethod);
	if (match != MATCH_METHOD && match != MATCH_BOTH)
630
		return 0;
631 632 633 634 635 636
	p = omethods + l;
	if (submethod && match == MATCH_BOTH)
		p += 1 + strlen(submethod); /* include colon */
	if (*p == ',')
		p++;
	*methods = xstrdup(p);
637 638 639 640 641 642 643 644 645 646 647
	free(omethods);
	return 1;
}

/*
 * Called after successful authentication. Will remove the successful method
 * from the start of each list in which it occurs. If it was the last method
 * in any list, then authentication is deemed successful.
 * Returns 1 if the method completed any authentication list or 0 otherwise.
 */
int
648 649
auth2_update_methods_lists(Authctxt *authctxt, const char *method,
    const char *submethod)
650 651 652 653 654
{
	u_int i, found = 0;

	debug3("%s: updating methods list after \"%s\"", __func__, method);
	for (i = 0; i < authctxt->num_auth_methods; i++) {
655 656
		if (!remove_method(&(authctxt->auth_methods[i]), method,
		    submethod))
657 658 659 660 661 662 663 664 665 666 667 668 669 670 671
			continue;
		found = 1;
		if (*authctxt->auth_methods[i] == '\0') {
			debug2("authentication methods list %d complete", i);
			return 1;
		}
		debug3("authentication methods list %d remaining: \"%s\"",
		    i, authctxt->auth_methods[i]);
	}
	/* This should not happen, but would be bad if it did */
	if (!found)
		fatal("%s: method not in AuthenticationMethods", __func__);
	return 0;
}

djm@openbsd.org's avatar
djm@openbsd.org committed
672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710
/* Reset method-specific information */
void auth2_authctxt_reset_info(Authctxt *authctxt)
{
	sshkey_free(authctxt->auth_method_key);
	free(authctxt->auth_method_info);
	authctxt->auth_method_key = NULL;
	authctxt->auth_method_info = NULL;
}

/* Record auth method-specific information for logs */
void
auth2_record_info(Authctxt *authctxt, const char *fmt, ...)
{
	va_list ap;
        int i;

	free(authctxt->auth_method_info);
	authctxt->auth_method_info = NULL;

	va_start(ap, fmt);
	i = vasprintf(&authctxt->auth_method_info, fmt, ap);
	va_end(ap);

	if (i < 0 || authctxt->auth_method_info == NULL)
		fatal("%s: vasprintf failed", __func__);
}

/*
 * Records a public key used in authentication. This is used for logging
 * and to ensure that the same key is not subsequently accepted again for
 * multiple authentication.
 */
void
auth2_record_key(Authctxt *authctxt, int authenticated,
    const struct sshkey *key)
{
	struct sshkey **tmp, *dup;
	int r;

711
	if ((r = sshkey_from_private(key, &dup)) != 0)
djm@openbsd.org's avatar
djm@openbsd.org committed
712 713 714 715 716 717 718 719
		fatal("%s: copy key: %s", __func__, ssh_err(r));
	sshkey_free(authctxt->auth_method_key);
	authctxt->auth_method_key = dup;

	if (!authenticated)
		return;

	/* If authenticated, make sure we don't accept this key again */
720
	if ((r = sshkey_from_private(key, &dup)) != 0)
djm@openbsd.org's avatar
djm@openbsd.org committed
721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795
		fatal("%s: copy key: %s", __func__, ssh_err(r));
	if (authctxt->nprev_keys >= INT_MAX ||
	    (tmp = recallocarray(authctxt->prev_keys, authctxt->nprev_keys,
	    authctxt->nprev_keys + 1, sizeof(*authctxt->prev_keys))) == NULL)
		fatal("%s: reallocarray failed", __func__);
	authctxt->prev_keys = tmp;
	authctxt->prev_keys[authctxt->nprev_keys] = dup;
	authctxt->nprev_keys++;

}

/* Checks whether a key has already been previously used for authentication */
int
auth2_key_already_used(Authctxt *authctxt, const struct sshkey *key)
{
	u_int i;
	char *fp;

	for (i = 0; i < authctxt->nprev_keys; i++) {
		if (sshkey_equal_public(key, authctxt->prev_keys[i])) {
			fp = sshkey_fingerprint(authctxt->prev_keys[i],
			    options.fingerprint_hash, SSH_FP_DEFAULT);
			debug3("%s: key already used: %s %s", __func__,
			    sshkey_type(authctxt->prev_keys[i]),
			    fp == NULL ? "UNKNOWN" : fp);
			free(fp);
			return 1;
		}
	}
	return 0;
}

/*
 * Updates authctxt->session_info with details of authentication. Should be
 * whenever an authentication method succeeds.
 */
void
auth2_update_session_info(Authctxt *authctxt, const char *method,
    const char *submethod)
{
	int r;

	if (authctxt->session_info == NULL) {
		if ((authctxt->session_info = sshbuf_new()) == NULL)
			fatal("%s: sshbuf_new", __func__);
	}

	/* Append method[/submethod] */
	if ((r = sshbuf_putf(authctxt->session_info, "%s%s%s",
	    method, submethod == NULL ? "" : "/",
	    submethod == NULL ? "" : submethod)) != 0)
		fatal("%s: append method: %s", __func__, ssh_err(r));

	/* Append key if present */
	if (authctxt->auth_method_key != NULL) {
		if ((r = sshbuf_put_u8(authctxt->session_info, ' ')) != 0 ||
		    (r = sshkey_format_text(authctxt->auth_method_key,
		    authctxt->session_info)) != 0)
			fatal("%s: append key: %s", __func__, ssh_err(r));
	}

	if (authctxt->auth_method_info != NULL) {
		/* Ensure no ambiguity here */
		if (strchr(authctxt->auth_method_info, '\n') != NULL)
			fatal("%s: auth_method_info contains \\n", __func__);
		if ((r = sshbuf_put_u8(authctxt->session_info, ' ')) != 0 ||
		    (r = sshbuf_putf(authctxt->session_info, "%s",
		    authctxt->auth_method_info)) != 0) {
			fatal("%s: append method info: %s",
			    __func__, ssh_err(r));
		}
	}
	if ((r = sshbuf_put_u8(authctxt->session_info, '\n')) != 0)
		fatal("%s: append: %s", __func__, ssh_err(r));
}
796