From 6c5c949782d86a6e7d58006599c7685bfcd01685 Mon Sep 17 00:00:00 2001 From: "djm@openbsd.org" Date: Mon, 16 Feb 2015 22:08:57 +0000 Subject: upstream commit Refactor hostkeys_foreach() and dependent code Deal with IP addresses (i.e. CheckHostIP) Don't clobber known_hosts when nothing changed ok markus@ as part of larger commit --- hostfile.c | 219 ++++++++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 143 insertions(+), 76 deletions(-) (limited to 'hostfile.c') diff --git a/hostfile.c b/hostfile.c index ea6bc6fc..b235795e 100644 --- a/hostfile.c +++ b/hostfile.c @@ -1,4 +1,4 @@ -/* $OpenBSD: hostfile.c,v 1.63 2015/01/26 13:36:53 djm Exp $ */ +/* $OpenBSD: hostfile.c,v 1.64 2015/02/16 22:08:57 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -184,24 +184,6 @@ hostfile_read_key(char **cpp, u_int *bitsp, struct sshkey *ret) return 1; } -static int -hostfile_check_key(int bits, const struct sshkey *key, const char *host, - const char *filename, u_long linenum) -{ -#ifdef WITH_SSH1 - if (key == NULL || key->type != KEY_RSA1 || key->rsa == NULL) - return 1; - if (bits != BN_num_bits(key->rsa->n)) { - logit("Warning: %s, line %lu: keysize mismatch for host %s: " - "actual %d vs. announced %d.", - filename, linenum, host, BN_num_bits(key->rsa->n), bits); - logit("Warning: replace %d with %d in %s, line %lu.", - bits, BN_num_bits(key->rsa->n), filename, linenum); - } -#endif - return 1; -} - static HostkeyMarker check_markers(char **cpp) { @@ -295,8 +277,8 @@ load_hostkeys(struct hostkeys *hostkeys, const char *host, const char *path) ctx.num_loaded = 0; ctx.hostkeys = hostkeys; - if ((r = hostkeys_foreach(path, record_hostkey, &ctx, host, - HKF_WANT_MATCH_HOST|HKF_WANT_PARSE_KEY)) != 0) { + if ((r = hostkeys_foreach(path, record_hostkey, &ctx, host, NULL, + HKF_WANT_MATCH|HKF_WANT_PARSE_KEY)) != 0) { if (r != SSH_ERR_SYSTEM_ERROR && errno != ENOENT) debug("%s: hostkeys_foreach failed for %s: %s", __func__, path, ssh_err(r)); @@ -433,7 +415,7 @@ lookup_key_in_hostkeys_by_type(struct hostkeys *hostkeys, int keytype, } static int -write_host_entry(FILE *f, const char *host, +write_host_entry(FILE *f, const char *host, const char *ip, const struct sshkey *key, int store_hash) { int r, success = 0; @@ -444,8 +426,11 @@ write_host_entry(FILE *f, const char *host, error("%s: host_hash failed", __func__); return 0; } - } - fprintf(f, "%s ", store_hash ? hashed_host : host); + fprintf(f, "%s ", hashed_host); + } else if (ip != NULL) + fprintf(f, "%s,%s ", host, ip); + else + fprintf(f, "%s ", host); if ((r = sshkey_write(key, f)) == 0) success = 1; @@ -471,7 +456,7 @@ add_host_to_hostfile(const char *filename, const char *host, f = fopen(filename, "a"); if (!f) return 0; - success = write_host_entry(f, host, key, store_hash); + success = write_host_entry(f, host, NULL, key, store_hash); fclose(f); return success; } @@ -480,19 +465,20 @@ struct host_delete_ctx { FILE *out; int quiet; const char *host; - int *skip_keys; + int *skip_keys; /* XXX split for host/ip? might want to ensure both */ struct sshkey * const *keys; size_t nkeys; + int modified; }; static int host_delete(struct hostkey_foreach_line *l, void *_ctx) { struct host_delete_ctx *ctx = (struct host_delete_ctx *)_ctx; - int loglevel = ctx->quiet ? SYSLOG_LEVEL_DEBUG1 : SYSLOG_LEVEL_INFO; + int loglevel = ctx->quiet ? SYSLOG_LEVEL_DEBUG1 : SYSLOG_LEVEL_VERBOSE; size_t i; - if (l->status == HKF_STATUS_HOST_MATCHED) { + if (l->status == HKF_STATUS_MATCHED) { if (l->marker != MRK_NONE) { /* Don't remove CA and revocation lines */ fprintf(ctx->out, "%s\n", l->line); @@ -525,9 +511,10 @@ host_delete(struct hostkey_foreach_line *l, void *_ctx) * Hostname matches and has no CA/revoke marker, delete it * by *not* writing the line to ctx->out. */ - do_log2(loglevel, "%s%s%s:%ld: Host %s removed", + do_log2(loglevel, "%s%s%s:%ld: Removed %s key for host %s", ctx->quiet ? __func__ : "", ctx->quiet ? ": " : "", - l->path, l->linenum, ctx->host); + l->path, l->linenum, sshkey_type(l->key), ctx->host); + ctx->modified = 1; return 0; } /* Retain non-matching hosts and invalid lines when deleting */ @@ -541,13 +528,13 @@ host_delete(struct hostkey_foreach_line *l, void *_ctx) } int -hostfile_replace_entries(const char *filename, const char *host, - struct sshkey **keys, size_t nkeys, int store_hash, int quiet) +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) { int r, fd, oerrno = 0; - int loglevel = quiet ? SYSLOG_LEVEL_DEBUG1 : SYSLOG_LEVEL_INFO; + int loglevel = quiet ? SYSLOG_LEVEL_DEBUG1 : SYSLOG_LEVEL_VERBOSE; struct host_delete_ctx ctx; - char *temp = NULL, *back = NULL; + char *fp, *temp = NULL, *back = NULL; mode_t omask; size_t i; @@ -560,6 +547,7 @@ hostfile_replace_entries(const char *filename, const char *host, return SSH_ERR_ALLOC_FAIL; ctx.keys = keys; ctx.nkeys = nkeys; + ctx.modified = 0; /* * Prepare temporary file for in-place deletion. @@ -585,7 +573,7 @@ hostfile_replace_entries(const char *filename, const char *host, } /* Remove all entries for the specified host from the file */ - if ((r = hostkeys_foreach(filename, host_delete, &ctx, host, + if ((r = hostkeys_foreach(filename, host_delete, &ctx, host, ip, HKF_WANT_PARSE_KEY)) != 0) { error("%s: hostkeys_foreach failed: %s", __func__, ssh_err(r)); goto fail; @@ -595,38 +583,54 @@ hostfile_replace_entries(const char *filename, const char *host, for (i = 0; i < nkeys; i++) { if (ctx.skip_keys[i]) continue; - do_log2(loglevel, "%s%sadd %s key to %s", - quiet ? __func__ : "", quiet ? ": " : NULL, - sshkey_type(keys[i]), filename); - if (!write_host_entry(ctx.out, host, keys[i], store_hash)) { + if ((fp = sshkey_fingerprint(keys[i], hash_alg, + SSH_FP_DEFAULT)) == NULL) { + r = SSH_ERR_ALLOC_FAIL; + goto fail; + } + do_log2(loglevel, "%s%sAdding new key for %s to %s: %s %s", + quiet ? __func__ : "", quiet ? ": " : "", host, filename, + sshkey_ssh_name(keys[i]), fp); + free(fp); + if (!write_host_entry(ctx.out, host, ip, keys[i], store_hash)) { r = SSH_ERR_INTERNAL_ERROR; goto fail; } + ctx.modified = 1; } fclose(ctx.out); ctx.out = NULL; - /* Backup the original file and replace it with the temporary */ - if (unlink(back) == -1 && errno != ENOENT) { - oerrno = errno; - error("%s: unlink %.100s: %s", __func__, back, strerror(errno)); - r = SSH_ERR_SYSTEM_ERROR; - goto fail; - } - if (link(filename, back) == -1) { - oerrno = errno; - error("%s: link %.100s to %.100s: %s", __func__, filename, back, - strerror(errno)); - r = SSH_ERR_SYSTEM_ERROR; - goto fail; - } - if (rename(temp, filename) == -1) { - oerrno = errno; - error("%s: rename \"%s\" to \"%s\": %s", __func__, - temp, filename, strerror(errno)); - r = SSH_ERR_SYSTEM_ERROR; - goto fail; + if (ctx.modified) { + /* Backup the original file and replace it with the temporary */ + if (unlink(back) == -1 && errno != ENOENT) { + oerrno = errno; + error("%s: unlink %.100s: %s", __func__, + back, strerror(errno)); + r = SSH_ERR_SYSTEM_ERROR; + goto fail; + } + if (link(filename, back) == -1) { + oerrno = errno; + error("%s: link %.100s to %.100s: %s", __func__, + filename, back, strerror(errno)); + r = SSH_ERR_SYSTEM_ERROR; + goto fail; + } + if (rename(temp, filename) == -1) { + oerrno = errno; + error("%s: rename \"%s\" to \"%s\": %s", __func__, + temp, filename, strerror(errno)); + r = SSH_ERR_SYSTEM_ERROR; + goto fail; + } + } else { + /* No changes made; just delete the temporary file */ + if (unlink(temp) != 0) + error("%s: unlink \"%s\": %s", __func__, + temp, strerror(errno)); } + /* success */ r = 0; fail: @@ -663,18 +667,20 @@ match_maybe_hashed(const char *host, const char *names, int *was_hashed) int hostkeys_foreach(const char *path, hostkeys_foreach_fn *callback, void *ctx, - const char *host, u_int options) + const char *host, const char *ip, u_int options) { FILE *f; - char line[8192], oline[8192]; + char line[8192], oline[8192], ktype[128]; u_long linenum = 0; char *cp, *cp2; u_int kbits; + int hashed; int s, r = 0; struct hostkey_foreach_line lineinfo; + size_t l; memset(&lineinfo, 0, sizeof(lineinfo)); - if (host == NULL && (options & HKF_WANT_MATCH_HOST) != 0) + if (host == NULL && (options & HKF_WANT_MATCH) != 0) return SSH_ERR_INVALID_ARGUMENT; if ((f = fopen(path, "r")) == NULL) return SSH_ERR_SYSTEM_ERROR; @@ -689,13 +695,15 @@ hostkeys_foreach(const char *path, hostkeys_foreach_fn *callback, void *ctx, lineinfo.path = path; lineinfo.linenum = linenum; lineinfo.line = oline; + lineinfo.marker = MRK_NONE; lineinfo.status = HKF_STATUS_OK; + lineinfo.keytype = KEY_UNSPEC; /* Skip any leading whitespace, comments and empty lines. */ for (cp = line; *cp == ' ' || *cp == '\t'; cp++) ; if (!*cp || *cp == '#' || *cp == '\n') { - if ((options & HKF_WANT_MATCH_HOST) == 0) { + if ((options & HKF_WANT_MATCH) == 0) { lineinfo.status = HKF_STATUS_COMMENT; if ((r = callback(&lineinfo, ctx)) != 0) break; @@ -706,7 +714,7 @@ hostkeys_foreach(const char *path, hostkeys_foreach_fn *callback, void *ctx, if ((lineinfo.marker = check_markers(&cp)) == MRK_ERROR) { verbose("%s: invalid marker at %s:%lu", __func__, path, linenum); - if ((options & HKF_WANT_MATCH_HOST) == 0) + if ((options & HKF_WANT_MATCH) == 0) goto bad; continue; } @@ -719,24 +727,47 @@ hostkeys_foreach(const char *path, hostkeys_foreach_fn *callback, void *ctx, /* Check if the host name matches. */ if (host != NULL) { - s = match_maybe_hashed(host, lineinfo.hosts, - &lineinfo.was_hashed); - if (s == 1) - lineinfo.status = HKF_STATUS_HOST_MATCHED; - else if ((options & HKF_WANT_MATCH_HOST) != 0) - continue; - else if (s == -1) { + if ((s = match_maybe_hashed(host, lineinfo.hosts, + &hashed)) == -1) { debug2("%s: %s:%ld: bad host hash \"%.32s\"", __func__, path, linenum, lineinfo.hosts); goto bad; } + if (s == 1) { + lineinfo.status = HKF_STATUS_MATCHED; + lineinfo.match |= HKF_MATCH_HOST | + (hashed ? HKF_MATCH_HOST_HASHED : 0); + } + /* Try matching IP address if supplied */ + if (ip != NULL) { + if ((s = match_maybe_hashed(ip, lineinfo.hosts, + &hashed)) == -1) { + debug2("%s: %s:%ld: bad ip hash " + "\"%.32s\"", __func__, path, + linenum, lineinfo.hosts); + goto bad; + } + if (s == 1) { + lineinfo.status = HKF_STATUS_MATCHED; + lineinfo.match |= HKF_MATCH_IP | + (hashed ? HKF_MATCH_IP_HASHED : 0); + } + } + /* + * Skip this line if host matching requested and + * neither host nor address matched. + */ + if ((options & HKF_WANT_MATCH) != 0 && + lineinfo.status != HKF_STATUS_MATCHED) + continue; } /* Got a match. Skip host name and any following whitespace */ for (; *cp2 == ' ' || *cp2 == '\t'; cp2++) ; if (*cp2 == '\0' || *cp2 == '#') { - debug2("%s:%ld: truncated before key", path, linenum); + debug2("%s:%ld: truncated before key type", + path, linenum); goto bad; } lineinfo.rawkey = cp = cp2; @@ -749,7 +780,8 @@ hostkeys_foreach(const char *path, hostkeys_foreach_fn *callback, void *ctx, */ if ((lineinfo.key = sshkey_new(KEY_UNSPEC)) == NULL) { error("%s: sshkey_new failed", __func__); - return SSH_ERR_ALLOC_FAIL; + r = SSH_ERR_ALLOC_FAIL; + break; } if (!hostfile_read_key(&cp, &kbits, lineinfo.key)) { #ifdef WITH_SSH1 @@ -757,7 +789,8 @@ hostkeys_foreach(const char *path, hostkeys_foreach_fn *callback, void *ctx, lineinfo.key = sshkey_new(KEY_RSA1); if (lineinfo.key == NULL) { error("%s: sshkey_new fail", __func__); - return SSH_ERR_ALLOC_FAIL; + r = SSH_ERR_ALLOC_FAIL; + break; } if (!hostfile_read_key(&cp, &kbits, lineinfo.key)) @@ -766,9 +799,43 @@ hostkeys_foreach(const char *path, hostkeys_foreach_fn *callback, void *ctx, goto bad; #endif } - if (!hostfile_check_key(kbits, lineinfo.key, host, - path, linenum)) { + lineinfo.keytype = lineinfo.key->type; + lineinfo.comment = cp; + } else { + /* Extract and parse key type */ + l = strcspn(lineinfo.rawkey, " \t"); + if (l <= 1 || l >= sizeof(ktype) || + lineinfo.rawkey[l] == '\0') + goto bad; + memcpy(ktype, lineinfo.rawkey, l); + ktype[l] = '\0'; + lineinfo.keytype = sshkey_type_from_name(ktype); +#ifdef WITH_SSH1 + /* + * Assume RSA1 if the first component is a short + * decimal number. + */ + if (lineinfo.keytype == KEY_UNSPEC && l < 8 && + strspn(ktype, "0123456789") == l) + lineinfo.keytype = KEY_RSA1; +#endif + /* + * Check that something other than whitespace follows + * the key type. This won't catch all corruption, but + * it does catch trivial truncation. + */ + cp2 += l; /* Skip past key type */ + for (; *cp2 == ' ' || *cp2 == '\t'; cp2++) + ; + if (*cp2 == '\0' || *cp2 == '#') { + debug2("%s:%ld: truncated after key type", + path, linenum); + lineinfo.keytype = KEY_UNSPEC; + } + if (lineinfo.keytype == KEY_UNSPEC) { bad: + sshkey_free(lineinfo.key); + lineinfo.key = NULL; lineinfo.status = HKF_STATUS_INVALID; if ((r = callback(&lineinfo, ctx)) != 0) break; -- cgit v1.2.1