1323136Sdes/* $OpenBSD: auth2-pubkey.c,v 1.62 2017/01/30 01:03:00 djm Exp $ */
298675Sdes/*
398675Sdes * Copyright (c) 2000 Markus Friedl.  All rights reserved.
498675Sdes *
598675Sdes * Redistribution and use in source and binary forms, with or without
698675Sdes * modification, are permitted provided that the following conditions
798675Sdes * are met:
898675Sdes * 1. Redistributions of source code must retain the above copyright
998675Sdes *    notice, this list of conditions and the following disclaimer.
1098675Sdes * 2. Redistributions in binary form must reproduce the above copyright
1198675Sdes *    notice, this list of conditions and the following disclaimer in the
1298675Sdes *    documentation and/or other materials provided with the distribution.
1398675Sdes *
1498675Sdes * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
1598675Sdes * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
1698675Sdes * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
1798675Sdes * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
1898675Sdes * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
1998675Sdes * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
2098675Sdes * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
2198675Sdes * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
2298675Sdes * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
2398675Sdes * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2498675Sdes */
2598675Sdes
2698675Sdes#include "includes.h"
2798675Sdes
28162852Sdes#include <sys/types.h>
29162852Sdes#include <sys/stat.h>
30248619Sdes#include <sys/wait.h>
31162852Sdes
32248619Sdes#include <errno.h>
33181111Sdes#include <fcntl.h>
34248619Sdes#ifdef HAVE_PATHS_H
35248619Sdes# include <paths.h>
36248619Sdes#endif
37162852Sdes#include <pwd.h>
38248619Sdes#include <signal.h>
39162852Sdes#include <stdio.h>
40162852Sdes#include <stdarg.h>
41204917Sdes#include <string.h>
42204917Sdes#include <time.h>
43181111Sdes#include <unistd.h>
44294332Sdes#include <limits.h>
45162852Sdes
46162852Sdes#include "xmalloc.h"
47146998Sdes#include "ssh.h"
4898675Sdes#include "ssh2.h"
4998675Sdes#include "packet.h"
5098675Sdes#include "buffer.h"
5198675Sdes#include "log.h"
52294328Sdes#include "misc.h"
5398675Sdes#include "servconf.h"
5498675Sdes#include "compat.h"
55162852Sdes#include "key.h"
56162852Sdes#include "hostfile.h"
5798675Sdes#include "auth.h"
5898675Sdes#include "pathnames.h"
5998675Sdes#include "uidswap.h"
6098675Sdes#include "auth-options.h"
6198675Sdes#include "canohost.h"
62162852Sdes#ifdef GSSAPI
63162852Sdes#include "ssh-gss.h"
64162852Sdes#endif
6598675Sdes#include "monitor_wrap.h"
66204917Sdes#include "authfile.h"
67215116Sdes#include "match.h"
68294336Sdes#include "ssherr.h"
69294336Sdes#include "channels.h" /* XXX for session.h */
70294336Sdes#include "session.h" /* XXX for child_set_env(); refactor? */
7198675Sdes
7298675Sdes/* import */
7398675Sdesextern ServerOptions options;
7498675Sdesextern u_char *session_id2;
75124208Sdesextern u_int session_id2_len;
7698675Sdes
7798675Sdesstatic int
7898675Sdesuserauth_pubkey(Authctxt *authctxt)
7998675Sdes{
8098675Sdes	Buffer b;
8198675Sdes	Key *key = NULL;
82296633Sdes	char *pkalg, *userstyle, *fp = NULL;
8398675Sdes	u_char *pkblob, *sig;
8498675Sdes	u_int alen, blen, slen;
8598675Sdes	int have_sig, pktype;
8698675Sdes	int authenticated = 0;
8798675Sdes
8898675Sdes	if (!authctxt->valid) {
89296633Sdes		debug2("%s: disabled because of invalid user", __func__);
9098675Sdes		return 0;
9198675Sdes	}
9298675Sdes	have_sig = packet_get_char();
9398675Sdes	if (datafellows & SSH_BUG_PKAUTH) {
94296633Sdes		debug2("%s: SSH_BUG_PKAUTH", __func__);
9598675Sdes		/* no explicit pkalg given */
9698675Sdes		pkblob = packet_get_string(&blen);
9798675Sdes		buffer_init(&b);
9898675Sdes		buffer_append(&b, pkblob, blen);
9998675Sdes		/* so we have to extract the pkalg from the pkblob */
10098675Sdes		pkalg = buffer_get_string(&b, &alen);
10198675Sdes		buffer_free(&b);
10298675Sdes	} else {
10398675Sdes		pkalg = packet_get_string(&alen);
10498675Sdes		pkblob = packet_get_string(&blen);
10598675Sdes	}
10698675Sdes	pktype = key_type_from_name(pkalg);
10798675Sdes	if (pktype == KEY_UNSPEC) {
10898675Sdes		/* this is perfectly legal */
109296633Sdes		logit("%s: unsupported public key algorithm: %s",
110296633Sdes		    __func__, pkalg);
11198675Sdes		goto done;
11298675Sdes	}
11398675Sdes	key = key_from_blob(pkblob, blen);
11498675Sdes	if (key == NULL) {
115296633Sdes		error("%s: cannot decode key: %s", __func__, pkalg);
11698675Sdes		goto done;
11798675Sdes	}
11898675Sdes	if (key->type != pktype) {
119296633Sdes		error("%s: type mismatch for decoded key "
120296633Sdes		    "(received %d, expected %d)", __func__, key->type, pktype);
12198675Sdes		goto done;
12298675Sdes	}
123261320Sdes	if (key_type_plain(key->type) == KEY_RSA &&
124261320Sdes	    (datafellows & SSH_BUG_RSASIGMD5) != 0) {
125261320Sdes		logit("Refusing RSA key because client uses unsafe "
126261320Sdes		    "signature scheme");
127261320Sdes		goto done;
128261320Sdes	}
129296633Sdes	fp = sshkey_fingerprint(key, options.fingerprint_hash, SSH_FP_DEFAULT);
130294332Sdes	if (auth2_userkey_already_used(authctxt, key)) {
131294332Sdes		logit("refusing previously-used %s key", key_type(key));
132294332Sdes		goto done;
133294332Sdes	}
134294336Sdes	if (match_pattern_list(sshkey_ssh_name(key),
135294336Sdes	    options.pubkey_key_types, 0) != 1) {
136294332Sdes		logit("%s: key type %s not in PubkeyAcceptedKeyTypes",
137294332Sdes		    __func__, sshkey_ssh_name(key));
138294332Sdes		goto done;
139294332Sdes	}
140294332Sdes
14198675Sdes	if (have_sig) {
142296633Sdes		debug3("%s: have signature for %s %s",
143296633Sdes		    __func__, sshkey_type(key), fp);
14498675Sdes		sig = packet_get_string(&slen);
14598675Sdes		packet_check_eom();
14698675Sdes		buffer_init(&b);
14798675Sdes		if (datafellows & SSH_OLD_SESSIONID) {
14898675Sdes			buffer_append(&b, session_id2, session_id2_len);
14998675Sdes		} else {
15098675Sdes			buffer_put_string(&b, session_id2, session_id2_len);
15198675Sdes		}
15298675Sdes		/* reconstruct packet */
15398675Sdes		buffer_put_char(&b, SSH2_MSG_USERAUTH_REQUEST);
154255767Sdes		xasprintf(&userstyle, "%s%s%s", authctxt->user,
155255767Sdes		    authctxt->style ? ":" : "",
156255767Sdes		    authctxt->style ? authctxt->style : "");
157255767Sdes		buffer_put_cstring(&b, userstyle);
158255767Sdes		free(userstyle);
15998675Sdes		buffer_put_cstring(&b,
16098675Sdes		    datafellows & SSH_BUG_PKSERVICE ?
16198675Sdes		    "ssh-userauth" :
16298675Sdes		    authctxt->service);
16398675Sdes		if (datafellows & SSH_BUG_PKAUTH) {
16498675Sdes			buffer_put_char(&b, have_sig);
16598675Sdes		} else {
16698675Sdes			buffer_put_cstring(&b, "publickey");
16798675Sdes			buffer_put_char(&b, have_sig);
16898675Sdes			buffer_put_cstring(&b, pkalg);
16998675Sdes		}
17098675Sdes		buffer_put_string(&b, pkblob, blen);
17198675Sdes#ifdef DEBUG_PK
17298675Sdes		buffer_dump(&b);
17398675Sdes#endif
174255767Sdes		pubkey_auth_info(authctxt, key, NULL);
175255767Sdes
17698675Sdes		/* test for correct signature */
17798675Sdes		authenticated = 0;
178294336Sdes		if (PRIVSEP(user_key_allowed(authctxt->pw, key, 1)) &&
17998675Sdes		    PRIVSEP(key_verify(key, sig, slen, buffer_ptr(&b),
180294332Sdes		    buffer_len(&b))) == 1) {
18198675Sdes			authenticated = 1;
182294332Sdes			/* Record the successful key to prevent reuse */
183294332Sdes			auth2_record_userkey(authctxt, key);
184294332Sdes			key = NULL; /* Don't free below */
185294332Sdes		}
186126274Sdes		buffer_free(&b);
187255767Sdes		free(sig);
18898675Sdes	} else {
189296633Sdes		debug("%s: test whether pkalg/pkblob are acceptable for %s %s",
190296633Sdes		    __func__, sshkey_type(key), fp);
19198675Sdes		packet_check_eom();
19298675Sdes
19398675Sdes		/* XXX fake reply and always send PK_OK ? */
19498675Sdes		/*
19598675Sdes		 * XXX this allows testing whether a user is allowed
19698675Sdes		 * to login: if you happen to have a valid pubkey this
19798675Sdes		 * message is sent. the message is NEVER sent at all
19898675Sdes		 * if a user is not allowed to login. is this an
19998675Sdes		 * issue? -markus
20098675Sdes		 */
201294336Sdes		if (PRIVSEP(user_key_allowed(authctxt->pw, key, 0))) {
20298675Sdes			packet_start(SSH2_MSG_USERAUTH_PK_OK);
20398675Sdes			packet_put_string(pkalg, alen);
20498675Sdes			packet_put_string(pkblob, blen);
20598675Sdes			packet_send();
20698675Sdes			packet_write_wait();
20798675Sdes			authctxt->postponed = 1;
20898675Sdes		}
20998675Sdes	}
21098675Sdes	if (authenticated != 1)
21198675Sdes		auth_clear_options();
21298675Sdesdone:
213296633Sdes	debug2("%s: authenticated %d pkalg %s", __func__, authenticated, pkalg);
21498675Sdes	if (key != NULL)
21598675Sdes		key_free(key);
216255767Sdes	free(pkalg);
217255767Sdes	free(pkblob);
218296633Sdes	free(fp);
21998675Sdes	return authenticated;
22098675Sdes}
22198675Sdes
222255767Sdesvoid
223255767Sdespubkey_auth_info(Authctxt *authctxt, const Key *key, const char *fmt, ...)
224255767Sdes{
225255767Sdes	char *fp, *extra;
226255767Sdes	va_list ap;
227255767Sdes	int i;
228255767Sdes
229255767Sdes	extra = NULL;
230255767Sdes	if (fmt != NULL) {
231255767Sdes		va_start(ap, fmt);
232255767Sdes		i = vasprintf(&extra, fmt, ap);
233255767Sdes		va_end(ap);
234255767Sdes		if (i < 0 || extra == NULL)
235255767Sdes			fatal("%s: vasprintf failed", __func__);
236255767Sdes	}
237255767Sdes
238255767Sdes	if (key_is_cert(key)) {
239294332Sdes		fp = sshkey_fingerprint(key->cert->signature_key,
240294332Sdes		    options.fingerprint_hash, SSH_FP_DEFAULT);
241255767Sdes		auth_info(authctxt, "%s ID %s (serial %llu) CA %s %s%s%s",
242255767Sdes		    key_type(key), key->cert->key_id,
243255767Sdes		    (unsigned long long)key->cert->serial,
244294332Sdes		    key_type(key->cert->signature_key),
245294332Sdes		    fp == NULL ? "(null)" : fp,
246255767Sdes		    extra == NULL ? "" : ", ", extra == NULL ? "" : extra);
247255767Sdes		free(fp);
248255767Sdes	} else {
249294332Sdes		fp = sshkey_fingerprint(key, options.fingerprint_hash,
250294332Sdes		    SSH_FP_DEFAULT);
251294332Sdes		auth_info(authctxt, "%s %s%s%s", key_type(key),
252294332Sdes		    fp == NULL ? "(null)" : fp,
253255767Sdes		    extra == NULL ? "" : ", ", extra == NULL ? "" : extra);
254255767Sdes		free(fp);
255255767Sdes	}
256255767Sdes	free(extra);
257255767Sdes}
258255767Sdes
259294336Sdes/*
260294336Sdes * Splits 's' into an argument vector. Handles quoted string and basic
261294336Sdes * escape characters (\\, \", \'). Caller must free the argument vector
262294336Sdes * and its members.
263294336Sdes */
264215116Sdesstatic int
265294336Sdessplit_argv(const char *s, int *argcp, char ***argvp)
266294336Sdes{
267294336Sdes	int r = SSH_ERR_INTERNAL_ERROR;
268294336Sdes	int argc = 0, quote, i, j;
269294336Sdes	char *arg, **argv = xcalloc(1, sizeof(*argv));
270294336Sdes
271294336Sdes	*argvp = NULL;
272294336Sdes	*argcp = 0;
273294336Sdes
274294336Sdes	for (i = 0; s[i] != '\0'; i++) {
275294336Sdes		/* Skip leading whitespace */
276294336Sdes		if (s[i] == ' ' || s[i] == '\t')
277294336Sdes			continue;
278294336Sdes
279294336Sdes		/* Start of a token */
280294336Sdes		quote = 0;
281294336Sdes		if (s[i] == '\\' &&
282294336Sdes		    (s[i + 1] == '\'' || s[i + 1] == '\"' || s[i + 1] == '\\'))
283294336Sdes			i++;
284294336Sdes		else if (s[i] == '\'' || s[i] == '"')
285294336Sdes			quote = s[i++];
286294336Sdes
287294336Sdes		argv = xreallocarray(argv, (argc + 2), sizeof(*argv));
288294336Sdes		arg = argv[argc++] = xcalloc(1, strlen(s + i) + 1);
289294336Sdes		argv[argc] = NULL;
290294336Sdes
291294336Sdes		/* Copy the token in, removing escapes */
292294336Sdes		for (j = 0; s[i] != '\0'; i++) {
293294336Sdes			if (s[i] == '\\') {
294294336Sdes				if (s[i + 1] == '\'' ||
295294336Sdes				    s[i + 1] == '\"' ||
296294336Sdes				    s[i + 1] == '\\') {
297294336Sdes					i++; /* Skip '\' */
298294336Sdes					arg[j++] = s[i];
299294336Sdes				} else {
300294336Sdes					/* Unrecognised escape */
301294336Sdes					arg[j++] = s[i];
302294336Sdes				}
303294336Sdes			} else if (quote == 0 && (s[i] == ' ' || s[i] == '\t'))
304294336Sdes				break; /* done */
305294336Sdes			else if (quote != 0 && s[i] == quote)
306294336Sdes				break; /* done */
307294336Sdes			else
308294336Sdes				arg[j++] = s[i];
309294336Sdes		}
310294336Sdes		if (s[i] == '\0') {
311294336Sdes			if (quote != 0) {
312294336Sdes				/* Ran out of string looking for close quote */
313294336Sdes				r = SSH_ERR_INVALID_FORMAT;
314294336Sdes				goto out;
315294336Sdes			}
316294336Sdes			break;
317294336Sdes		}
318294336Sdes	}
319294336Sdes	/* Success */
320294336Sdes	*argcp = argc;
321294336Sdes	*argvp = argv;
322294336Sdes	argc = 0;
323294336Sdes	argv = NULL;
324294336Sdes	r = 0;
325294336Sdes out:
326294336Sdes	if (argc != 0 && argv != NULL) {
327294336Sdes		for (i = 0; i < argc; i++)
328294336Sdes			free(argv[i]);
329294336Sdes		free(argv);
330294336Sdes	}
331294336Sdes	return r;
332294336Sdes}
333294336Sdes
334294336Sdes/*
335294336Sdes * Reassemble an argument vector into a string, quoting and escaping as
336294336Sdes * necessary. Caller must free returned string.
337294336Sdes */
338294336Sdesstatic char *
339294336Sdesassemble_argv(int argc, char **argv)
340294336Sdes{
341294336Sdes	int i, j, ws, r;
342294336Sdes	char c, *ret;
343294336Sdes	struct sshbuf *buf, *arg;
344294336Sdes
345294336Sdes	if ((buf = sshbuf_new()) == NULL || (arg = sshbuf_new()) == NULL)
346294336Sdes		fatal("%s: sshbuf_new failed", __func__);
347294336Sdes
348294336Sdes	for (i = 0; i < argc; i++) {
349294336Sdes		ws = 0;
350294336Sdes		sshbuf_reset(arg);
351294336Sdes		for (j = 0; argv[i][j] != '\0'; j++) {
352294336Sdes			r = 0;
353294336Sdes			c = argv[i][j];
354294336Sdes			switch (c) {
355294336Sdes			case ' ':
356294336Sdes			case '\t':
357294336Sdes				ws = 1;
358294336Sdes				r = sshbuf_put_u8(arg, c);
359294336Sdes				break;
360294336Sdes			case '\\':
361294336Sdes			case '\'':
362294336Sdes			case '"':
363294336Sdes				if ((r = sshbuf_put_u8(arg, '\\')) != 0)
364294336Sdes					break;
365294336Sdes				/* FALLTHROUGH */
366294336Sdes			default:
367294336Sdes				r = sshbuf_put_u8(arg, c);
368294336Sdes				break;
369294336Sdes			}
370294336Sdes			if (r != 0)
371294336Sdes				fatal("%s: sshbuf_put_u8: %s",
372294336Sdes				    __func__, ssh_err(r));
373294336Sdes		}
374294336Sdes		if ((i != 0 && (r = sshbuf_put_u8(buf, ' ')) != 0) ||
375294336Sdes		    (ws != 0 && (r = sshbuf_put_u8(buf, '"')) != 0) ||
376294336Sdes		    (r = sshbuf_putb(buf, arg)) != 0 ||
377294336Sdes		    (ws != 0 && (r = sshbuf_put_u8(buf, '"')) != 0))
378294336Sdes			fatal("%s: buffer error: %s", __func__, ssh_err(r));
379294336Sdes	}
380294336Sdes	if ((ret = malloc(sshbuf_len(buf) + 1)) == NULL)
381294336Sdes		fatal("%s: malloc failed", __func__);
382294336Sdes	memcpy(ret, sshbuf_ptr(buf), sshbuf_len(buf));
383294336Sdes	ret[sshbuf_len(buf)] = '\0';
384294336Sdes	sshbuf_free(buf);
385294336Sdes	sshbuf_free(arg);
386294336Sdes	return ret;
387294336Sdes}
388294336Sdes
389294336Sdes/*
390294336Sdes * Runs command in a subprocess. Returns pid on success and a FILE* to the
391294336Sdes * subprocess' stdout or 0 on failure.
392294336Sdes * NB. "command" is only used for logging.
393294336Sdes */
394294336Sdesstatic pid_t
395294336Sdessubprocess(const char *tag, struct passwd *pw, const char *command,
396294336Sdes    int ac, char **av, FILE **child)
397294336Sdes{
398294336Sdes	FILE *f;
399294336Sdes	struct stat st;
400294336Sdes	int devnull, p[2], i;
401294336Sdes	pid_t pid;
402294336Sdes	char *cp, errmsg[512];
403294336Sdes	u_int envsize;
404294336Sdes	char **child_env;
405294336Sdes
406294336Sdes	*child = NULL;
407294336Sdes
408294336Sdes	debug3("%s: %s command \"%s\" running as %s", __func__,
409294336Sdes	    tag, command, pw->pw_name);
410294336Sdes
411294336Sdes	/* Verify the path exists and is safe-ish to execute */
412294336Sdes	if (*av[0] != '/') {
413294336Sdes		error("%s path is not absolute", tag);
414294336Sdes		return 0;
415294336Sdes	}
416294336Sdes	temporarily_use_uid(pw);
417294336Sdes	if (stat(av[0], &st) < 0) {
418294336Sdes		error("Could not stat %s \"%s\": %s", tag,
419294336Sdes		    av[0], strerror(errno));
420294336Sdes		restore_uid();
421294336Sdes		return 0;
422294336Sdes	}
423294336Sdes	if (auth_secure_path(av[0], &st, NULL, 0,
424294336Sdes	    errmsg, sizeof(errmsg)) != 0) {
425294336Sdes		error("Unsafe %s \"%s\": %s", tag, av[0], errmsg);
426294336Sdes		restore_uid();
427294336Sdes		return 0;
428294336Sdes	}
429294336Sdes
430294336Sdes	/*
431294336Sdes	 * Run the command; stderr is left in place, stdout is the
432294336Sdes	 * authorized_keys output.
433294336Sdes	 */
434294336Sdes	if (pipe(p) != 0) {
435294336Sdes		error("%s: pipe: %s", tag, strerror(errno));
436294336Sdes		restore_uid();
437294336Sdes		return 0;
438294336Sdes	}
439294336Sdes
440294336Sdes	/*
441294336Sdes	 * Don't want to call this in the child, where it can fatal() and
442294336Sdes	 * run cleanup_exit() code.
443294336Sdes	 */
444294336Sdes	restore_uid();
445294336Sdes
446294336Sdes	switch ((pid = fork())) {
447294336Sdes	case -1: /* error */
448294336Sdes		error("%s: fork: %s", tag, strerror(errno));
449294336Sdes		close(p[0]);
450294336Sdes		close(p[1]);
451294336Sdes		return 0;
452294336Sdes	case 0: /* child */
453294336Sdes		/* Prepare a minimal environment for the child. */
454294336Sdes		envsize = 5;
455294336Sdes		child_env = xcalloc(sizeof(*child_env), envsize);
456294336Sdes		child_set_env(&child_env, &envsize, "PATH", _PATH_STDPATH);
457294336Sdes		child_set_env(&child_env, &envsize, "USER", pw->pw_name);
458294336Sdes		child_set_env(&child_env, &envsize, "LOGNAME", pw->pw_name);
459294336Sdes		child_set_env(&child_env, &envsize, "HOME", pw->pw_dir);
460294336Sdes		if ((cp = getenv("LANG")) != NULL)
461294336Sdes			child_set_env(&child_env, &envsize, "LANG", cp);
462294336Sdes
463294336Sdes		for (i = 0; i < NSIG; i++)
464294336Sdes			signal(i, SIG_DFL);
465294336Sdes
466294336Sdes		if ((devnull = open(_PATH_DEVNULL, O_RDWR)) == -1) {
467294336Sdes			error("%s: open %s: %s", tag, _PATH_DEVNULL,
468294336Sdes			    strerror(errno));
469294336Sdes			_exit(1);
470294336Sdes		}
471294336Sdes		/* Keep stderr around a while longer to catch errors */
472294336Sdes		if (dup2(devnull, STDIN_FILENO) == -1 ||
473294336Sdes		    dup2(p[1], STDOUT_FILENO) == -1) {
474294336Sdes			error("%s: dup2: %s", tag, strerror(errno));
475294336Sdes			_exit(1);
476294336Sdes		}
477294336Sdes		closefrom(STDERR_FILENO + 1);
478294336Sdes
479294336Sdes		/* Don't use permanently_set_uid() here to avoid fatal() */
480294336Sdes		if (setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) != 0) {
481294336Sdes			error("%s: setresgid %u: %s", tag, (u_int)pw->pw_gid,
482294336Sdes			    strerror(errno));
483294336Sdes			_exit(1);
484294336Sdes		}
485294336Sdes		if (setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) != 0) {
486294336Sdes			error("%s: setresuid %u: %s", tag, (u_int)pw->pw_uid,
487294336Sdes			    strerror(errno));
488294336Sdes			_exit(1);
489294336Sdes		}
490294336Sdes		/* stdin is pointed to /dev/null at this point */
491294336Sdes		if (dup2(STDIN_FILENO, STDERR_FILENO) == -1) {
492294336Sdes			error("%s: dup2: %s", tag, strerror(errno));
493294336Sdes			_exit(1);
494294336Sdes		}
495294336Sdes
496294336Sdes		execve(av[0], av, child_env);
497294336Sdes		error("%s exec \"%s\": %s", tag, command, strerror(errno));
498294336Sdes		_exit(127);
499294336Sdes	default: /* parent */
500294336Sdes		break;
501294336Sdes	}
502294336Sdes
503294336Sdes	close(p[1]);
504294336Sdes	if ((f = fdopen(p[0], "r")) == NULL) {
505294336Sdes		error("%s: fdopen: %s", tag, strerror(errno));
506294336Sdes		close(p[0]);
507294336Sdes		/* Don't leave zombie child */
508294336Sdes		kill(pid, SIGTERM);
509294336Sdes		while (waitpid(pid, NULL, 0) == -1 && errno == EINTR)
510294336Sdes			;
511294336Sdes		return 0;
512294336Sdes	}
513294336Sdes	/* Success */
514294336Sdes	debug3("%s: %s pid %ld", __func__, tag, (long)pid);
515294336Sdes	*child = f;
516294336Sdes	return pid;
517294336Sdes}
518294336Sdes
519294336Sdes/* Returns 0 if pid exited cleanly, non-zero otherwise */
520294336Sdesstatic int
521294336Sdesexited_cleanly(pid_t pid, const char *tag, const char *cmd)
522294336Sdes{
523294336Sdes	int status;
524294336Sdes
525294336Sdes	while (waitpid(pid, &status, 0) == -1) {
526294336Sdes		if (errno != EINTR) {
527294336Sdes			error("%s: waitpid: %s", tag, strerror(errno));
528294336Sdes			return -1;
529294336Sdes		}
530294336Sdes	}
531294336Sdes	if (WIFSIGNALED(status)) {
532294336Sdes		error("%s %s exited on signal %d", tag, cmd, WTERMSIG(status));
533294336Sdes		return -1;
534294336Sdes	} else if (WEXITSTATUS(status) != 0) {
535294336Sdes		error("%s %s failed, status %d", tag, cmd, WEXITSTATUS(status));
536294336Sdes		return -1;
537294336Sdes	}
538294336Sdes	return 0;
539294336Sdes}
540294336Sdes
541294336Sdesstatic int
542294328Sdesmatch_principals_option(const char *principal_list, struct sshkey_cert *cert)
543215116Sdes{
544215116Sdes	char *result;
545215116Sdes	u_int i;
546215116Sdes
547215116Sdes	/* XXX percent_expand() sequences for authorized_principals? */
548215116Sdes
549215116Sdes	for (i = 0; i < cert->nprincipals; i++) {
550215116Sdes		if ((result = match_list(cert->principals[i],
551215116Sdes		    principal_list, NULL)) != NULL) {
552215116Sdes			debug3("matched principal from key options \"%.100s\"",
553215116Sdes			    result);
554255767Sdes			free(result);
555215116Sdes			return 1;
556215116Sdes		}
557215116Sdes	}
558215116Sdes	return 0;
559215116Sdes}
560215116Sdes
561215116Sdesstatic int
562294336Sdesprocess_principals(FILE *f, char *file, struct passwd *pw,
563323134Sdes    const struct sshkey_cert *cert)
564215116Sdes{
565215116Sdes	char line[SSH_MAX_PUBKEY_BYTES], *cp, *ep, *line_opts;
566215116Sdes	u_long linenum = 0;
567323136Sdes	u_int i, found_principal = 0;
568215116Sdes
569215116Sdes	while (read_keyfile_line(f, file, line, sizeof(line), &linenum) != -1) {
570323136Sdes		/* Always consume entire input */
571323136Sdes		if (found_principal)
572323136Sdes			continue;
573215116Sdes		/* Skip leading whitespace. */
574215116Sdes		for (cp = line; *cp == ' ' || *cp == '\t'; cp++)
575215116Sdes			;
576215116Sdes		/* Skip blank and comment lines. */
577215116Sdes		if ((ep = strchr(cp, '#')) != NULL)
578215116Sdes			*ep = '\0';
579215116Sdes		if (!*cp || *cp == '\n')
580215116Sdes			continue;
581215116Sdes		/* Trim trailing whitespace. */
582215116Sdes		ep = cp + strlen(cp) - 1;
583215116Sdes		while (ep > cp && (*ep == '\n' || *ep == ' ' || *ep == '\t'))
584215116Sdes			*ep-- = '\0';
585215116Sdes		/*
586215116Sdes		 * If the line has internal whitespace then assume it has
587215116Sdes		 * key options.
588215116Sdes		 */
589215116Sdes		line_opts = NULL;
590215116Sdes		if ((ep = strrchr(cp, ' ')) != NULL ||
591215116Sdes		    (ep = strrchr(cp, '\t')) != NULL) {
592215116Sdes			for (; *ep == ' ' || *ep == '\t'; ep++)
593221420Sdes				;
594215116Sdes			line_opts = cp;
595215116Sdes			cp = ep;
596215116Sdes		}
597215116Sdes		for (i = 0; i < cert->nprincipals; i++) {
598215116Sdes			if (strcmp(cp, cert->principals[i]) == 0) {
599294336Sdes				debug3("%s:%lu: matched principal \"%.100s\"",
600294336Sdes				    file == NULL ? "(command)" : file,
601294336Sdes				    linenum, cert->principals[i]);
602215116Sdes				if (auth_parse_options(pw, line_opts,
603215116Sdes				    file, linenum) != 1)
604215116Sdes					continue;
605323136Sdes				found_principal = 1;
606323136Sdes				continue;
607215116Sdes			}
608215116Sdes		}
609215116Sdes	}
610323136Sdes	return found_principal;
611294336Sdes}
612294336Sdes
613294336Sdesstatic int
614294336Sdesmatch_principals_file(char *file, struct passwd *pw, struct sshkey_cert *cert)
615294336Sdes{
616294336Sdes	FILE *f;
617294336Sdes	int success;
618294336Sdes
619294336Sdes	temporarily_use_uid(pw);
620294336Sdes	debug("trying authorized principals file %s", file);
621294336Sdes	if ((f = auth_openprincipals(file, pw, options.strict_modes)) == NULL) {
622294336Sdes		restore_uid();
623294336Sdes		return 0;
624294336Sdes	}
625294336Sdes	success = process_principals(f, file, pw, cert);
626215116Sdes	fclose(f);
627215116Sdes	restore_uid();
628294336Sdes	return success;
629248619Sdes}
630215116Sdes
631248619Sdes/*
632294336Sdes * Checks whether principal is allowed in output of command.
633294336Sdes * returns 1 if the principal is allowed or 0 otherwise.
634294336Sdes */
635294336Sdesstatic int
636323134Sdesmatch_principals_command(struct passwd *user_pw, const struct sshkey *key)
637294336Sdes{
638323134Sdes	const struct sshkey_cert *cert = key->cert;
639294336Sdes	FILE *f = NULL;
640323134Sdes	int r, ok, found_principal = 0;
641294336Sdes	struct passwd *pw;
642294336Sdes	int i, ac = 0, uid_swapped = 0;
643294336Sdes	pid_t pid;
644294336Sdes	char *tmp, *username = NULL, *command = NULL, **av = NULL;
645323134Sdes	char *ca_fp = NULL, *key_fp = NULL, *catext = NULL, *keytext = NULL;
646323134Sdes	char serial_s[16];
647294336Sdes	void (*osigchld)(int);
648294336Sdes
649294336Sdes	if (options.authorized_principals_command == NULL)
650294336Sdes		return 0;
651294336Sdes	if (options.authorized_principals_command_user == NULL) {
652294336Sdes		error("No user for AuthorizedPrincipalsCommand specified, "
653294336Sdes		    "skipping");
654294336Sdes		return 0;
655294336Sdes	}
656294336Sdes
657294336Sdes	/*
658294336Sdes	 * NB. all returns later this function should go via "out" to
659294336Sdes	 * ensure the original SIGCHLD handler is restored properly.
660294336Sdes	 */
661294336Sdes	osigchld = signal(SIGCHLD, SIG_DFL);
662294336Sdes
663294336Sdes	/* Prepare and verify the user for the command */
664294336Sdes	username = percent_expand(options.authorized_principals_command_user,
665294336Sdes	    "u", user_pw->pw_name, (char *)NULL);
666294336Sdes	pw = getpwnam(username);
667294336Sdes	if (pw == NULL) {
668294336Sdes		error("AuthorizedPrincipalsCommandUser \"%s\" not found: %s",
669294336Sdes		    username, strerror(errno));
670294336Sdes		goto out;
671294336Sdes	}
672294336Sdes
673294336Sdes	/* Turn the command into an argument vector */
674294336Sdes	if (split_argv(options.authorized_principals_command, &ac, &av) != 0) {
675294336Sdes		error("AuthorizedPrincipalsCommand \"%s\" contains "
676294336Sdes		    "invalid quotes", command);
677294336Sdes		goto out;
678294336Sdes	}
679294336Sdes	if (ac == 0) {
680294336Sdes		error("AuthorizedPrincipalsCommand \"%s\" yielded no arguments",
681294336Sdes		    command);
682294336Sdes		goto out;
683294336Sdes	}
684323134Sdes	if ((ca_fp = sshkey_fingerprint(cert->signature_key,
685323134Sdes	    options.fingerprint_hash, SSH_FP_DEFAULT)) == NULL) {
686323134Sdes		error("%s: sshkey_fingerprint failed", __func__);
687323134Sdes		goto out;
688323134Sdes	}
689323134Sdes	if ((key_fp = sshkey_fingerprint(key,
690323134Sdes	    options.fingerprint_hash, SSH_FP_DEFAULT)) == NULL) {
691323134Sdes		error("%s: sshkey_fingerprint failed", __func__);
692323134Sdes		goto out;
693323134Sdes	}
694323134Sdes	if ((r = sshkey_to_base64(cert->signature_key, &catext)) != 0) {
695323134Sdes		error("%s: sshkey_to_base64 failed: %s", __func__, ssh_err(r));
696323134Sdes		goto out;
697323134Sdes	}
698323134Sdes	if ((r = sshkey_to_base64(key, &keytext)) != 0) {
699323134Sdes		error("%s: sshkey_to_base64 failed: %s", __func__, ssh_err(r));
700323134Sdes		goto out;
701323134Sdes	}
702323134Sdes	snprintf(serial_s, sizeof(serial_s), "%llu",
703323134Sdes	    (unsigned long long)cert->serial);
704294336Sdes	for (i = 1; i < ac; i++) {
705294336Sdes		tmp = percent_expand(av[i],
706294336Sdes		    "u", user_pw->pw_name,
707294336Sdes		    "h", user_pw->pw_dir,
708323134Sdes		    "t", sshkey_ssh_name(key),
709323134Sdes		    "T", sshkey_ssh_name(cert->signature_key),
710323134Sdes		    "f", key_fp,
711323134Sdes		    "F", ca_fp,
712323134Sdes		    "k", keytext,
713323134Sdes		    "K", catext,
714323134Sdes		    "i", cert->key_id,
715323134Sdes		    "s", serial_s,
716294336Sdes		    (char *)NULL);
717294336Sdes		if (tmp == NULL)
718294336Sdes			fatal("%s: percent_expand failed", __func__);
719294336Sdes		free(av[i]);
720294336Sdes		av[i] = tmp;
721294336Sdes	}
722294336Sdes	/* Prepare a printable command for logs, etc. */
723294336Sdes	command = assemble_argv(ac, av);
724294336Sdes
725294336Sdes	if ((pid = subprocess("AuthorizedPrincipalsCommand", pw, command,
726294336Sdes	    ac, av, &f)) == 0)
727294336Sdes		goto out;
728294336Sdes
729294336Sdes	uid_swapped = 1;
730294336Sdes	temporarily_use_uid(pw);
731294336Sdes
732294336Sdes	ok = process_principals(f, NULL, pw, cert);
733294336Sdes
734323136Sdes	fclose(f);
735323136Sdes	f = NULL;
736323136Sdes
737294336Sdes	if (exited_cleanly(pid, "AuthorizedPrincipalsCommand", command) != 0)
738294336Sdes		goto out;
739294336Sdes
740294336Sdes	/* Read completed successfully */
741294336Sdes	found_principal = ok;
742294336Sdes out:
743294336Sdes	if (f != NULL)
744294336Sdes		fclose(f);
745294336Sdes	signal(SIGCHLD, osigchld);
746294336Sdes	for (i = 0; i < ac; i++)
747294336Sdes		free(av[i]);
748294336Sdes	free(av);
749294336Sdes	if (uid_swapped)
750294336Sdes		restore_uid();
751294336Sdes	free(command);
752294336Sdes	free(username);
753323134Sdes	free(ca_fp);
754323134Sdes	free(key_fp);
755323134Sdes	free(catext);
756323134Sdes	free(keytext);
757294336Sdes	return found_principal;
758294336Sdes}
759294336Sdes/*
760248619Sdes * Checks whether key is allowed in authorized_keys-format file,
761248619Sdes * returns 1 if the key is allowed or 0 otherwise.
762248619Sdes */
76398675Sdesstatic int
764248619Sdescheck_authkeys_file(FILE *f, char *file, Key* key, struct passwd *pw)
76598675Sdes{
766146998Sdes	char line[SSH_MAX_PUBKEY_BYTES];
76798675Sdes	int found_key = 0;
76898675Sdes	u_long linenum = 0;
76998675Sdes	Key *found;
77098675Sdes
77198675Sdes	found_key = 0;
77298675Sdes
773255767Sdes	found = NULL;
774146998Sdes	while (read_keyfile_line(f, file, line, sizeof(line), &linenum) != -1) {
775323134Sdes		char *cp, *key_options = NULL, *fp = NULL;
776323134Sdes		const char *reason = NULL;
777323134Sdes
778323136Sdes		/* Always consume entrire file */
779323136Sdes		if (found_key)
780323136Sdes			continue;
781255767Sdes		if (found != NULL)
782255767Sdes			key_free(found);
783255767Sdes		found = key_new(key_is_cert(key) ? KEY_UNSPEC : key->type);
784204917Sdes		auth_clear_options();
785204917Sdes
78698675Sdes		/* Skip leading whitespace, empty and comment lines. */
78798675Sdes		for (cp = line; *cp == ' ' || *cp == '\t'; cp++)
78898675Sdes			;
78998675Sdes		if (!*cp || *cp == '\n' || *cp == '#')
79098675Sdes			continue;
79198675Sdes
79298675Sdes		if (key_read(found, &cp) != 1) {
79398675Sdes			/* no key?  check if there are options for this key */
79498675Sdes			int quoted = 0;
79598675Sdes			debug2("user_key_allowed: check options: '%s'", cp);
796137015Sdes			key_options = cp;
79798675Sdes			for (; *cp && (quoted || (*cp != ' ' && *cp != '\t')); cp++) {
79898675Sdes				if (*cp == '\\' && cp[1] == '"')
79998675Sdes					cp++;	/* Skip both */
80098675Sdes				else if (*cp == '"')
80198675Sdes					quoted = !quoted;
80298675Sdes			}
80398675Sdes			/* Skip remaining whitespace. */
80498675Sdes			for (; *cp == ' ' || *cp == '\t'; cp++)
80598675Sdes				;
80698675Sdes			if (key_read(found, &cp) != 1) {
80798675Sdes				debug2("user_key_allowed: advance: '%s'", cp);
80898675Sdes				/* still no key?  advance to next line*/
80998675Sdes				continue;
81098675Sdes			}
81198675Sdes		}
812215116Sdes		if (key_is_cert(key)) {
813215116Sdes			if (!key_equal(found, key->cert->signature_key))
814215116Sdes				continue;
815215116Sdes			if (auth_parse_options(pw, key_options, file,
816215116Sdes			    linenum) != 1)
817215116Sdes				continue;
818204917Sdes			if (!key_is_cert_authority)
819204917Sdes				continue;
820294332Sdes			if ((fp = sshkey_fingerprint(found,
821294332Sdes			    options.fingerprint_hash, SSH_FP_DEFAULT)) == NULL)
822294332Sdes				continue;
823207319Sdes			debug("matching CA found: file %s, line %lu, %s %s",
824207319Sdes			    file, linenum, key_type(found), fp);
825215116Sdes			/*
826215116Sdes			 * If the user has specified a list of principals as
827215116Sdes			 * a key option, then prefer that list to matching
828215116Sdes			 * their username in the certificate principals list.
829215116Sdes			 */
830215116Sdes			if (authorized_principals != NULL &&
831215116Sdes			    !match_principals_option(authorized_principals,
832215116Sdes			    key->cert)) {
833215116Sdes				reason = "Certificate does not contain an "
834215116Sdes				    "authorized principal";
835215116Sdes fail_reason:
836255767Sdes				free(fp);
837204917Sdes				error("%s", reason);
838204917Sdes				auth_debug_add("%s", reason);
839204917Sdes				continue;
840204917Sdes			}
841215116Sdes			if (key_cert_check_authority(key, 0, 0,
842215116Sdes			    authorized_principals == NULL ? pw->pw_name : NULL,
843215116Sdes			    &reason) != 0)
844215116Sdes				goto fail_reason;
845323134Sdes			if (auth_cert_options(key, pw, &reason) != 0)
846323134Sdes				goto fail_reason;
847296633Sdes			verbose("Accepted certificate ID \"%s\" (serial %llu) "
848207319Sdes			    "signed by %s CA %s via %s", key->cert->key_id,
849296633Sdes			    (unsigned long long)key->cert->serial,
850207319Sdes			    key_type(found), fp, file);
851255767Sdes			free(fp);
85298675Sdes			found_key = 1;
853204917Sdes			break;
854215116Sdes		} else if (key_equal(found, key)) {
855215116Sdes			if (auth_parse_options(pw, key_options, file,
856215116Sdes			    linenum) != 1)
857215116Sdes				continue;
858215116Sdes			if (key_is_cert_authority)
859215116Sdes				continue;
860294332Sdes			if ((fp = sshkey_fingerprint(found,
861294332Sdes			    options.fingerprint_hash, SSH_FP_DEFAULT)) == NULL)
862294332Sdes				continue;
863255767Sdes			debug("matching key found: file %s, line %lu %s %s",
864255767Sdes			    file, linenum, key_type(found), fp);
865255767Sdes			free(fp);
866294332Sdes			found_key = 1;
867323136Sdes			continue;
86898675Sdes		}
86998675Sdes	}
870255767Sdes	if (found != NULL)
871255767Sdes		key_free(found);
87298675Sdes	if (!found_key)
87398675Sdes		debug2("key not found");
87498675Sdes	return found_key;
87598675Sdes}
87698675Sdes
877204917Sdes/* Authenticate a certificate key against TrustedUserCAKeys */
878204917Sdesstatic int
879204917Sdesuser_cert_trusted_ca(struct passwd *pw, Key *key)
880204917Sdes{
881215116Sdes	char *ca_fp, *principals_file = NULL;
882204917Sdes	const char *reason;
883294336Sdes	int ret = 0, found_principal = 0, use_authorized_principals;
884204917Sdes
885204917Sdes	if (!key_is_cert(key) || options.trusted_user_ca_keys == NULL)
886204917Sdes		return 0;
887204917Sdes
888294332Sdes	if ((ca_fp = sshkey_fingerprint(key->cert->signature_key,
889294332Sdes	    options.fingerprint_hash, SSH_FP_DEFAULT)) == NULL)
890294332Sdes		return 0;
891204917Sdes
892294332Sdes	if (sshkey_in_file(key->cert->signature_key,
893294332Sdes	    options.trusted_user_ca_keys, 1, 0) != 0) {
894204917Sdes		debug2("%s: CA %s %s is not listed in %s", __func__,
895204917Sdes		    key_type(key->cert->signature_key), ca_fp,
896204917Sdes		    options.trusted_user_ca_keys);
897204917Sdes		goto out;
898204917Sdes	}
899215116Sdes	/*
900215116Sdes	 * If AuthorizedPrincipals is in use, then compare the certificate
901215116Sdes	 * principals against the names in that file rather than matching
902215116Sdes	 * against the username.
903215116Sdes	 */
904215116Sdes	if ((principals_file = authorized_principals_file(pw)) != NULL) {
905294336Sdes		if (match_principals_file(principals_file, pw, key->cert))
906294336Sdes			found_principal = 1;
907294336Sdes	}
908294336Sdes	/* Try querying command if specified */
909323134Sdes	if (!found_principal && match_principals_command(pw, key))
910294336Sdes		found_principal = 1;
911294336Sdes	/* If principals file or command is specified, then require a match */
912294336Sdes	use_authorized_principals = principals_file != NULL ||
913294336Sdes            options.authorized_principals_command != NULL;
914294336Sdes	if (!found_principal && use_authorized_principals) {
915294336Sdes		reason = "Certificate does not contain an authorized principal";
916215116Sdes fail_reason:
917294336Sdes		error("%s", reason);
918294336Sdes		auth_debug_add("%s", reason);
919294336Sdes		goto out;
920204917Sdes	}
921215116Sdes	if (key_cert_check_authority(key, 0, 1,
922294336Sdes	    use_authorized_principals ? NULL : pw->pw_name, &reason) != 0)
923215116Sdes		goto fail_reason;
924323134Sdes	if (auth_cert_options(key, pw, &reason) != 0)
925323134Sdes		goto fail_reason;
926204917Sdes
927296633Sdes	verbose("Accepted certificate ID \"%s\" (serial %llu) signed by "
928296633Sdes	    "%s CA %s via %s", key->cert->key_id,
929296633Sdes	    (unsigned long long)key->cert->serial,
930296633Sdes	    key_type(key->cert->signature_key), ca_fp,
931207319Sdes	    options.trusted_user_ca_keys);
932204917Sdes	ret = 1;
933204917Sdes
934204917Sdes out:
935255767Sdes	free(principals_file);
936255767Sdes	free(ca_fp);
937204917Sdes	return ret;
938204917Sdes}
939204917Sdes
940248619Sdes/*
941248619Sdes * Checks whether key is allowed in file.
942248619Sdes * returns 1 if the key is allowed or 0 otherwise.
943248619Sdes */
944248619Sdesstatic int
945248619Sdesuser_key_allowed2(struct passwd *pw, Key *key, char *file)
946248619Sdes{
947248619Sdes	FILE *f;
948248619Sdes	int found_key = 0;
949248619Sdes
950248619Sdes	/* Temporarily use the user's uid. */
951248619Sdes	temporarily_use_uid(pw);
952248619Sdes
953248619Sdes	debug("trying public key file %s", file);
954248619Sdes	if ((f = auth_openkeyfile(file, pw, options.strict_modes)) != NULL) {
955248619Sdes		found_key = check_authkeys_file(f, file, key, pw);
956248619Sdes		fclose(f);
957248619Sdes	}
958248619Sdes
959248619Sdes	restore_uid();
960248619Sdes	return found_key;
961248619Sdes}
962248619Sdes
963248619Sdes/*
964248619Sdes * Checks whether key is allowed in output of command.
965248619Sdes * returns 1 if the key is allowed or 0 otherwise.
966248619Sdes */
967248619Sdesstatic int
968248619Sdesuser_key_command_allowed2(struct passwd *user_pw, Key *key)
969248619Sdes{
970294336Sdes	FILE *f = NULL;
971294336Sdes	int r, ok, found_key = 0;
972248619Sdes	struct passwd *pw;
973294336Sdes	int i, uid_swapped = 0, ac = 0;
974248619Sdes	pid_t pid;
975294336Sdes	char *username = NULL, *key_fp = NULL, *keytext = NULL;
976294336Sdes	char *tmp, *command = NULL, **av = NULL;
977294336Sdes	void (*osigchld)(int);
978248619Sdes
979294336Sdes	if (options.authorized_keys_command == NULL)
980248619Sdes		return 0;
981248619Sdes	if (options.authorized_keys_command_user == NULL) {
982248619Sdes		error("No user for AuthorizedKeysCommand specified, skipping");
983248619Sdes		return 0;
984248619Sdes	}
985248619Sdes
986294336Sdes	/*
987294336Sdes	 * NB. all returns later this function should go via "out" to
988294336Sdes	 * ensure the original SIGCHLD handler is restored properly.
989294336Sdes	 */
990294336Sdes	osigchld = signal(SIGCHLD, SIG_DFL);
991294336Sdes
992294336Sdes	/* Prepare and verify the user for the command */
993248619Sdes	username = percent_expand(options.authorized_keys_command_user,
994248619Sdes	    "u", user_pw->pw_name, (char *)NULL);
995248619Sdes	pw = getpwnam(username);
996248619Sdes	if (pw == NULL) {
997248619Sdes		error("AuthorizedKeysCommandUser \"%s\" not found: %s",
998248619Sdes		    username, strerror(errno));
999294336Sdes		goto out;
1000248619Sdes	}
1001248619Sdes
1002294336Sdes	/* Prepare AuthorizedKeysCommand */
1003294336Sdes	if ((key_fp = sshkey_fingerprint(key, options.fingerprint_hash,
1004294336Sdes	    SSH_FP_DEFAULT)) == NULL) {
1005294336Sdes		error("%s: sshkey_fingerprint failed", __func__);
1006248619Sdes		goto out;
1007248619Sdes	}
1008294336Sdes	if ((r = sshkey_to_base64(key, &keytext)) != 0) {
1009294336Sdes		error("%s: sshkey_to_base64 failed: %s", __func__, ssh_err(r));
1010248619Sdes		goto out;
1011248619Sdes	}
1012248619Sdes
1013294336Sdes	/* Turn the command into an argument vector */
1014294336Sdes	if (split_argv(options.authorized_keys_command, &ac, &av) != 0) {
1015294336Sdes		error("AuthorizedKeysCommand \"%s\" contains invalid quotes",
1016294336Sdes		    command);
1017248619Sdes		goto out;
1018248619Sdes	}
1019294336Sdes	if (ac == 0) {
1020294336Sdes		error("AuthorizedKeysCommand \"%s\" yielded no arguments",
1021294336Sdes		    command);
1022294336Sdes		goto out;
1023294336Sdes	}
1024294336Sdes	for (i = 1; i < ac; i++) {
1025294336Sdes		tmp = percent_expand(av[i],
1026294336Sdes		    "u", user_pw->pw_name,
1027294336Sdes		    "h", user_pw->pw_dir,
1028294336Sdes		    "t", sshkey_ssh_name(key),
1029294336Sdes		    "f", key_fp,
1030294336Sdes		    "k", keytext,
1031294336Sdes		    (char *)NULL);
1032294336Sdes		if (tmp == NULL)
1033294336Sdes			fatal("%s: percent_expand failed", __func__);
1034294336Sdes		free(av[i]);
1035294336Sdes		av[i] = tmp;
1036294336Sdes	}
1037294336Sdes	/* Prepare a printable command for logs, etc. */
1038294336Sdes	command = assemble_argv(ac, av);
1039248619Sdes
1040248619Sdes	/*
1041294336Sdes	 * If AuthorizedKeysCommand was run without arguments
1042294336Sdes	 * then fall back to the old behaviour of passing the
1043294336Sdes	 * target username as a single argument.
1044248619Sdes	 */
1045294336Sdes	if (ac == 1) {
1046294336Sdes		av = xreallocarray(av, ac + 2, sizeof(*av));
1047294336Sdes		av[1] = xstrdup(user_pw->pw_name);
1048294336Sdes		av[2] = NULL;
1049294336Sdes		/* Fix up command too, since it is used in log messages */
1050294336Sdes		free(command);
1051294336Sdes		xasprintf(&command, "%s %s", av[0], av[1]);
1052294336Sdes	}
1053248619Sdes
1054294336Sdes	if ((pid = subprocess("AuthorizedKeysCommand", pw, command,
1055294336Sdes	    ac, av, &f)) == 0)
1056294336Sdes		goto out;
1057248619Sdes
1058294336Sdes	uid_swapped = 1;
1059248619Sdes	temporarily_use_uid(pw);
1060248619Sdes
1061248619Sdes	ok = check_authkeys_file(f, options.authorized_keys_command, key, pw);
1062248619Sdes
1063323136Sdes	fclose(f);
1064323136Sdes	f = NULL;
1065323136Sdes
1066294336Sdes	if (exited_cleanly(pid, "AuthorizedKeysCommand", command) != 0)
1067248619Sdes		goto out;
1068294336Sdes
1069294336Sdes	/* Read completed successfully */
1070248619Sdes	found_key = ok;
1071248619Sdes out:
1072294336Sdes	if (f != NULL)
1073294336Sdes		fclose(f);
1074294336Sdes	signal(SIGCHLD, osigchld);
1075294336Sdes	for (i = 0; i < ac; i++)
1076294336Sdes		free(av[i]);
1077294336Sdes	free(av);
1078294336Sdes	if (uid_swapped)
1079294336Sdes		restore_uid();
1080294336Sdes	free(command);
1081294336Sdes	free(username);
1082294336Sdes	free(key_fp);
1083294336Sdes	free(keytext);
1084248619Sdes	return found_key;
1085248619Sdes}
1086248619Sdes
1087248619Sdes/*
1088248619Sdes * Check whether key authenticates and authorises the user.
1089248619Sdes */
109098675Sdesint
1091294336Sdesuser_key_allowed(struct passwd *pw, Key *key, int auth_attempt)
109298675Sdes{
1093226046Sdes	u_int success, i;
109498675Sdes	char *file;
109598675Sdes
1096204917Sdes	if (auth_key_is_revoked(key))
1097204917Sdes		return 0;
1098204917Sdes	if (key_is_cert(key) && auth_key_is_revoked(key->cert->signature_key))
1099204917Sdes		return 0;
1100204917Sdes
1101204917Sdes	success = user_cert_trusted_ca(pw, key);
1102204917Sdes	if (success)
1103204917Sdes		return success;
1104204917Sdes
1105248619Sdes	success = user_key_command_allowed2(pw, key);
1106248619Sdes	if (success > 0)
1107248619Sdes		return success;
1108248619Sdes
1109226046Sdes	for (i = 0; !success && i < options.num_authkeys_files; i++) {
1110248619Sdes
1111248619Sdes		if (strcasecmp(options.authorized_keys_files[i], "none") == 0)
1112248619Sdes			continue;
1113226046Sdes		file = expand_authorized_keys(
1114226046Sdes		    options.authorized_keys_files[i], pw);
1115248619Sdes
1116226046Sdes		success = user_key_allowed2(pw, key, file);
1117255767Sdes		free(file);
1118226046Sdes	}
111998675Sdes
112098675Sdes	return success;
112198675Sdes}
112298675Sdes
1123294332Sdes/* Records a public key in the list of previously-successful keys */
1124294332Sdesvoid
1125294332Sdesauth2_record_userkey(Authctxt *authctxt, struct sshkey *key)
1126294332Sdes{
1127294332Sdes	struct sshkey **tmp;
1128294332Sdes
1129294332Sdes	if (authctxt->nprev_userkeys >= INT_MAX ||
1130294332Sdes	    (tmp = reallocarray(authctxt->prev_userkeys,
1131294332Sdes	    authctxt->nprev_userkeys + 1, sizeof(*tmp))) == NULL)
1132294332Sdes		fatal("%s: reallocarray failed", __func__);
1133294332Sdes	authctxt->prev_userkeys = tmp;
1134294332Sdes	authctxt->prev_userkeys[authctxt->nprev_userkeys] = key;
1135294332Sdes	authctxt->nprev_userkeys++;
1136294332Sdes}
1137294332Sdes
1138294332Sdes/* Checks whether a key has already been used successfully for authentication */
1139294332Sdesint
1140294332Sdesauth2_userkey_already_used(Authctxt *authctxt, struct sshkey *key)
1141294332Sdes{
1142294332Sdes	u_int i;
1143294332Sdes
1144294332Sdes	for (i = 0; i < authctxt->nprev_userkeys; i++) {
1145294332Sdes		if (sshkey_equal_public(key, authctxt->prev_userkeys[i])) {
1146294332Sdes			return 1;
1147294332Sdes		}
1148294332Sdes	}
1149294332Sdes	return 0;
1150294332Sdes}
1151294332Sdes
115298675SdesAuthmethod method_pubkey = {
115398675Sdes	"publickey",
115498675Sdes	userauth_pubkey,
115598675Sdes	&options.pubkey_authentication
115698675Sdes};
1157