version 1.63, 2015/01/26 13:36:53 |
version 1.64, 2015/02/16 22:08:57 |
|
|
return 1; |
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 |
static HostkeyMarker |
check_markers(char **cpp) |
check_markers(char **cpp) |
{ |
{ |
|
|
ctx.num_loaded = 0; |
ctx.num_loaded = 0; |
ctx.hostkeys = hostkeys; |
ctx.hostkeys = hostkeys; |
|
|
if ((r = hostkeys_foreach(path, record_hostkey, &ctx, host, |
if ((r = hostkeys_foreach(path, record_hostkey, &ctx, host, NULL, |
HKF_WANT_MATCH_HOST|HKF_WANT_PARSE_KEY)) != 0) { |
HKF_WANT_MATCH|HKF_WANT_PARSE_KEY)) != 0) { |
if (r != SSH_ERR_SYSTEM_ERROR && errno != ENOENT) |
if (r != SSH_ERR_SYSTEM_ERROR && errno != ENOENT) |
debug("%s: hostkeys_foreach failed for %s: %s", |
debug("%s: hostkeys_foreach failed for %s: %s", |
__func__, path, ssh_err(r)); |
__func__, path, ssh_err(r)); |
|
|
} |
} |
|
|
static int |
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) |
const struct sshkey *key, int store_hash) |
{ |
{ |
int r, success = 0; |
int r, success = 0; |
|
|
error("%s: host_hash failed", __func__); |
error("%s: host_hash failed", __func__); |
return 0; |
return 0; |
} |
} |
} |
fprintf(f, "%s ", hashed_host); |
fprintf(f, "%s ", store_hash ? hashed_host : host); |
} else if (ip != NULL) |
|
fprintf(f, "%s,%s ", host, ip); |
|
else |
|
fprintf(f, "%s ", host); |
|
|
if ((r = sshkey_write(key, f)) == 0) |
if ((r = sshkey_write(key, f)) == 0) |
success = 1; |
success = 1; |
|
|
f = fopen(filename, "a"); |
f = fopen(filename, "a"); |
if (!f) |
if (!f) |
return 0; |
return 0; |
success = write_host_entry(f, host, key, store_hash); |
success = write_host_entry(f, host, NULL, key, store_hash); |
fclose(f); |
fclose(f); |
return success; |
return success; |
} |
} |
|
|
FILE *out; |
FILE *out; |
int quiet; |
int quiet; |
const char *host; |
const char *host; |
int *skip_keys; |
int *skip_keys; /* XXX split for host/ip? might want to ensure both */ |
struct sshkey * const *keys; |
struct sshkey * const *keys; |
size_t nkeys; |
size_t nkeys; |
|
int modified; |
}; |
}; |
|
|
static int |
static int |
host_delete(struct hostkey_foreach_line *l, void *_ctx) |
host_delete(struct hostkey_foreach_line *l, void *_ctx) |
{ |
{ |
struct host_delete_ctx *ctx = (struct host_delete_ctx *)_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; |
size_t i; |
|
|
if (l->status == HKF_STATUS_HOST_MATCHED) { |
if (l->status == HKF_STATUS_MATCHED) { |
if (l->marker != MRK_NONE) { |
if (l->marker != MRK_NONE) { |
/* Don't remove CA and revocation lines */ |
/* Don't remove CA and revocation lines */ |
fprintf(ctx->out, "%s\n", l->line); |
fprintf(ctx->out, "%s\n", l->line); |
|
|
* Hostname matches and has no CA/revoke marker, delete it |
* Hostname matches and has no CA/revoke marker, delete it |
* by *not* writing the line to ctx->out. |
* 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 ? ": " : "", |
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; |
return 0; |
} |
} |
/* Retain non-matching hosts and invalid lines when deleting */ |
/* Retain non-matching hosts and invalid lines when deleting */ |
|
|
} |
} |
|
|
int |
int |
hostfile_replace_entries(const char *filename, const char *host, |
hostfile_replace_entries(const char *filename, const char *host, const char *ip, |
struct sshkey **keys, size_t nkeys, int store_hash, int quiet) |
struct sshkey **keys, size_t nkeys, int store_hash, int quiet, int hash_alg) |
{ |
{ |
int r, fd, oerrno = 0; |
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; |
struct host_delete_ctx ctx; |
char *temp = NULL, *back = NULL; |
char *fp, *temp = NULL, *back = NULL; |
mode_t omask; |
mode_t omask; |
size_t i; |
size_t i; |
|
|
|
|
return SSH_ERR_ALLOC_FAIL; |
return SSH_ERR_ALLOC_FAIL; |
ctx.keys = keys; |
ctx.keys = keys; |
ctx.nkeys = nkeys; |
ctx.nkeys = nkeys; |
|
ctx.modified = 0; |
|
|
/* |
/* |
* Prepare temporary file for in-place deletion. |
* Prepare temporary file for in-place deletion. |
|
|
} |
} |
|
|
/* Remove all entries for the specified host from the file */ |
/* 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) { |
HKF_WANT_PARSE_KEY)) != 0) { |
error("%s: hostkeys_foreach failed: %s", __func__, ssh_err(r)); |
error("%s: hostkeys_foreach failed: %s", __func__, ssh_err(r)); |
goto fail; |
goto fail; |
|
|
for (i = 0; i < nkeys; i++) { |
for (i = 0; i < nkeys; i++) { |
if (ctx.skip_keys[i]) |
if (ctx.skip_keys[i]) |
continue; |
continue; |
do_log2(loglevel, "%s%sadd %s key to %s", |
if ((fp = sshkey_fingerprint(keys[i], hash_alg, |
quiet ? __func__ : "", quiet ? ": " : NULL, |
SSH_FP_DEFAULT)) == NULL) { |
sshkey_type(keys[i]), filename); |
r = SSH_ERR_ALLOC_FAIL; |
if (!write_host_entry(ctx.out, host, keys[i], store_hash)) { |
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; |
r = SSH_ERR_INTERNAL_ERROR; |
goto fail; |
goto fail; |
} |
} |
|
ctx.modified = 1; |
} |
} |
fclose(ctx.out); |
fclose(ctx.out); |
ctx.out = NULL; |
ctx.out = NULL; |
|
|
/* Backup the original file and replace it with the temporary */ |
if (ctx.modified) { |
if (unlink(back) == -1 && errno != ENOENT) { |
/* Backup the original file and replace it with the temporary */ |
oerrno = errno; |
if (unlink(back) == -1 && errno != ENOENT) { |
error("%s: unlink %.100s: %s", __func__, back, strerror(errno)); |
oerrno = errno; |
r = SSH_ERR_SYSTEM_ERROR; |
error("%s: unlink %.100s: %s", __func__, |
goto fail; |
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)); |
} |
} |
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; |
|
} |
|
/* success */ |
/* success */ |
r = 0; |
r = 0; |
fail: |
fail: |
|
|
|
|
int |
int |
hostkeys_foreach(const char *path, hostkeys_foreach_fn *callback, void *ctx, |
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; |
FILE *f; |
char line[8192], oline[8192]; |
char line[8192], oline[8192], ktype[128]; |
u_long linenum = 0; |
u_long linenum = 0; |
char *cp, *cp2; |
char *cp, *cp2; |
u_int kbits; |
u_int kbits; |
|
int hashed; |
int s, r = 0; |
int s, r = 0; |
struct hostkey_foreach_line lineinfo; |
struct hostkey_foreach_line lineinfo; |
|
size_t l; |
|
|
memset(&lineinfo, 0, sizeof(lineinfo)); |
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; |
return SSH_ERR_INVALID_ARGUMENT; |
if ((f = fopen(path, "r")) == NULL) |
if ((f = fopen(path, "r")) == NULL) |
return SSH_ERR_SYSTEM_ERROR; |
return SSH_ERR_SYSTEM_ERROR; |
|
|
lineinfo.path = path; |
lineinfo.path = path; |
lineinfo.linenum = linenum; |
lineinfo.linenum = linenum; |
lineinfo.line = oline; |
lineinfo.line = oline; |
|
lineinfo.marker = MRK_NONE; |
lineinfo.status = HKF_STATUS_OK; |
lineinfo.status = HKF_STATUS_OK; |
|
lineinfo.keytype = KEY_UNSPEC; |
|
|
/* Skip any leading whitespace, comments and empty lines. */ |
/* Skip any leading whitespace, comments and empty lines. */ |
for (cp = line; *cp == ' ' || *cp == '\t'; cp++) |
for (cp = line; *cp == ' ' || *cp == '\t'; cp++) |
; |
; |
if (!*cp || *cp == '#' || *cp == '\n') { |
if (!*cp || *cp == '#' || *cp == '\n') { |
if ((options & HKF_WANT_MATCH_HOST) == 0) { |
if ((options & HKF_WANT_MATCH) == 0) { |
lineinfo.status = HKF_STATUS_COMMENT; |
lineinfo.status = HKF_STATUS_COMMENT; |
if ((r = callback(&lineinfo, ctx)) != 0) |
if ((r = callback(&lineinfo, ctx)) != 0) |
break; |
break; |
|
|
if ((lineinfo.marker = check_markers(&cp)) == MRK_ERROR) { |
if ((lineinfo.marker = check_markers(&cp)) == MRK_ERROR) { |
verbose("%s: invalid marker at %s:%lu", |
verbose("%s: invalid marker at %s:%lu", |
__func__, path, linenum); |
__func__, path, linenum); |
if ((options & HKF_WANT_MATCH_HOST) == 0) |
if ((options & HKF_WANT_MATCH) == 0) |
goto bad; |
goto bad; |
continue; |
continue; |
} |
} |
|
|
|
|
/* Check if the host name matches. */ |
/* Check if the host name matches. */ |
if (host != NULL) { |
if (host != NULL) { |
s = match_maybe_hashed(host, lineinfo.hosts, |
if ((s = match_maybe_hashed(host, lineinfo.hosts, |
&lineinfo.was_hashed); |
&hashed)) == -1) { |
if (s == 1) |
|
lineinfo.status = HKF_STATUS_HOST_MATCHED; |
|
else if ((options & HKF_WANT_MATCH_HOST) != 0) |
|
continue; |
|
else if (s == -1) { |
|
debug2("%s: %s:%ld: bad host hash \"%.32s\"", |
debug2("%s: %s:%ld: bad host hash \"%.32s\"", |
__func__, path, linenum, lineinfo.hosts); |
__func__, path, linenum, lineinfo.hosts); |
goto bad; |
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 */ |
/* Got a match. Skip host name and any following whitespace */ |
for (; *cp2 == ' ' || *cp2 == '\t'; cp2++) |
for (; *cp2 == ' ' || *cp2 == '\t'; cp2++) |
; |
; |
if (*cp2 == '\0' || *cp2 == '#') { |
if (*cp2 == '\0' || *cp2 == '#') { |
debug2("%s:%ld: truncated before key", path, linenum); |
debug2("%s:%ld: truncated before key type", |
|
path, linenum); |
goto bad; |
goto bad; |
} |
} |
lineinfo.rawkey = cp = cp2; |
lineinfo.rawkey = cp = cp2; |
|
|
*/ |
*/ |
if ((lineinfo.key = sshkey_new(KEY_UNSPEC)) == NULL) { |
if ((lineinfo.key = sshkey_new(KEY_UNSPEC)) == NULL) { |
error("%s: sshkey_new failed", __func__); |
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)) { |
if (!hostfile_read_key(&cp, &kbits, lineinfo.key)) { |
#ifdef WITH_SSH1 |
#ifdef WITH_SSH1 |
|
|
lineinfo.key = sshkey_new(KEY_RSA1); |
lineinfo.key = sshkey_new(KEY_RSA1); |
if (lineinfo.key == NULL) { |
if (lineinfo.key == NULL) { |
error("%s: sshkey_new fail", __func__); |
error("%s: sshkey_new fail", __func__); |
return SSH_ERR_ALLOC_FAIL; |
r = SSH_ERR_ALLOC_FAIL; |
|
break; |
} |
} |
if (!hostfile_read_key(&cp, &kbits, |
if (!hostfile_read_key(&cp, &kbits, |
lineinfo.key)) |
lineinfo.key)) |
|
|
goto bad; |
goto bad; |
#endif |
#endif |
} |
} |
if (!hostfile_check_key(kbits, lineinfo.key, host, |
lineinfo.keytype = lineinfo.key->type; |
path, linenum)) { |
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: |
bad: |
|
sshkey_free(lineinfo.key); |
|
lineinfo.key = NULL; |
lineinfo.status = HKF_STATUS_INVALID; |
lineinfo.status = HKF_STATUS_INVALID; |
if ((r = callback(&lineinfo, ctx)) != 0) |
if ((r = callback(&lineinfo, ctx)) != 0) |
break; |
break; |