xref: /dflybsd-src/crypto/openssh/hostfile.c (revision ba1276acd1c8c22d225b1bcf370a14c878644f44)
1*ba1276acSMatthew Dillon /* $OpenBSD: hostfile.c,v 1.95 2023/02/21 06:48:18 dtucker Exp $ */
218de8d7fSPeter Avalos /*
318de8d7fSPeter Avalos  * Author: Tatu Ylonen <ylo@cs.hut.fi>
418de8d7fSPeter Avalos  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
518de8d7fSPeter Avalos  *                    All rights reserved
618de8d7fSPeter Avalos  * Functions for manipulating the known hosts files.
718de8d7fSPeter Avalos  *
818de8d7fSPeter Avalos  * As far as I am concerned, the code I have written for this software
918de8d7fSPeter Avalos  * can be used freely for any purpose.  Any derived versions of this
1018de8d7fSPeter Avalos  * software must be clearly marked as such, and if the derived work is
1118de8d7fSPeter Avalos  * incompatible with the protocol description in the RFC file, it must be
1218de8d7fSPeter Avalos  * called by a name other than "ssh" or "Secure Shell".
1318de8d7fSPeter Avalos  *
1418de8d7fSPeter Avalos  *
1518de8d7fSPeter Avalos  * Copyright (c) 1999, 2000 Markus Friedl.  All rights reserved.
1618de8d7fSPeter Avalos  * Copyright (c) 1999 Niels Provos.  All rights reserved.
1718de8d7fSPeter Avalos  *
1818de8d7fSPeter Avalos  * Redistribution and use in source and binary forms, with or without
1918de8d7fSPeter Avalos  * modification, are permitted provided that the following conditions
2018de8d7fSPeter Avalos  * are met:
2118de8d7fSPeter Avalos  * 1. Redistributions of source code must retain the above copyright
2218de8d7fSPeter Avalos  *    notice, this list of conditions and the following disclaimer.
2318de8d7fSPeter Avalos  * 2. Redistributions in binary form must reproduce the above copyright
2418de8d7fSPeter Avalos  *    notice, this list of conditions and the following disclaimer in the
2518de8d7fSPeter Avalos  *    documentation and/or other materials provided with the distribution.
2618de8d7fSPeter Avalos  *
2718de8d7fSPeter Avalos  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
2818de8d7fSPeter Avalos  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
2918de8d7fSPeter Avalos  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
3018de8d7fSPeter Avalos  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
3118de8d7fSPeter Avalos  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
3218de8d7fSPeter Avalos  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
3318de8d7fSPeter Avalos  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
3418de8d7fSPeter Avalos  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
3518de8d7fSPeter Avalos  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
3618de8d7fSPeter Avalos  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3718de8d7fSPeter Avalos  */
3818de8d7fSPeter Avalos 
3918de8d7fSPeter Avalos #include "includes.h"
4018de8d7fSPeter Avalos 
4118de8d7fSPeter Avalos #include <sys/types.h>
42e9778795SPeter Avalos #include <sys/stat.h>
4318de8d7fSPeter Avalos 
4418de8d7fSPeter Avalos #include <netinet/in.h>
4518de8d7fSPeter Avalos 
46e9778795SPeter Avalos #include <errno.h>
4718de8d7fSPeter Avalos #include <resolv.h>
4818de8d7fSPeter Avalos #include <stdarg.h>
4918de8d7fSPeter Avalos #include <stdio.h>
5018de8d7fSPeter Avalos #include <stdlib.h>
5118de8d7fSPeter Avalos #include <string.h>
52e9778795SPeter Avalos #include <unistd.h>
5318de8d7fSPeter Avalos 
5418de8d7fSPeter Avalos #include "xmalloc.h"
5518de8d7fSPeter Avalos #include "match.h"
56e9778795SPeter Avalos #include "sshkey.h"
5718de8d7fSPeter Avalos #include "hostfile.h"
5818de8d7fSPeter Avalos #include "log.h"
599f304aafSPeter Avalos #include "misc.h"
6050a69bb5SSascha Wildner #include "pathnames.h"
61e9778795SPeter Avalos #include "ssherr.h"
6236e94dc5SPeter Avalos #include "digest.h"
6336e94dc5SPeter Avalos #include "hmac.h"
6450a69bb5SSascha Wildner #include "sshbuf.h"
6518de8d7fSPeter Avalos 
66e9778795SPeter Avalos /* XXX hmac is too easy to dictionary attack; use bcrypt? */
67e9778795SPeter Avalos 
6818de8d7fSPeter Avalos static int
extract_salt(const char * s,u_int l,u_char * salt,size_t salt_len)6936e94dc5SPeter Avalos extract_salt(const char *s, u_int l, u_char *salt, size_t salt_len)
7018de8d7fSPeter Avalos {
7118de8d7fSPeter Avalos 	char *p, *b64salt;
7218de8d7fSPeter Avalos 	u_int b64len;
7318de8d7fSPeter Avalos 	int ret;
7418de8d7fSPeter Avalos 
7518de8d7fSPeter Avalos 	if (l < sizeof(HASH_MAGIC) - 1) {
7618de8d7fSPeter Avalos 		debug2("extract_salt: string too short");
7718de8d7fSPeter Avalos 		return (-1);
7818de8d7fSPeter Avalos 	}
7918de8d7fSPeter Avalos 	if (strncmp(s, HASH_MAGIC, sizeof(HASH_MAGIC) - 1) != 0) {
8018de8d7fSPeter Avalos 		debug2("extract_salt: invalid magic identifier");
8118de8d7fSPeter Avalos 		return (-1);
8218de8d7fSPeter Avalos 	}
8318de8d7fSPeter Avalos 	s += sizeof(HASH_MAGIC) - 1;
8418de8d7fSPeter Avalos 	l -= sizeof(HASH_MAGIC) - 1;
8518de8d7fSPeter Avalos 	if ((p = memchr(s, HASH_DELIM, l)) == NULL) {
8618de8d7fSPeter Avalos 		debug2("extract_salt: missing salt termination character");
8718de8d7fSPeter Avalos 		return (-1);
8818de8d7fSPeter Avalos 	}
8918de8d7fSPeter Avalos 
9018de8d7fSPeter Avalos 	b64len = p - s;
9118de8d7fSPeter Avalos 	/* Sanity check */
9218de8d7fSPeter Avalos 	if (b64len == 0 || b64len > 1024) {
9318de8d7fSPeter Avalos 		debug2("extract_salt: bad encoded salt length %u", b64len);
9418de8d7fSPeter Avalos 		return (-1);
9518de8d7fSPeter Avalos 	}
9618de8d7fSPeter Avalos 	b64salt = xmalloc(1 + b64len);
9718de8d7fSPeter Avalos 	memcpy(b64salt, s, b64len);
9818de8d7fSPeter Avalos 	b64salt[b64len] = '\0';
9918de8d7fSPeter Avalos 
10018de8d7fSPeter Avalos 	ret = __b64_pton(b64salt, salt, salt_len);
10136e94dc5SPeter Avalos 	free(b64salt);
10218de8d7fSPeter Avalos 	if (ret == -1) {
10318de8d7fSPeter Avalos 		debug2("extract_salt: salt decode error");
10418de8d7fSPeter Avalos 		return (-1);
10518de8d7fSPeter Avalos 	}
10636e94dc5SPeter Avalos 	if (ret != (int)ssh_hmac_bytes(SSH_DIGEST_SHA1)) {
10736e94dc5SPeter Avalos 		debug2("extract_salt: expected salt len %zd, got %d",
10836e94dc5SPeter Avalos 		    ssh_hmac_bytes(SSH_DIGEST_SHA1), ret);
10918de8d7fSPeter Avalos 		return (-1);
11018de8d7fSPeter Avalos 	}
11118de8d7fSPeter Avalos 
11218de8d7fSPeter Avalos 	return (0);
11318de8d7fSPeter Avalos }
11418de8d7fSPeter Avalos 
11518de8d7fSPeter Avalos char *
host_hash(const char * host,const char * name_from_hostfile,u_int src_len)11618de8d7fSPeter Avalos host_hash(const char *host, const char *name_from_hostfile, u_int src_len)
11718de8d7fSPeter Avalos {
11836e94dc5SPeter Avalos 	struct ssh_hmac_ctx *ctx;
11936e94dc5SPeter Avalos 	u_char salt[256], result[256];
12036e94dc5SPeter Avalos 	char uu_salt[512], uu_result[512];
121ee116499SAntonio Huete Jimenez 	char *encoded = NULL;
122ce74bacaSMatthew Dillon 	u_int len;
12318de8d7fSPeter Avalos 
12436e94dc5SPeter Avalos 	len = ssh_digest_bytes(SSH_DIGEST_SHA1);
12518de8d7fSPeter Avalos 
12618de8d7fSPeter Avalos 	if (name_from_hostfile == NULL) {
12718de8d7fSPeter Avalos 		/* Create new salt */
128ce74bacaSMatthew Dillon 		arc4random_buf(salt, len);
12918de8d7fSPeter Avalos 	} else {
13018de8d7fSPeter Avalos 		/* Extract salt from known host entry */
13118de8d7fSPeter Avalos 		if (extract_salt(name_from_hostfile, src_len, salt,
13218de8d7fSPeter Avalos 		    sizeof(salt)) == -1)
13318de8d7fSPeter Avalos 			return (NULL);
13418de8d7fSPeter Avalos 	}
13518de8d7fSPeter Avalos 
13636e94dc5SPeter Avalos 	if ((ctx = ssh_hmac_start(SSH_DIGEST_SHA1)) == NULL ||
13736e94dc5SPeter Avalos 	    ssh_hmac_init(ctx, salt, len) < 0 ||
13836e94dc5SPeter Avalos 	    ssh_hmac_update(ctx, host, strlen(host)) < 0 ||
13936e94dc5SPeter Avalos 	    ssh_hmac_final(ctx, result, sizeof(result)))
14050a69bb5SSascha Wildner 		fatal_f("ssh_hmac failed");
14136e94dc5SPeter Avalos 	ssh_hmac_free(ctx);
14218de8d7fSPeter Avalos 
14318de8d7fSPeter Avalos 	if (__b64_ntop(salt, len, uu_salt, sizeof(uu_salt)) == -1 ||
14418de8d7fSPeter Avalos 	    __b64_ntop(result, len, uu_result, sizeof(uu_result)) == -1)
14550a69bb5SSascha Wildner 		fatal_f("__b64_ntop failed");
146ee116499SAntonio Huete Jimenez 	xasprintf(&encoded, "%s%s%c%s", HASH_MAGIC, uu_salt, HASH_DELIM,
147ee116499SAntonio Huete Jimenez 	    uu_result);
14818de8d7fSPeter Avalos 
14918de8d7fSPeter Avalos 	return (encoded);
15018de8d7fSPeter Avalos }
15118de8d7fSPeter Avalos 
15218de8d7fSPeter Avalos /*
15318de8d7fSPeter Avalos  * Parses an RSA (number of bits, e, n) or DSA key from a string.  Moves the
15418de8d7fSPeter Avalos  * pointer over the key.  Skips any whitespace at the beginning and at end.
15518de8d7fSPeter Avalos  */
15618de8d7fSPeter Avalos 
15718de8d7fSPeter Avalos int
hostfile_read_key(char ** cpp,u_int * bitsp,struct sshkey * ret)158e9778795SPeter Avalos hostfile_read_key(char **cpp, u_int *bitsp, struct sshkey *ret)
15918de8d7fSPeter Avalos {
16018de8d7fSPeter Avalos 	char *cp;
16118de8d7fSPeter Avalos 
16218de8d7fSPeter Avalos 	/* Skip leading whitespace. */
16318de8d7fSPeter Avalos 	for (cp = *cpp; *cp == ' ' || *cp == '\t'; cp++)
16418de8d7fSPeter Avalos 		;
16518de8d7fSPeter Avalos 
1660cbfa66cSDaniel Fojt 	if (sshkey_read(ret, &cp) != 0)
16718de8d7fSPeter Avalos 		return 0;
16818de8d7fSPeter Avalos 
16918de8d7fSPeter Avalos 	/* Skip trailing whitespace. */
17018de8d7fSPeter Avalos 	for (; *cp == ' ' || *cp == '\t'; cp++)
17118de8d7fSPeter Avalos 		;
17218de8d7fSPeter Avalos 
17318de8d7fSPeter Avalos 	/* Return results. */
17418de8d7fSPeter Avalos 	*cpp = cp;
175e9778795SPeter Avalos 	if (bitsp != NULL)
176e9778795SPeter Avalos 		*bitsp = sshkey_size(ret);
17718de8d7fSPeter Avalos 	return 1;
17818de8d7fSPeter Avalos }
17918de8d7fSPeter Avalos 
1809f304aafSPeter Avalos static HostkeyMarker
check_markers(char ** cpp)181856ea928SPeter Avalos check_markers(char **cpp)
182856ea928SPeter Avalos {
183856ea928SPeter Avalos 	char marker[32], *sp, *cp = *cpp;
184856ea928SPeter Avalos 	int ret = MRK_NONE;
185856ea928SPeter Avalos 
186856ea928SPeter Avalos 	while (*cp == '@') {
187856ea928SPeter Avalos 		/* Only one marker is allowed */
188856ea928SPeter Avalos 		if (ret != MRK_NONE)
189856ea928SPeter Avalos 			return MRK_ERROR;
190856ea928SPeter Avalos 		/* Markers are terminated by whitespace */
191856ea928SPeter Avalos 		if ((sp = strchr(cp, ' ')) == NULL &&
192856ea928SPeter Avalos 		    (sp = strchr(cp, '\t')) == NULL)
193856ea928SPeter Avalos 			return MRK_ERROR;
194856ea928SPeter Avalos 		/* Extract marker for comparison */
195856ea928SPeter Avalos 		if (sp <= cp + 1 || sp >= cp + sizeof(marker))
196856ea928SPeter Avalos 			return MRK_ERROR;
197856ea928SPeter Avalos 		memcpy(marker, cp, sp - cp);
198856ea928SPeter Avalos 		marker[sp - cp] = '\0';
199856ea928SPeter Avalos 		if (strcmp(marker, CA_MARKER) == 0)
200856ea928SPeter Avalos 			ret = MRK_CA;
201856ea928SPeter Avalos 		else if (strcmp(marker, REVOKE_MARKER) == 0)
202856ea928SPeter Avalos 			ret = MRK_REVOKE;
203856ea928SPeter Avalos 		else
204856ea928SPeter Avalos 			return MRK_ERROR;
205856ea928SPeter Avalos 
206856ea928SPeter Avalos 		/* Skip past marker and any whitespace that follows it */
207856ea928SPeter Avalos 		cp = sp;
208856ea928SPeter Avalos 		for (; *cp == ' ' || *cp == '\t'; cp++)
209856ea928SPeter Avalos 			;
210856ea928SPeter Avalos 	}
211856ea928SPeter Avalos 	*cpp = cp;
212856ea928SPeter Avalos 	return ret;
213856ea928SPeter Avalos }
214856ea928SPeter Avalos 
2159f304aafSPeter Avalos struct hostkeys *
init_hostkeys(void)2169f304aafSPeter Avalos init_hostkeys(void)
2179f304aafSPeter Avalos {
2189f304aafSPeter Avalos 	struct hostkeys *ret = xcalloc(1, sizeof(*ret));
21918de8d7fSPeter Avalos 
2209f304aafSPeter Avalos 	ret->entries = NULL;
2219f304aafSPeter Avalos 	return ret;
2229f304aafSPeter Avalos }
2239f304aafSPeter Avalos 
224e9778795SPeter Avalos struct load_callback_ctx {
225e9778795SPeter Avalos 	const char *host;
226e9778795SPeter Avalos 	u_long num_loaded;
227e9778795SPeter Avalos 	struct hostkeys *hostkeys;
228e9778795SPeter Avalos };
229e9778795SPeter Avalos 
230e9778795SPeter Avalos static int
record_hostkey(struct hostkey_foreach_line * l,void * _ctx)231e9778795SPeter Avalos record_hostkey(struct hostkey_foreach_line *l, void *_ctx)
232e9778795SPeter Avalos {
233e9778795SPeter Avalos 	struct load_callback_ctx *ctx = (struct load_callback_ctx *)_ctx;
234e9778795SPeter Avalos 	struct hostkeys *hostkeys = ctx->hostkeys;
235e9778795SPeter Avalos 	struct hostkey_entry *tmp;
236e9778795SPeter Avalos 
237e9778795SPeter Avalos 	if (l->status == HKF_STATUS_INVALID) {
238e9778795SPeter Avalos 		/* XXX make this verbose() in the future */
239e9778795SPeter Avalos 		debug("%s:%ld: parse error in hostkeys file",
240e9778795SPeter Avalos 		    l->path, l->linenum);
241e9778795SPeter Avalos 		return 0;
242e9778795SPeter Avalos 	}
243e9778795SPeter Avalos 
24450a69bb5SSascha Wildner 	debug3_f("found %skey type %s in file %s:%lu",
245e9778795SPeter Avalos 	    l->marker == MRK_NONE ? "" :
246e9778795SPeter Avalos 	    (l->marker == MRK_CA ? "ca " : "revoked "),
247e9778795SPeter Avalos 	    sshkey_type(l->key), l->path, l->linenum);
248ce74bacaSMatthew Dillon 	if ((tmp = recallocarray(hostkeys->entries, hostkeys->num_entries,
249e9778795SPeter Avalos 	    hostkeys->num_entries + 1, sizeof(*hostkeys->entries))) == NULL)
250e9778795SPeter Avalos 		return SSH_ERR_ALLOC_FAIL;
251e9778795SPeter Avalos 	hostkeys->entries = tmp;
252e9778795SPeter Avalos 	hostkeys->entries[hostkeys->num_entries].host = xstrdup(ctx->host);
253e9778795SPeter Avalos 	hostkeys->entries[hostkeys->num_entries].file = xstrdup(l->path);
254e9778795SPeter Avalos 	hostkeys->entries[hostkeys->num_entries].line = l->linenum;
255e9778795SPeter Avalos 	hostkeys->entries[hostkeys->num_entries].key = l->key;
256e9778795SPeter Avalos 	l->key = NULL; /* steal it */
257e9778795SPeter Avalos 	hostkeys->entries[hostkeys->num_entries].marker = l->marker;
25850a69bb5SSascha Wildner 	hostkeys->entries[hostkeys->num_entries].note = l->note;
259e9778795SPeter Avalos 	hostkeys->num_entries++;
260e9778795SPeter Avalos 	ctx->num_loaded++;
261e9778795SPeter Avalos 
262e9778795SPeter Avalos 	return 0;
263e9778795SPeter Avalos }
264e9778795SPeter Avalos 
2659f304aafSPeter Avalos void
load_hostkeys_file(struct hostkeys * hostkeys,const char * host,const char * path,FILE * f,u_int note)26650a69bb5SSascha Wildner load_hostkeys_file(struct hostkeys *hostkeys, const char *host,
26750a69bb5SSascha Wildner     const char *path, FILE *f, u_int note)
26818de8d7fSPeter Avalos {
269e9778795SPeter Avalos 	int r;
270e9778795SPeter Avalos 	struct load_callback_ctx ctx;
27118de8d7fSPeter Avalos 
272e9778795SPeter Avalos 	ctx.host = host;
273e9778795SPeter Avalos 	ctx.num_loaded = 0;
274e9778795SPeter Avalos 	ctx.hostkeys = hostkeys;
27518de8d7fSPeter Avalos 
27650a69bb5SSascha Wildner 	if ((r = hostkeys_foreach_file(path, f, record_hostkey, &ctx, host,
27750a69bb5SSascha Wildner 	    NULL, HKF_WANT_MATCH|HKF_WANT_PARSE_KEY, note)) != 0) {
278e9778795SPeter Avalos 		if (r != SSH_ERR_SYSTEM_ERROR && errno != ENOENT)
27950a69bb5SSascha Wildner 			debug_fr(r, "hostkeys_foreach failed for %s", path);
2809f304aafSPeter Avalos 	}
281e9778795SPeter Avalos 	if (ctx.num_loaded != 0)
28250a69bb5SSascha Wildner 		debug3_f("loaded %lu keys from %s", ctx.num_loaded, host);
28350a69bb5SSascha Wildner }
28450a69bb5SSascha Wildner 
28550a69bb5SSascha Wildner void
load_hostkeys(struct hostkeys * hostkeys,const char * host,const char * path,u_int note)28650a69bb5SSascha Wildner load_hostkeys(struct hostkeys *hostkeys, const char *host, const char *path,
28750a69bb5SSascha Wildner     u_int note)
28850a69bb5SSascha Wildner {
28950a69bb5SSascha Wildner 	FILE *f;
29050a69bb5SSascha Wildner 
29150a69bb5SSascha Wildner 	if ((f = fopen(path, "r")) == NULL) {
29250a69bb5SSascha Wildner 		debug_f("fopen %s: %s", path, strerror(errno));
29350a69bb5SSascha Wildner 		return;
29450a69bb5SSascha Wildner 	}
29550a69bb5SSascha Wildner 
29650a69bb5SSascha Wildner 	load_hostkeys_file(hostkeys, host, path, f, note);
29750a69bb5SSascha Wildner 	fclose(f);
29818de8d7fSPeter Avalos }
29918de8d7fSPeter Avalos 
3009f304aafSPeter Avalos void
free_hostkeys(struct hostkeys * hostkeys)3019f304aafSPeter Avalos free_hostkeys(struct hostkeys *hostkeys)
3029f304aafSPeter Avalos {
3039f304aafSPeter Avalos 	u_int i;
30418de8d7fSPeter Avalos 
3059f304aafSPeter Avalos 	for (i = 0; i < hostkeys->num_entries; i++) {
30636e94dc5SPeter Avalos 		free(hostkeys->entries[i].host);
30736e94dc5SPeter Avalos 		free(hostkeys->entries[i].file);
308e9778795SPeter Avalos 		sshkey_free(hostkeys->entries[i].key);
30936e94dc5SPeter Avalos 		explicit_bzero(hostkeys->entries + i, sizeof(*hostkeys->entries));
310856ea928SPeter Avalos 	}
31136e94dc5SPeter Avalos 	free(hostkeys->entries);
3120cbfa66cSDaniel Fojt 	freezero(hostkeys, sizeof(*hostkeys));
313856ea928SPeter Avalos }
314856ea928SPeter Avalos 
3159f304aafSPeter Avalos static int
check_key_not_revoked(struct hostkeys * hostkeys,struct sshkey * k)316e9778795SPeter Avalos check_key_not_revoked(struct hostkeys *hostkeys, struct sshkey *k)
3179f304aafSPeter Avalos {
318e9778795SPeter Avalos 	int is_cert = sshkey_is_cert(k);
3199f304aafSPeter Avalos 	u_int i;
3209f304aafSPeter Avalos 
3219f304aafSPeter Avalos 	for (i = 0; i < hostkeys->num_entries; i++) {
3229f304aafSPeter Avalos 		if (hostkeys->entries[i].marker != MRK_REVOKE)
3239f304aafSPeter Avalos 			continue;
324e9778795SPeter Avalos 		if (sshkey_equal_public(k, hostkeys->entries[i].key))
3259f304aafSPeter Avalos 			return -1;
3260cbfa66cSDaniel Fojt 		if (is_cert && k != NULL &&
327e9778795SPeter Avalos 		    sshkey_equal_public(k->cert->signature_key,
3289f304aafSPeter Avalos 		    hostkeys->entries[i].key))
3299f304aafSPeter Avalos 			return -1;
33018de8d7fSPeter Avalos 	}
3319f304aafSPeter Avalos 	return 0;
3329f304aafSPeter Avalos }
3339f304aafSPeter Avalos 
33418de8d7fSPeter Avalos /*
3359f304aafSPeter Avalos  * Match keys against a specified key, or look one up by key type.
3369f304aafSPeter Avalos  *
3379f304aafSPeter Avalos  * If looking for a keytype (key == NULL) and one is found then return
3389f304aafSPeter Avalos  * HOST_FOUND, otherwise HOST_NEW.
3399f304aafSPeter Avalos  *
3409f304aafSPeter Avalos  * If looking for a key (key != NULL):
3419f304aafSPeter Avalos  *  1. If the key is a cert and a matching CA is found, return HOST_OK
3429f304aafSPeter Avalos  *  2. If the key is not a cert and a matching key is found, return HOST_OK
3439f304aafSPeter Avalos  *  3. If no key matches but a key with a different type is found, then
3449f304aafSPeter Avalos  *     return HOST_CHANGED
3459f304aafSPeter Avalos  *  4. If no matching keys are found, then return HOST_NEW.
3469f304aafSPeter Avalos  *
3479f304aafSPeter Avalos  * Finally, check any found key is not revoked.
34818de8d7fSPeter Avalos  */
3499f304aafSPeter Avalos static HostStatus
check_hostkeys_by_key_or_type(struct hostkeys * hostkeys,struct sshkey * k,int keytype,int nid,const struct hostkey_entry ** found)3509f304aafSPeter Avalos check_hostkeys_by_key_or_type(struct hostkeys *hostkeys,
35150a69bb5SSascha Wildner     struct sshkey *k, int keytype, int nid, const struct hostkey_entry **found)
3529f304aafSPeter Avalos {
3539f304aafSPeter Avalos 	u_int i;
3549f304aafSPeter Avalos 	HostStatus end_return = HOST_NEW;
355e9778795SPeter Avalos 	int want_cert = sshkey_is_cert(k);
3569f304aafSPeter Avalos 	HostkeyMarker want_marker = want_cert ? MRK_CA : MRK_NONE;
3579f304aafSPeter Avalos 
3589f304aafSPeter Avalos 	if (found != NULL)
3599f304aafSPeter Avalos 		*found = NULL;
3609f304aafSPeter Avalos 
3619f304aafSPeter Avalos 	for (i = 0; i < hostkeys->num_entries; i++) {
3629f304aafSPeter Avalos 		if (hostkeys->entries[i].marker != want_marker)
3639f304aafSPeter Avalos 			continue;
3649f304aafSPeter Avalos 		if (k == NULL) {
3659f304aafSPeter Avalos 			if (hostkeys->entries[i].key->type != keytype)
3669f304aafSPeter Avalos 				continue;
36750a69bb5SSascha Wildner 			if (nid != -1 &&
36850a69bb5SSascha Wildner 			    sshkey_type_plain(keytype) == KEY_ECDSA &&
36950a69bb5SSascha Wildner 			    hostkeys->entries[i].key->ecdsa_nid != nid)
37050a69bb5SSascha Wildner 				continue;
3719f304aafSPeter Avalos 			end_return = HOST_FOUND;
3729f304aafSPeter Avalos 			if (found != NULL)
3739f304aafSPeter Avalos 				*found = hostkeys->entries + i;
3749f304aafSPeter Avalos 			k = hostkeys->entries[i].key;
3759f304aafSPeter Avalos 			break;
3769f304aafSPeter Avalos 		}
3779f304aafSPeter Avalos 		if (want_cert) {
378e9778795SPeter Avalos 			if (sshkey_equal_public(k->cert->signature_key,
3799f304aafSPeter Avalos 			    hostkeys->entries[i].key)) {
3809f304aafSPeter Avalos 				/* A matching CA exists */
3819f304aafSPeter Avalos 				end_return = HOST_OK;
3829f304aafSPeter Avalos 				if (found != NULL)
3839f304aafSPeter Avalos 					*found = hostkeys->entries + i;
3849f304aafSPeter Avalos 				break;
3859f304aafSPeter Avalos 			}
3869f304aafSPeter Avalos 		} else {
387e9778795SPeter Avalos 			if (sshkey_equal(k, hostkeys->entries[i].key)) {
3889f304aafSPeter Avalos 				end_return = HOST_OK;
3899f304aafSPeter Avalos 				if (found != NULL)
3909f304aafSPeter Avalos 					*found = hostkeys->entries + i;
3919f304aafSPeter Avalos 				break;
3929f304aafSPeter Avalos 			}
39350a69bb5SSascha Wildner 			/* A non-matching key exists */
39418de8d7fSPeter Avalos 			end_return = HOST_CHANGED;
3959f304aafSPeter Avalos 			if (found != NULL)
3969f304aafSPeter Avalos 				*found = hostkeys->entries + i;
39718de8d7fSPeter Avalos 		}
3989f304aafSPeter Avalos 	}
3999f304aafSPeter Avalos 	if (check_key_not_revoked(hostkeys, k) != 0) {
4009f304aafSPeter Avalos 		end_return = HOST_REVOKED;
4019f304aafSPeter Avalos 		if (found != NULL)
4029f304aafSPeter Avalos 			*found = NULL;
4039f304aafSPeter Avalos 	}
40418de8d7fSPeter Avalos 	return end_return;
40518de8d7fSPeter Avalos }
40618de8d7fSPeter Avalos 
40718de8d7fSPeter Avalos HostStatus
check_key_in_hostkeys(struct hostkeys * hostkeys,struct sshkey * key,const struct hostkey_entry ** found)408e9778795SPeter Avalos check_key_in_hostkeys(struct hostkeys *hostkeys, struct sshkey *key,
4099f304aafSPeter Avalos     const struct hostkey_entry **found)
41018de8d7fSPeter Avalos {
41118de8d7fSPeter Avalos 	if (key == NULL)
41218de8d7fSPeter Avalos 		fatal("no key to look up");
41350a69bb5SSascha Wildner 	return check_hostkeys_by_key_or_type(hostkeys, key, 0, -1, found);
41418de8d7fSPeter Avalos }
41518de8d7fSPeter Avalos 
41618de8d7fSPeter Avalos int
lookup_key_in_hostkeys_by_type(struct hostkeys * hostkeys,int keytype,int nid,const struct hostkey_entry ** found)41750a69bb5SSascha Wildner lookup_key_in_hostkeys_by_type(struct hostkeys *hostkeys, int keytype, int nid,
4189f304aafSPeter Avalos     const struct hostkey_entry **found)
41918de8d7fSPeter Avalos {
42050a69bb5SSascha Wildner 	return (check_hostkeys_by_key_or_type(hostkeys, NULL, keytype, nid,
4219f304aafSPeter Avalos 	    found) == HOST_FOUND);
42218de8d7fSPeter Avalos }
42318de8d7fSPeter Avalos 
42450a69bb5SSascha Wildner int
lookup_marker_in_hostkeys(struct hostkeys * hostkeys,int want_marker)42550a69bb5SSascha Wildner lookup_marker_in_hostkeys(struct hostkeys *hostkeys, int want_marker)
42650a69bb5SSascha Wildner {
42750a69bb5SSascha Wildner 	u_int i;
42850a69bb5SSascha Wildner 
42950a69bb5SSascha Wildner 	for (i = 0; i < hostkeys->num_entries; i++) {
43050a69bb5SSascha Wildner 		if (hostkeys->entries[i].marker == (HostkeyMarker)want_marker)
43150a69bb5SSascha Wildner 			return 1;
43250a69bb5SSascha Wildner 	}
43350a69bb5SSascha Wildner 	return 0;
43450a69bb5SSascha Wildner }
43550a69bb5SSascha Wildner 
436e9778795SPeter Avalos static int
write_host_entry(FILE * f,const char * host,const char * ip,const struct sshkey * key,int store_hash)437e9778795SPeter Avalos write_host_entry(FILE *f, const char *host, const char *ip,
438e9778795SPeter Avalos     const struct sshkey *key, int store_hash)
439e9778795SPeter Avalos {
440e9778795SPeter Avalos 	int r, success = 0;
441ce74bacaSMatthew Dillon 	char *hashed_host = NULL, *lhost;
442ce74bacaSMatthew Dillon 
443ce74bacaSMatthew Dillon 	lhost = xstrdup(host);
444ce74bacaSMatthew Dillon 	lowercase(lhost);
445e9778795SPeter Avalos 
446e9778795SPeter Avalos 	if (store_hash) {
447ce74bacaSMatthew Dillon 		if ((hashed_host = host_hash(lhost, NULL, 0)) == NULL) {
44850a69bb5SSascha Wildner 			error_f("host_hash failed");
449ce74bacaSMatthew Dillon 			free(lhost);
450e9778795SPeter Avalos 			return 0;
451e9778795SPeter Avalos 		}
452e9778795SPeter Avalos 		fprintf(f, "%s ", hashed_host);
453e9778795SPeter Avalos 	} else if (ip != NULL)
454ce74bacaSMatthew Dillon 		fprintf(f, "%s,%s ", lhost, ip);
455ce74bacaSMatthew Dillon 	else {
456ce74bacaSMatthew Dillon 		fprintf(f, "%s ", lhost);
457ce74bacaSMatthew Dillon 	}
458ee116499SAntonio Huete Jimenez 	free(hashed_host);
459ce74bacaSMatthew Dillon 	free(lhost);
460e9778795SPeter Avalos 	if ((r = sshkey_write(key, f)) == 0)
461e9778795SPeter Avalos 		success = 1;
462e9778795SPeter Avalos 	else
46350a69bb5SSascha Wildner 		error_fr(r, "sshkey_write");
464e9778795SPeter Avalos 	fputc('\n', f);
46550a69bb5SSascha Wildner 	/* If hashing is enabled, the IP address needs to go on its own line */
46650a69bb5SSascha Wildner 	if (success && store_hash && ip != NULL)
46750a69bb5SSascha Wildner 		success = write_host_entry(f, ip, NULL, key, 1);
468e9778795SPeter Avalos 	return success;
469e9778795SPeter Avalos }
470e9778795SPeter Avalos 
47118de8d7fSPeter Avalos /*
47250a69bb5SSascha Wildner  * Create user ~/.ssh directory if it doesn't exist and we want to write to it.
47350a69bb5SSascha Wildner  * If notify is set, a message will be emitted if the directory is created.
47450a69bb5SSascha Wildner  */
47550a69bb5SSascha Wildner void
hostfile_create_user_ssh_dir(const char * filename,int notify)47650a69bb5SSascha Wildner hostfile_create_user_ssh_dir(const char *filename, int notify)
47750a69bb5SSascha Wildner {
47850a69bb5SSascha Wildner 	char *dotsshdir = NULL, *p;
47950a69bb5SSascha Wildner 	size_t len;
48050a69bb5SSascha Wildner 	struct stat st;
48150a69bb5SSascha Wildner 
48250a69bb5SSascha Wildner 	if ((p = strrchr(filename, '/')) == NULL)
48350a69bb5SSascha Wildner 		return;
48450a69bb5SSascha Wildner 	len = p - filename;
48550a69bb5SSascha Wildner 	dotsshdir = tilde_expand_filename("~/" _PATH_SSH_USER_DIR, getuid());
48650a69bb5SSascha Wildner 	if (strlen(dotsshdir) > len || strncmp(filename, dotsshdir, len) != 0)
48750a69bb5SSascha Wildner 		goto out; /* not ~/.ssh prefixed */
48850a69bb5SSascha Wildner 	if (stat(dotsshdir, &st) == 0)
48950a69bb5SSascha Wildner 		goto out; /* dir already exists */
49050a69bb5SSascha Wildner 	else if (errno != ENOENT)
49150a69bb5SSascha Wildner 		error("Could not stat %s: %s", dotsshdir, strerror(errno));
49250a69bb5SSascha Wildner 	else {
49350a69bb5SSascha Wildner #ifdef WITH_SELINUX
49450a69bb5SSascha Wildner 		ssh_selinux_setfscreatecon(dotsshdir);
49550a69bb5SSascha Wildner #endif
49650a69bb5SSascha Wildner 		if (mkdir(dotsshdir, 0700) == -1)
49750a69bb5SSascha Wildner 			error("Could not create directory '%.200s' (%s).",
49850a69bb5SSascha Wildner 			    dotsshdir, strerror(errno));
49950a69bb5SSascha Wildner 		else if (notify)
50050a69bb5SSascha Wildner 			logit("Created directory '%s'.", dotsshdir);
50150a69bb5SSascha Wildner #ifdef WITH_SELINUX
50250a69bb5SSascha Wildner 		ssh_selinux_setfscreatecon(NULL);
50350a69bb5SSascha Wildner #endif
50450a69bb5SSascha Wildner 	}
50550a69bb5SSascha Wildner  out:
50650a69bb5SSascha Wildner 	free(dotsshdir);
50750a69bb5SSascha Wildner }
50850a69bb5SSascha Wildner 
50950a69bb5SSascha Wildner /*
51018de8d7fSPeter Avalos  * Appends an entry to the host file.  Returns false if the entry could not
51118de8d7fSPeter Avalos  * be appended.
51218de8d7fSPeter Avalos  */
51318de8d7fSPeter Avalos int
add_host_to_hostfile(const char * filename,const char * host,const struct sshkey * key,int store_hash)514e9778795SPeter Avalos add_host_to_hostfile(const char *filename, const char *host,
515e9778795SPeter Avalos     const struct sshkey *key, int store_hash)
51618de8d7fSPeter Avalos {
51718de8d7fSPeter Avalos 	FILE *f;
518*ba1276acSMatthew Dillon 	int success, addnl = 0;
51918de8d7fSPeter Avalos 
52018de8d7fSPeter Avalos 	if (key == NULL)
52118de8d7fSPeter Avalos 		return 1;	/* XXX ? */
52250a69bb5SSascha Wildner 	hostfile_create_user_ssh_dir(filename, 0);
523*ba1276acSMatthew Dillon 	f = fopen(filename, "a+");
52418de8d7fSPeter Avalos 	if (!f)
52518de8d7fSPeter Avalos 		return 0;
526*ba1276acSMatthew Dillon 	/* Make sure we have a terminating newline. */
527*ba1276acSMatthew Dillon 	if (fseek(f, -1L, SEEK_END) == 0 && fgetc(f) != '\n')
528*ba1276acSMatthew Dillon 		addnl = 1;
529*ba1276acSMatthew Dillon 	if (fseek(f, 0L, SEEK_END) != 0 || (addnl && fputc('\n', f) != '\n')) {
530*ba1276acSMatthew Dillon 		error("Failed to add terminating newline to %s: %s",
531*ba1276acSMatthew Dillon 		   filename, strerror(errno));
532*ba1276acSMatthew Dillon 		fclose(f);
533*ba1276acSMatthew Dillon 		return 0;
534*ba1276acSMatthew Dillon 	}
535e9778795SPeter Avalos 	success = write_host_entry(f, host, NULL, key, store_hash);
53618de8d7fSPeter Avalos 	fclose(f);
537e9778795SPeter Avalos 	return success;
538e9778795SPeter Avalos }
539e9778795SPeter Avalos 
540e9778795SPeter Avalos struct host_delete_ctx {
541e9778795SPeter Avalos 	FILE *out;
542e9778795SPeter Avalos 	int quiet;
54350a69bb5SSascha Wildner 	const char *host, *ip;
54450a69bb5SSascha Wildner 	u_int *match_keys;	/* mask of HKF_MATCH_* for this key */
545e9778795SPeter Avalos 	struct sshkey * const *keys;
546e9778795SPeter Avalos 	size_t nkeys;
547e9778795SPeter Avalos 	int modified;
548e9778795SPeter Avalos };
549e9778795SPeter Avalos 
550e9778795SPeter Avalos static int
host_delete(struct hostkey_foreach_line * l,void * _ctx)551e9778795SPeter Avalos host_delete(struct hostkey_foreach_line *l, void *_ctx)
552e9778795SPeter Avalos {
553e9778795SPeter Avalos 	struct host_delete_ctx *ctx = (struct host_delete_ctx *)_ctx;
554e9778795SPeter Avalos 	int loglevel = ctx->quiet ? SYSLOG_LEVEL_DEBUG1 : SYSLOG_LEVEL_VERBOSE;
555e9778795SPeter Avalos 	size_t i;
556e9778795SPeter Avalos 
557e9778795SPeter Avalos 	/* Don't remove CA and revocation lines */
55850a69bb5SSascha Wildner 	if (l->status == HKF_STATUS_MATCHED && l->marker == MRK_NONE) {
559e9778795SPeter Avalos 		/*
560e9778795SPeter Avalos 		 * If this line contains one of the keys that we will be
561e9778795SPeter Avalos 		 * adding later, then don't change it and mark the key for
562e9778795SPeter Avalos 		 * skipping.
563e9778795SPeter Avalos 		 */
564e9778795SPeter Avalos 		for (i = 0; i < ctx->nkeys; i++) {
56550a69bb5SSascha Wildner 			if (!sshkey_equal(ctx->keys[i], l->key))
56650a69bb5SSascha Wildner 				continue;
56750a69bb5SSascha Wildner 			ctx->match_keys[i] |= l->match;
568e9778795SPeter Avalos 			fprintf(ctx->out, "%s\n", l->line);
56950a69bb5SSascha Wildner 			debug3_f("%s key already at %s:%ld",
570e9778795SPeter Avalos 			    sshkey_type(l->key), l->path, l->linenum);
57118de8d7fSPeter Avalos 			return 0;
57218de8d7fSPeter Avalos 		}
57318de8d7fSPeter Avalos 
574e9778795SPeter Avalos 		/*
575e9778795SPeter Avalos 		 * Hostname matches and has no CA/revoke marker, delete it
576e9778795SPeter Avalos 		 * by *not* writing the line to ctx->out.
577e9778795SPeter Avalos 		 */
578e9778795SPeter Avalos 		do_log2(loglevel, "%s%s%s:%ld: Removed %s key for host %s",
579e9778795SPeter Avalos 		    ctx->quiet ? __func__ : "", ctx->quiet ? ": " : "",
580e9778795SPeter Avalos 		    l->path, l->linenum, sshkey_type(l->key), ctx->host);
581e9778795SPeter Avalos 		ctx->modified = 1;
582e9778795SPeter Avalos 		return 0;
58318de8d7fSPeter Avalos 	}
584e9778795SPeter Avalos 	/* Retain non-matching hosts and invalid lines when deleting */
585e9778795SPeter Avalos 	if (l->status == HKF_STATUS_INVALID) {
586e9778795SPeter Avalos 		do_log2(loglevel, "%s%s%s:%ld: invalid known_hosts entry",
587e9778795SPeter Avalos 		    ctx->quiet ? __func__ : "", ctx->quiet ? ": " : "",
588e9778795SPeter Avalos 		    l->path, l->linenum);
589e9778795SPeter Avalos 	}
590e9778795SPeter Avalos 	fprintf(ctx->out, "%s\n", l->line);
591e9778795SPeter Avalos 	return 0;
592e9778795SPeter Avalos }
593e9778795SPeter Avalos 
594e9778795SPeter Avalos int
hostfile_replace_entries(const char * filename,const char * host,const char * ip,struct sshkey ** keys,size_t nkeys,int store_hash,int quiet,int hash_alg)595e9778795SPeter Avalos hostfile_replace_entries(const char *filename, const char *host, const char *ip,
596e9778795SPeter Avalos     struct sshkey **keys, size_t nkeys, int store_hash, int quiet, int hash_alg)
597e9778795SPeter Avalos {
598e9778795SPeter Avalos 	int r, fd, oerrno = 0;
599e9778795SPeter Avalos 	int loglevel = quiet ? SYSLOG_LEVEL_DEBUG1 : SYSLOG_LEVEL_VERBOSE;
600e9778795SPeter Avalos 	struct host_delete_ctx ctx;
601e9778795SPeter Avalos 	char *fp, *temp = NULL, *back = NULL;
60250a69bb5SSascha Wildner 	const char *what;
603e9778795SPeter Avalos 	mode_t omask;
604e9778795SPeter Avalos 	size_t i;
60550a69bb5SSascha Wildner 	u_int want;
606e9778795SPeter Avalos 
607e9778795SPeter Avalos 	omask = umask(077);
608e9778795SPeter Avalos 
609e9778795SPeter Avalos 	memset(&ctx, 0, sizeof(ctx));
610e9778795SPeter Avalos 	ctx.host = host;
61150a69bb5SSascha Wildner 	ctx.ip = ip;
612e9778795SPeter Avalos 	ctx.quiet = quiet;
61350a69bb5SSascha Wildner 
61450a69bb5SSascha Wildner 	if ((ctx.match_keys = calloc(nkeys, sizeof(*ctx.match_keys))) == NULL)
615e9778795SPeter Avalos 		return SSH_ERR_ALLOC_FAIL;
616e9778795SPeter Avalos 	ctx.keys = keys;
617e9778795SPeter Avalos 	ctx.nkeys = nkeys;
618e9778795SPeter Avalos 	ctx.modified = 0;
619e9778795SPeter Avalos 
620e9778795SPeter Avalos 	/*
621e9778795SPeter Avalos 	 * Prepare temporary file for in-place deletion.
622e9778795SPeter Avalos 	 */
6230cbfa66cSDaniel Fojt 	if ((r = asprintf(&temp, "%s.XXXXXXXXXXX", filename)) == -1 ||
6240cbfa66cSDaniel Fojt 	    (r = asprintf(&back, "%s.old", filename)) == -1) {
625e9778795SPeter Avalos 		r = SSH_ERR_ALLOC_FAIL;
626e9778795SPeter Avalos 		goto fail;
627e9778795SPeter Avalos 	}
628e9778795SPeter Avalos 
629e9778795SPeter Avalos 	if ((fd = mkstemp(temp)) == -1) {
630e9778795SPeter Avalos 		oerrno = errno;
63150a69bb5SSascha Wildner 		error_f("mkstemp: %s", strerror(oerrno));
632e9778795SPeter Avalos 		r = SSH_ERR_SYSTEM_ERROR;
633e9778795SPeter Avalos 		goto fail;
634e9778795SPeter Avalos 	}
635e9778795SPeter Avalos 	if ((ctx.out = fdopen(fd, "w")) == NULL) {
636e9778795SPeter Avalos 		oerrno = errno;
637e9778795SPeter Avalos 		close(fd);
63850a69bb5SSascha Wildner 		error_f("fdopen: %s", strerror(oerrno));
639e9778795SPeter Avalos 		r = SSH_ERR_SYSTEM_ERROR;
640e9778795SPeter Avalos 		goto fail;
641e9778795SPeter Avalos 	}
642e9778795SPeter Avalos 
64350a69bb5SSascha Wildner 	/* Remove stale/mismatching entries for the specified host */
644e9778795SPeter Avalos 	if ((r = hostkeys_foreach(filename, host_delete, &ctx, host, ip,
64550a69bb5SSascha Wildner 	    HKF_WANT_PARSE_KEY, 0)) != 0) {
6460cbfa66cSDaniel Fojt 		oerrno = errno;
64750a69bb5SSascha Wildner 		error_fr(r, "hostkeys_foreach");
648e9778795SPeter Avalos 		goto fail;
649e9778795SPeter Avalos 	}
650e9778795SPeter Avalos 
65150a69bb5SSascha Wildner 	/* Re-add the requested keys */
65250a69bb5SSascha Wildner 	want = HKF_MATCH_HOST | (ip == NULL ? 0 : HKF_MATCH_IP);
653e9778795SPeter Avalos 	for (i = 0; i < nkeys; i++) {
654ee116499SAntonio Huete Jimenez 		if (keys[i] == NULL || (want & ctx.match_keys[i]) == want)
655e9778795SPeter Avalos 			continue;
656e9778795SPeter Avalos 		if ((fp = sshkey_fingerprint(keys[i], hash_alg,
657e9778795SPeter Avalos 		    SSH_FP_DEFAULT)) == NULL) {
658e9778795SPeter Avalos 			r = SSH_ERR_ALLOC_FAIL;
659e9778795SPeter Avalos 			goto fail;
660e9778795SPeter Avalos 		}
66150a69bb5SSascha Wildner 		/* write host/ip */
66250a69bb5SSascha Wildner 		what = "";
66350a69bb5SSascha Wildner 		if (ctx.match_keys[i] == 0) {
66450a69bb5SSascha Wildner 			what = "Adding new key";
66550a69bb5SSascha Wildner 			if (!write_host_entry(ctx.out, host, ip,
66650a69bb5SSascha Wildner 			    keys[i], store_hash)) {
667e9778795SPeter Avalos 				r = SSH_ERR_INTERNAL_ERROR;
668e9778795SPeter Avalos 				goto fail;
669e9778795SPeter Avalos 			}
67050a69bb5SSascha Wildner 		} else if ((want & ~ctx.match_keys[i]) == HKF_MATCH_HOST) {
67150a69bb5SSascha Wildner 			what = "Fixing match (hostname)";
67250a69bb5SSascha Wildner 			if (!write_host_entry(ctx.out, host, NULL,
67350a69bb5SSascha Wildner 			    keys[i], store_hash)) {
67450a69bb5SSascha Wildner 				r = SSH_ERR_INTERNAL_ERROR;
67550a69bb5SSascha Wildner 				goto fail;
67650a69bb5SSascha Wildner 			}
67750a69bb5SSascha Wildner 		} else if ((want & ~ctx.match_keys[i]) == HKF_MATCH_IP) {
67850a69bb5SSascha Wildner 			what = "Fixing match (address)";
67950a69bb5SSascha Wildner 			if (!write_host_entry(ctx.out, ip, NULL,
68050a69bb5SSascha Wildner 			    keys[i], store_hash)) {
68150a69bb5SSascha Wildner 				r = SSH_ERR_INTERNAL_ERROR;
68250a69bb5SSascha Wildner 				goto fail;
68350a69bb5SSascha Wildner 			}
68450a69bb5SSascha Wildner 		}
68550a69bb5SSascha Wildner 		do_log2(loglevel, "%s%s%s for %s%s%s to %s: %s %s",
68650a69bb5SSascha Wildner 		    quiet ? __func__ : "", quiet ? ": " : "", what,
68750a69bb5SSascha Wildner 		    host, ip == NULL ? "" : ",", ip == NULL ? "" : ip, filename,
68850a69bb5SSascha Wildner 		    sshkey_ssh_name(keys[i]), fp);
68950a69bb5SSascha Wildner 		free(fp);
690e9778795SPeter Avalos 		ctx.modified = 1;
691e9778795SPeter Avalos 	}
692e9778795SPeter Avalos 	fclose(ctx.out);
693e9778795SPeter Avalos 	ctx.out = NULL;
694e9778795SPeter Avalos 
695e9778795SPeter Avalos 	if (ctx.modified) {
696e9778795SPeter Avalos 		/* Backup the original file and replace it with the temporary */
697e9778795SPeter Avalos 		if (unlink(back) == -1 && errno != ENOENT) {
698e9778795SPeter Avalos 			oerrno = errno;
69950a69bb5SSascha Wildner 			error_f("unlink %.100s: %s", back, strerror(errno));
700e9778795SPeter Avalos 			r = SSH_ERR_SYSTEM_ERROR;
701e9778795SPeter Avalos 			goto fail;
702e9778795SPeter Avalos 		}
703e9778795SPeter Avalos 		if (link(filename, back) == -1) {
704e9778795SPeter Avalos 			oerrno = errno;
70550a69bb5SSascha Wildner 			error_f("link %.100s to %.100s: %s", filename,
70650a69bb5SSascha Wildner 			    back, strerror(errno));
707e9778795SPeter Avalos 			r = SSH_ERR_SYSTEM_ERROR;
708e9778795SPeter Avalos 			goto fail;
709e9778795SPeter Avalos 		}
710e9778795SPeter Avalos 		if (rename(temp, filename) == -1) {
711e9778795SPeter Avalos 			oerrno = errno;
71250a69bb5SSascha Wildner 			error_f("rename \"%s\" to \"%s\": %s", temp,
71350a69bb5SSascha Wildner 			    filename, strerror(errno));
714e9778795SPeter Avalos 			r = SSH_ERR_SYSTEM_ERROR;
715e9778795SPeter Avalos 			goto fail;
716e9778795SPeter Avalos 		}
717e9778795SPeter Avalos 	} else {
718e9778795SPeter Avalos 		/* No changes made; just delete the temporary file */
719e9778795SPeter Avalos 		if (unlink(temp) != 0)
72050a69bb5SSascha Wildner 			error_f("unlink \"%s\": %s", temp, strerror(errno));
721e9778795SPeter Avalos 	}
722e9778795SPeter Avalos 
723e9778795SPeter Avalos 	/* success */
724e9778795SPeter Avalos 	r = 0;
725e9778795SPeter Avalos  fail:
726e9778795SPeter Avalos 	if (temp != NULL && r != 0)
727e9778795SPeter Avalos 		unlink(temp);
728e9778795SPeter Avalos 	free(temp);
729e9778795SPeter Avalos 	free(back);
730e9778795SPeter Avalos 	if (ctx.out != NULL)
731e9778795SPeter Avalos 		fclose(ctx.out);
73250a69bb5SSascha Wildner 	free(ctx.match_keys);
733e9778795SPeter Avalos 	umask(omask);
734e9778795SPeter Avalos 	if (r == SSH_ERR_SYSTEM_ERROR)
735e9778795SPeter Avalos 		errno = oerrno;
736e9778795SPeter Avalos 	return r;
737e9778795SPeter Avalos }
738e9778795SPeter Avalos 
739e9778795SPeter Avalos static int
match_maybe_hashed(const char * host,const char * names,int * was_hashed)740e9778795SPeter Avalos match_maybe_hashed(const char *host, const char *names, int *was_hashed)
741e9778795SPeter Avalos {
742ee116499SAntonio Huete Jimenez 	int hashed = *names == HASH_DELIM, ret;
743ee116499SAntonio Huete Jimenez 	char *hashed_host = NULL;
744e9778795SPeter Avalos 	size_t nlen = strlen(names);
745e9778795SPeter Avalos 
746e9778795SPeter Avalos 	if (was_hashed != NULL)
747e9778795SPeter Avalos 		*was_hashed = hashed;
748e9778795SPeter Avalos 	if (hashed) {
749e9778795SPeter Avalos 		if ((hashed_host = host_hash(host, names, nlen)) == NULL)
750e9778795SPeter Avalos 			return -1;
751ee116499SAntonio Huete Jimenez 		ret = (nlen == strlen(hashed_host) &&
752ee116499SAntonio Huete Jimenez 		    strncmp(hashed_host, names, nlen) == 0);
753ee116499SAntonio Huete Jimenez 		free(hashed_host);
754ee116499SAntonio Huete Jimenez 		return ret;
755e9778795SPeter Avalos 	}
756e9778795SPeter Avalos 	return match_hostname(host, names) == 1;
757e9778795SPeter Avalos }
758e9778795SPeter Avalos 
759e9778795SPeter Avalos int
hostkeys_foreach_file(const char * path,FILE * f,hostkeys_foreach_fn * callback,void * ctx,const char * host,const char * ip,u_int options,u_int note)76050a69bb5SSascha Wildner hostkeys_foreach_file(const char *path, FILE *f, hostkeys_foreach_fn *callback,
76150a69bb5SSascha Wildner     void *ctx, const char *host, const char *ip, u_int options, u_int note)
762e9778795SPeter Avalos {
763664f4763Szrj 	char *line = NULL, ktype[128];
764e9778795SPeter Avalos 	u_long linenum = 0;
765e9778795SPeter Avalos 	char *cp, *cp2;
766e9778795SPeter Avalos 	u_int kbits;
767e9778795SPeter Avalos 	int hashed;
768e9778795SPeter Avalos 	int s, r = 0;
769e9778795SPeter Avalos 	struct hostkey_foreach_line lineinfo;
770664f4763Szrj 	size_t linesize = 0, l;
771e9778795SPeter Avalos 
772e9778795SPeter Avalos 	memset(&lineinfo, 0, sizeof(lineinfo));
773e9778795SPeter Avalos 	if (host == NULL && (options & HKF_WANT_MATCH) != 0)
774e9778795SPeter Avalos 		return SSH_ERR_INVALID_ARGUMENT;
775e9778795SPeter Avalos 
776664f4763Szrj 	while (getline(&line, &linesize, f) != -1) {
777664f4763Szrj 		linenum++;
778e9778795SPeter Avalos 		line[strcspn(line, "\n")] = '\0';
779e9778795SPeter Avalos 
780664f4763Szrj 		free(lineinfo.line);
781e9778795SPeter Avalos 		sshkey_free(lineinfo.key);
782e9778795SPeter Avalos 		memset(&lineinfo, 0, sizeof(lineinfo));
783e9778795SPeter Avalos 		lineinfo.path = path;
784e9778795SPeter Avalos 		lineinfo.linenum = linenum;
785664f4763Szrj 		lineinfo.line = xstrdup(line);
786e9778795SPeter Avalos 		lineinfo.marker = MRK_NONE;
787e9778795SPeter Avalos 		lineinfo.status = HKF_STATUS_OK;
788e9778795SPeter Avalos 		lineinfo.keytype = KEY_UNSPEC;
78950a69bb5SSascha Wildner 		lineinfo.note = note;
790e9778795SPeter Avalos 
791e9778795SPeter Avalos 		/* Skip any leading whitespace, comments and empty lines. */
792e9778795SPeter Avalos 		for (cp = line; *cp == ' ' || *cp == '\t'; cp++)
793e9778795SPeter Avalos 			;
794e9778795SPeter Avalos 		if (!*cp || *cp == '#' || *cp == '\n') {
795e9778795SPeter Avalos 			if ((options & HKF_WANT_MATCH) == 0) {
796e9778795SPeter Avalos 				lineinfo.status = HKF_STATUS_COMMENT;
797e9778795SPeter Avalos 				if ((r = callback(&lineinfo, ctx)) != 0)
798e9778795SPeter Avalos 					break;
799e9778795SPeter Avalos 			}
800e9778795SPeter Avalos 			continue;
801e9778795SPeter Avalos 		}
802e9778795SPeter Avalos 
803e9778795SPeter Avalos 		if ((lineinfo.marker = check_markers(&cp)) == MRK_ERROR) {
80450a69bb5SSascha Wildner 			verbose_f("invalid marker at %s:%lu", path, linenum);
805e9778795SPeter Avalos 			if ((options & HKF_WANT_MATCH) == 0)
806e9778795SPeter Avalos 				goto bad;
807e9778795SPeter Avalos 			continue;
808e9778795SPeter Avalos 		}
809e9778795SPeter Avalos 
810e9778795SPeter Avalos 		/* Find the end of the host name portion. */
811e9778795SPeter Avalos 		for (cp2 = cp; *cp2 && *cp2 != ' ' && *cp2 != '\t'; cp2++)
812e9778795SPeter Avalos 			;
813e9778795SPeter Avalos 		lineinfo.hosts = cp;
814e9778795SPeter Avalos 		*cp2++ = '\0';
815e9778795SPeter Avalos 
816e9778795SPeter Avalos 		/* Check if the host name matches. */
817e9778795SPeter Avalos 		if (host != NULL) {
818e9778795SPeter Avalos 			if ((s = match_maybe_hashed(host, lineinfo.hosts,
819e9778795SPeter Avalos 			    &hashed)) == -1) {
82050a69bb5SSascha Wildner 				debug2_f("%s:%ld: bad host hash \"%.32s\"",
82150a69bb5SSascha Wildner 				    path, linenum, lineinfo.hosts);
822e9778795SPeter Avalos 				goto bad;
823e9778795SPeter Avalos 			}
824e9778795SPeter Avalos 			if (s == 1) {
825e9778795SPeter Avalos 				lineinfo.status = HKF_STATUS_MATCHED;
826e9778795SPeter Avalos 				lineinfo.match |= HKF_MATCH_HOST |
827e9778795SPeter Avalos 				    (hashed ? HKF_MATCH_HOST_HASHED : 0);
828e9778795SPeter Avalos 			}
829e9778795SPeter Avalos 			/* Try matching IP address if supplied */
830e9778795SPeter Avalos 			if (ip != NULL) {
831e9778795SPeter Avalos 				if ((s = match_maybe_hashed(ip, lineinfo.hosts,
832e9778795SPeter Avalos 				    &hashed)) == -1) {
83350a69bb5SSascha Wildner 					debug2_f("%s:%ld: bad ip hash "
83450a69bb5SSascha Wildner 					    "\"%.32s\"", path, linenum,
83550a69bb5SSascha Wildner 					    lineinfo.hosts);
836e9778795SPeter Avalos 					goto bad;
837e9778795SPeter Avalos 				}
838e9778795SPeter Avalos 				if (s == 1) {
839e9778795SPeter Avalos 					lineinfo.status = HKF_STATUS_MATCHED;
840e9778795SPeter Avalos 					lineinfo.match |= HKF_MATCH_IP |
841e9778795SPeter Avalos 					    (hashed ? HKF_MATCH_IP_HASHED : 0);
842e9778795SPeter Avalos 				}
843e9778795SPeter Avalos 			}
844e9778795SPeter Avalos 			/*
845e9778795SPeter Avalos 			 * Skip this line if host matching requested and
846e9778795SPeter Avalos 			 * neither host nor address matched.
847e9778795SPeter Avalos 			 */
848e9778795SPeter Avalos 			if ((options & HKF_WANT_MATCH) != 0 &&
849e9778795SPeter Avalos 			    lineinfo.status != HKF_STATUS_MATCHED)
850e9778795SPeter Avalos 				continue;
851e9778795SPeter Avalos 		}
852e9778795SPeter Avalos 
853e9778795SPeter Avalos 		/* Got a match.  Skip host name and any following whitespace */
854e9778795SPeter Avalos 		for (; *cp2 == ' ' || *cp2 == '\t'; cp2++)
855e9778795SPeter Avalos 			;
856e9778795SPeter Avalos 		if (*cp2 == '\0' || *cp2 == '#') {
857e9778795SPeter Avalos 			debug2("%s:%ld: truncated before key type",
858e9778795SPeter Avalos 			    path, linenum);
859e9778795SPeter Avalos 			goto bad;
860e9778795SPeter Avalos 		}
861e9778795SPeter Avalos 		lineinfo.rawkey = cp = cp2;
862e9778795SPeter Avalos 
863e9778795SPeter Avalos 		if ((options & HKF_WANT_PARSE_KEY) != 0) {
864e9778795SPeter Avalos 			/*
865e9778795SPeter Avalos 			 * Extract the key from the line.  This will skip
866e9778795SPeter Avalos 			 * any leading whitespace.  Ignore badly formatted
867e9778795SPeter Avalos 			 * lines.
868e9778795SPeter Avalos 			 */
869e9778795SPeter Avalos 			if ((lineinfo.key = sshkey_new(KEY_UNSPEC)) == NULL) {
87050a69bb5SSascha Wildner 				error_f("sshkey_new failed");
871e9778795SPeter Avalos 				r = SSH_ERR_ALLOC_FAIL;
872e9778795SPeter Avalos 				break;
873e9778795SPeter Avalos 			}
874e9778795SPeter Avalos 			if (!hostfile_read_key(&cp, &kbits, lineinfo.key)) {
875e9778795SPeter Avalos 				goto bad;
876e9778795SPeter Avalos 			}
877e9778795SPeter Avalos 			lineinfo.keytype = lineinfo.key->type;
878e9778795SPeter Avalos 			lineinfo.comment = cp;
879e9778795SPeter Avalos 		} else {
880e9778795SPeter Avalos 			/* Extract and parse key type */
881e9778795SPeter Avalos 			l = strcspn(lineinfo.rawkey, " \t");
882e9778795SPeter Avalos 			if (l <= 1 || l >= sizeof(ktype) ||
883e9778795SPeter Avalos 			    lineinfo.rawkey[l] == '\0')
884e9778795SPeter Avalos 				goto bad;
885e9778795SPeter Avalos 			memcpy(ktype, lineinfo.rawkey, l);
886e9778795SPeter Avalos 			ktype[l] = '\0';
887e9778795SPeter Avalos 			lineinfo.keytype = sshkey_type_from_name(ktype);
888e9778795SPeter Avalos 
889e9778795SPeter Avalos 			/*
890ce74bacaSMatthew Dillon 			 * Assume legacy RSA1 if the first component is a short
891e9778795SPeter Avalos 			 * decimal number.
892e9778795SPeter Avalos 			 */
893e9778795SPeter Avalos 			if (lineinfo.keytype == KEY_UNSPEC && l < 8 &&
894e9778795SPeter Avalos 			    strspn(ktype, "0123456789") == l)
895ce74bacaSMatthew Dillon 				goto bad;
896e9778795SPeter Avalos 
897e9778795SPeter Avalos 			/*
898e9778795SPeter Avalos 			 * Check that something other than whitespace follows
899e9778795SPeter Avalos 			 * the key type. This won't catch all corruption, but
900e9778795SPeter Avalos 			 * it does catch trivial truncation.
901e9778795SPeter Avalos 			 */
902e9778795SPeter Avalos 			cp2 += l; /* Skip past key type */
903e9778795SPeter Avalos 			for (; *cp2 == ' ' || *cp2 == '\t'; cp2++)
904e9778795SPeter Avalos 				;
905e9778795SPeter Avalos 			if (*cp2 == '\0' || *cp2 == '#') {
906e9778795SPeter Avalos 				debug2("%s:%ld: truncated after key type",
907e9778795SPeter Avalos 				    path, linenum);
908e9778795SPeter Avalos 				lineinfo.keytype = KEY_UNSPEC;
909e9778795SPeter Avalos 			}
910e9778795SPeter Avalos 			if (lineinfo.keytype == KEY_UNSPEC) {
911e9778795SPeter Avalos  bad:
912e9778795SPeter Avalos 				sshkey_free(lineinfo.key);
913e9778795SPeter Avalos 				lineinfo.key = NULL;
914e9778795SPeter Avalos 				lineinfo.status = HKF_STATUS_INVALID;
915e9778795SPeter Avalos 				if ((r = callback(&lineinfo, ctx)) != 0)
916e9778795SPeter Avalos 					break;
917e9778795SPeter Avalos 				continue;
918e9778795SPeter Avalos 			}
919e9778795SPeter Avalos 		}
920e9778795SPeter Avalos 		if ((r = callback(&lineinfo, ctx)) != 0)
921e9778795SPeter Avalos 			break;
922e9778795SPeter Avalos 	}
923e9778795SPeter Avalos 	sshkey_free(lineinfo.key);
924664f4763Szrj 	free(lineinfo.line);
925664f4763Szrj 	free(line);
92650a69bb5SSascha Wildner 	return r;
92750a69bb5SSascha Wildner }
92850a69bb5SSascha Wildner 
92950a69bb5SSascha Wildner int
hostkeys_foreach(const char * path,hostkeys_foreach_fn * callback,void * ctx,const char * host,const char * ip,u_int options,u_int note)93050a69bb5SSascha Wildner hostkeys_foreach(const char *path, hostkeys_foreach_fn *callback, void *ctx,
93150a69bb5SSascha Wildner     const char *host, const char *ip, u_int options, u_int note)
93250a69bb5SSascha Wildner {
93350a69bb5SSascha Wildner 	FILE *f;
93450a69bb5SSascha Wildner 	int r, oerrno;
93550a69bb5SSascha Wildner 
93650a69bb5SSascha Wildner 	if ((f = fopen(path, "r")) == NULL)
93750a69bb5SSascha Wildner 		return SSH_ERR_SYSTEM_ERROR;
93850a69bb5SSascha Wildner 
93950a69bb5SSascha Wildner 	debug3_f("reading file \"%s\"", path);
94050a69bb5SSascha Wildner 	r = hostkeys_foreach_file(path, f, callback, ctx, host, ip,
94150a69bb5SSascha Wildner 	    options, note);
94250a69bb5SSascha Wildner 	oerrno = errno;
94318de8d7fSPeter Avalos 	fclose(f);
94450a69bb5SSascha Wildner 	errno = oerrno;
945e9778795SPeter Avalos 	return r;
94618de8d7fSPeter Avalos }
947