version 1.341, 2020/10/18 11:32:02 |
version 1.342, 2020/11/12 22:56:00 |
|
|
return 0; |
return 0; |
} |
} |
|
|
|
struct find_by_key_ctx { |
|
const char *host, *ip; |
|
const struct sshkey *key; |
|
char **names; |
|
u_int nnames; |
|
}; |
|
|
|
/* Try to replace home directory prefix (per $HOME) with a ~/ sequence */ |
|
static char * |
|
try_tilde_unexpand(const char *path) |
|
{ |
|
char *home, *ret = NULL; |
|
size_t l; |
|
|
|
if (*path != '/') |
|
return xstrdup(path); |
|
if ((home = getenv("HOME")) == NULL || (l = strlen(home)) == 0) |
|
return xstrdup(path); |
|
if (strncmp(path, home, l) != 0) |
|
return xstrdup(path); |
|
/* |
|
* ensure we have matched on a path boundary: either the $HOME that |
|
* we just compared ends with a '/' or the next character of the path |
|
* must be a '/'. |
|
*/ |
|
if (home[l - 1] != '/' && path[l] != '/') |
|
return xstrdup(path); |
|
if (path[l] == '/') |
|
l++; |
|
xasprintf(&ret, "~/%s", path + l); |
|
return ret; |
|
} |
|
|
|
static int |
|
hostkeys_find_by_key_cb(struct hostkey_foreach_line *l, void *_ctx) |
|
{ |
|
struct find_by_key_ctx *ctx = (struct find_by_key_ctx *)_ctx; |
|
char *path; |
|
|
|
/* we are looking for keys with names that *do not* match */ |
|
if ((l->match & HKF_MATCH_HOST) != 0) |
|
return 0; |
|
/* not interested in marker lines */ |
|
if (l->marker != MRK_NONE) |
|
return 0; |
|
/* we are only interested in exact key matches */ |
|
if (l->key == NULL || !sshkey_equal(ctx->key, l->key)) |
|
return 0; |
|
path = try_tilde_unexpand(l->path); |
|
debug_f("found matching key in %s:%lu", path, l->linenum); |
|
ctx->names = xrecallocarray(ctx->names, |
|
ctx->nnames, ctx->nnames + 1, sizeof(*ctx->names)); |
|
xasprintf(&ctx->names[ctx->nnames], "%s:%lu: %s", path, l->linenum, |
|
strncmp(l->hosts, HASH_MAGIC, strlen(HASH_MAGIC)) == 0 ? |
|
"[hashed name]" : l->hosts); |
|
ctx->nnames++; |
|
free(path); |
|
return 0; |
|
} |
|
|
|
static int |
|
hostkeys_find_by_key_hostfile(const char *file, const char *which, |
|
struct find_by_key_ctx *ctx) |
|
{ |
|
int r; |
|
|
|
debug3_f("trying %s hostfile \"%s\"", which, file); |
|
if ((r = hostkeys_foreach(file, hostkeys_find_by_key_cb, ctx, |
|
ctx->host, ctx->ip, HKF_WANT_PARSE_KEY)) != 0) { |
|
if (r == SSH_ERR_SYSTEM_ERROR && errno == ENOENT) { |
|
debug_f("hostkeys file %s does not exist", file); |
|
return 0; |
|
} |
|
error_fr(r, "hostkeys_foreach failed for %s", file); |
|
return r; |
|
} |
|
return 0; |
|
} |
|
|
/* |
/* |
|
* Find 'key' in known hosts file(s) that do not match host/ip. |
|
* Used to display also-known-as information for previously-unseen hostkeys. |
|
*/ |
|
static void |
|
hostkeys_find_by_key(const char *host, const char *ip, const struct sshkey *key, |
|
char **user_hostfiles, u_int num_user_hostfiles, |
|
char **system_hostfiles, u_int num_system_hostfiles, |
|
char ***names, u_int *nnames) |
|
{ |
|
struct find_by_key_ctx ctx = {0}; |
|
u_int i; |
|
|
|
*names = NULL; |
|
*nnames = 0; |
|
|
|
if (key == NULL || sshkey_is_cert(key)) |
|
return; |
|
|
|
ctx.host = host; |
|
ctx.ip = ip; |
|
ctx.key = key; |
|
|
|
for (i = 0; i < num_user_hostfiles; i++) { |
|
if (hostkeys_find_by_key_hostfile(user_hostfiles[i], |
|
"user", &ctx) != 0) |
|
goto fail; |
|
} |
|
for (i = 0; i < num_system_hostfiles; i++) { |
|
if (hostkeys_find_by_key_hostfile(system_hostfiles[i], |
|
"system", &ctx) != 0) |
|
goto fail; |
|
} |
|
/* success */ |
|
*names = ctx.names; |
|
*nnames = ctx.nnames; |
|
ctx.names = NULL; |
|
ctx.nnames = 0; |
|
return; |
|
fail: |
|
for (i = 0; i < ctx.nnames; i++) |
|
free(ctx.names[i]); |
|
free(ctx.names); |
|
} |
|
|
|
#define MAX_OTHER_NAMES 8 /* Maximum number of names to list */ |
|
static char * |
|
other_hostkeys_message(const char *host, const char *ip, |
|
const struct sshkey *key, |
|
char **user_hostfiles, u_int num_user_hostfiles, |
|
char **system_hostfiles, u_int num_system_hostfiles) |
|
{ |
|
char *ret = NULL, **othernames = NULL; |
|
u_int i, n, num_othernames = 0; |
|
|
|
hostkeys_find_by_key(host, ip, key, |
|
user_hostfiles, num_user_hostfiles, |
|
system_hostfiles, num_system_hostfiles, |
|
&othernames, &num_othernames); |
|
if (num_othernames == 0) |
|
return xstrdup("This key is not known by any other names"); |
|
|
|
xasprintf(&ret, "This host key is known by the following other " |
|
"names/addresses:"); |
|
|
|
n = num_othernames; |
|
if (n > MAX_OTHER_NAMES) |
|
n = MAX_OTHER_NAMES; |
|
for (i = 0; i < n; i++) { |
|
xextendf(&ret, "\n", " %s", othernames[i]); |
|
} |
|
if (n < num_othernames) { |
|
xextendf(&ret, "\n", " (%d additional names ommitted)", |
|
num_othernames - n); |
|
} |
|
for (i = 0; i < num_othernames; i++) |
|
free(othernames[i]); |
|
free(othernames); |
|
return ret; |
|
} |
|
|
|
/* |
* check whether the supplied host key is valid, return -1 if the key |
* check whether the supplied host key is valid, return -1 if the key |
* is not valid. user_hostfile[0] will not be updated if 'readonly' is true. |
* is not valid. user_hostfile[0] will not be updated if 'readonly' is true. |
*/ |
*/ |
|
|
goto fail; |
goto fail; |
} else if (options.strict_host_key_checking == |
} else if (options.strict_host_key_checking == |
SSH_STRICT_HOSTKEY_ASK) { |
SSH_STRICT_HOSTKEY_ASK) { |
char msg1[1024], msg2[1024]; |
char *msg1 = NULL, *msg2 = NULL; |
|
|
if (show_other_keys(host_hostkeys, host_key)) |
xasprintf(&msg1, "The authenticity of host " |
snprintf(msg1, sizeof(msg1), |
"'%.200s (%s)' can't be established", host, ip); |
"\nbut keys of different type are already" |
|
" known for this host."); |
if (show_other_keys(host_hostkeys, host_key)) { |
else |
xextendf(&msg1, "\n", "but keys of different " |
snprintf(msg1, sizeof(msg1), "."); |
"type are already known for this host."); |
/* The default */ |
} else |
|
xextendf(&msg1, "", "."); |
|
|
fp = sshkey_fingerprint(host_key, |
fp = sshkey_fingerprint(host_key, |
options.fingerprint_hash, SSH_FP_DEFAULT); |
options.fingerprint_hash, SSH_FP_DEFAULT); |
ra = sshkey_fingerprint(host_key, |
ra = sshkey_fingerprint(host_key, |
options.fingerprint_hash, SSH_FP_RANDOMART); |
options.fingerprint_hash, SSH_FP_RANDOMART); |
if (fp == NULL || ra == NULL) |
if (fp == NULL || ra == NULL) |
fatal_f("sshkey_fingerprint failed"); |
fatal_f("sshkey_fingerprint failed"); |
msg2[0] = '\0'; |
xextendf(&msg1, "\n", "%s key fingerprint is %s.", |
|
type, fp); |
|
if (options.visual_host_key) |
|
xextendf(&msg1, "\n", "%s", ra); |
if (options.verify_host_key_dns) { |
if (options.verify_host_key_dns) { |
if (matching_host_key_dns) |
xextendf(&msg1, "\n", |
snprintf(msg2, sizeof(msg2), |
"%s host key fingerprint found in DNS.", |
"Matching host key fingerprint" |
matching_host_key_dns ? |
" found in DNS.\n"); |
"Matching" : "No matching"); |
else |
|
snprintf(msg2, sizeof(msg2), |
|
"No matching host key fingerprint" |
|
" found in DNS.\n"); |
|
} |
} |
snprintf(msg, sizeof(msg), |
/* msg2 informs for other names matching this key */ |
"The authenticity of host '%.200s (%s)' can't be " |
if ((msg2 = other_hostkeys_message(host, ip, host_key, |
"established%s\n" |
user_hostfiles, num_user_hostfiles, |
"%s key fingerprint is %s.%s%s\n%s" |
system_hostfiles, num_system_hostfiles)) != NULL) |
|
xextendf(&msg1, "\n", "%s", msg2); |
|
|
|
xextendf(&msg1, "\n", |
"Are you sure you want to continue connecting " |
"Are you sure you want to continue connecting " |
"(yes/no/[fingerprint])? ", |
"(yes/no/[fingerprint])? "); |
host, ip, msg1, type, fp, |
|
options.visual_host_key ? "\n" : "", |
confirmed = confirm(msg1, fp); |
options.visual_host_key ? ra : "", |
|
msg2); |
|
free(ra); |
free(ra); |
confirmed = confirm(msg, fp); |
|
free(fp); |
free(fp); |
|
free(msg1); |
|
free(msg2); |
if (!confirmed) |
if (!confirmed) |
goto fail; |
goto fail; |
hostkey_trusted = 1; /* user explicitly confirmed */ |
hostkey_trusted = 1; /* user explicitly confirmed */ |