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