diff options
-rw-r--r-- | clientloop.c | 22 | ||||
-rw-r--r-- | hostfile.c | 219 | ||||
-rw-r--r-- | hostfile.h | 31 | ||||
-rw-r--r-- | ssh-keygen.c | 71 |
4 files changed, 218 insertions, 125 deletions
diff --git a/clientloop.c b/clientloop.c index 7b54b6eb0..c6f8e9dc1 100644 --- a/clientloop.c +++ b/clientloop.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: clientloop.c,v 1.267 2015/01/26 03:04:45 djm Exp $ */ | 1 | /* $OpenBSD: clientloop.c,v 1.268 2015/02/16 22:08:57 djm Exp $ */ |
2 | /* | 2 | /* |
3 | * Author: Tatu Ylonen <ylo@cs.hut.fi> | 3 | * Author: Tatu Ylonen <ylo@cs.hut.fi> |
4 | * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland | 4 | * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland |
@@ -2102,8 +2102,9 @@ client_input_hostkeys(void) | |||
2102 | struct sshbuf *buf = NULL; | 2102 | struct sshbuf *buf = NULL; |
2103 | struct sshkey *key = NULL, **tmp, **keys = NULL; | 2103 | struct sshkey *key = NULL, **tmp, **keys = NULL; |
2104 | int r, success = 1; | 2104 | int r, success = 1; |
2105 | char *fp, *host_str = NULL; | 2105 | char *fp, *host_str = NULL, *ip_str = NULL; |
2106 | static int hostkeys_seen = 0; /* XXX use struct ssh */ | 2106 | static int hostkeys_seen = 0; /* XXX use struct ssh */ |
2107 | extern struct sockaddr_storage hostaddr; /* XXX from ssh.c */ | ||
2107 | 2108 | ||
2108 | /* | 2109 | /* |
2109 | * NB. Return success for all cases other than protocol error. The | 2110 | * NB. Return success for all cases other than protocol error. The |
@@ -2148,16 +2149,24 @@ client_input_hostkeys(void) | |||
2148 | key = NULL; | 2149 | key = NULL; |
2149 | } | 2150 | } |
2150 | 2151 | ||
2151 | debug3("%s: received %u keys from server", __func__, nkeys); | ||
2152 | if (nkeys == 0) { | 2152 | if (nkeys == 0) { |
2153 | error("%s: server sent no hostkeys", __func__); | 2153 | error("%s: server sent no hostkeys", __func__); |
2154 | goto out; | 2154 | goto out; |
2155 | } | 2155 | } |
2156 | 2156 | ||
2157 | get_hostfile_hostname_ipaddr(host, NULL, options.port, &host_str, NULL); | 2157 | get_hostfile_hostname_ipaddr(host, |
2158 | options.check_host_ip ? (struct sockaddr *)&hostaddr : NULL, | ||
2159 | options.port, &host_str, options.check_host_ip ? &ip_str : NULL); | ||
2158 | 2160 | ||
2159 | if ((r = hostfile_replace_entries(options.user_hostfiles[0], host_str, | 2161 | debug3("%s: update known hosts for %s%s%s with %u keys from server", |
2160 | keys, nkeys, options.hash_known_hosts, 1)) != 0) { | 2162 | __func__, host_str, |
2163 | options.check_host_ip ? " " : "", | ||
2164 | options.check_host_ip ? ip_str : "", nkeys); | ||
2165 | |||
2166 | if ((r = hostfile_replace_entries(options.user_hostfiles[0], | ||
2167 | host_str, options.check_host_ip ? ip_str : NULL, | ||
2168 | keys, nkeys, options.hash_known_hosts, 0, | ||
2169 | options.fingerprint_hash)) != 0) { | ||
2161 | error("%s: hostfile_replace_entries failed: %s", | 2170 | error("%s: hostfile_replace_entries failed: %s", |
2162 | __func__, ssh_err(r)); | 2171 | __func__, ssh_err(r)); |
2163 | goto out; | 2172 | goto out; |
@@ -2166,6 +2175,7 @@ client_input_hostkeys(void) | |||
2166 | /* Success */ | 2175 | /* Success */ |
2167 | out: | 2176 | out: |
2168 | free(host_str); | 2177 | free(host_str); |
2178 | free(ip_str); | ||
2169 | sshkey_free(key); | 2179 | sshkey_free(key); |
2170 | for (i = 0; i < nkeys; i++) | 2180 | for (i = 0; i < nkeys; i++) |
2171 | sshkey_free(keys[i]); | 2181 | sshkey_free(keys[i]); |
diff --git a/hostfile.c b/hostfile.c index ea6bc6fc8..b235795e6 100644 --- a/hostfile.c +++ b/hostfile.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: hostfile.c,v 1.63 2015/01/26 13:36:53 djm Exp $ */ | 1 | /* $OpenBSD: hostfile.c,v 1.64 2015/02/16 22:08:57 djm Exp $ */ |
2 | /* | 2 | /* |
3 | * Author: Tatu Ylonen <ylo@cs.hut.fi> | 3 | * Author: Tatu Ylonen <ylo@cs.hut.fi> |
4 | * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland | 4 | * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland |
@@ -184,24 +184,6 @@ hostfile_read_key(char **cpp, u_int *bitsp, struct sshkey *ret) | |||
184 | return 1; | 184 | return 1; |
185 | } | 185 | } |
186 | 186 | ||
187 | static int | ||
188 | hostfile_check_key(int bits, const struct sshkey *key, const char *host, | ||
189 | const char *filename, u_long linenum) | ||
190 | { | ||
191 | #ifdef WITH_SSH1 | ||
192 | if (key == NULL || key->type != KEY_RSA1 || key->rsa == NULL) | ||
193 | return 1; | ||
194 | if (bits != BN_num_bits(key->rsa->n)) { | ||
195 | logit("Warning: %s, line %lu: keysize mismatch for host %s: " | ||
196 | "actual %d vs. announced %d.", | ||
197 | filename, linenum, host, BN_num_bits(key->rsa->n), bits); | ||
198 | logit("Warning: replace %d with %d in %s, line %lu.", | ||
199 | bits, BN_num_bits(key->rsa->n), filename, linenum); | ||
200 | } | ||
201 | #endif | ||
202 | return 1; | ||
203 | } | ||
204 | |||
205 | static HostkeyMarker | 187 | static HostkeyMarker |
206 | check_markers(char **cpp) | 188 | check_markers(char **cpp) |
207 | { | 189 | { |
@@ -295,8 +277,8 @@ load_hostkeys(struct hostkeys *hostkeys, const char *host, const char *path) | |||
295 | ctx.num_loaded = 0; | 277 | ctx.num_loaded = 0; |
296 | ctx.hostkeys = hostkeys; | 278 | ctx.hostkeys = hostkeys; |
297 | 279 | ||
298 | if ((r = hostkeys_foreach(path, record_hostkey, &ctx, host, | 280 | if ((r = hostkeys_foreach(path, record_hostkey, &ctx, host, NULL, |
299 | HKF_WANT_MATCH_HOST|HKF_WANT_PARSE_KEY)) != 0) { | 281 | HKF_WANT_MATCH|HKF_WANT_PARSE_KEY)) != 0) { |
300 | if (r != SSH_ERR_SYSTEM_ERROR && errno != ENOENT) | 282 | if (r != SSH_ERR_SYSTEM_ERROR && errno != ENOENT) |
301 | debug("%s: hostkeys_foreach failed for %s: %s", | 283 | debug("%s: hostkeys_foreach failed for %s: %s", |
302 | __func__, path, ssh_err(r)); | 284 | __func__, path, ssh_err(r)); |
@@ -433,7 +415,7 @@ lookup_key_in_hostkeys_by_type(struct hostkeys *hostkeys, int keytype, | |||
433 | } | 415 | } |
434 | 416 | ||
435 | static int | 417 | static int |
436 | write_host_entry(FILE *f, const char *host, | 418 | write_host_entry(FILE *f, const char *host, const char *ip, |
437 | const struct sshkey *key, int store_hash) | 419 | const struct sshkey *key, int store_hash) |
438 | { | 420 | { |
439 | int r, success = 0; | 421 | int r, success = 0; |
@@ -444,8 +426,11 @@ write_host_entry(FILE *f, const char *host, | |||
444 | error("%s: host_hash failed", __func__); | 426 | error("%s: host_hash failed", __func__); |
445 | return 0; | 427 | return 0; |
446 | } | 428 | } |
447 | } | 429 | fprintf(f, "%s ", hashed_host); |
448 | fprintf(f, "%s ", store_hash ? hashed_host : host); | 430 | } else if (ip != NULL) |
431 | fprintf(f, "%s,%s ", host, ip); | ||
432 | else | ||
433 | fprintf(f, "%s ", host); | ||
449 | 434 | ||
450 | if ((r = sshkey_write(key, f)) == 0) | 435 | if ((r = sshkey_write(key, f)) == 0) |
451 | success = 1; | 436 | success = 1; |
@@ -471,7 +456,7 @@ add_host_to_hostfile(const char *filename, const char *host, | |||
471 | f = fopen(filename, "a"); | 456 | f = fopen(filename, "a"); |
472 | if (!f) | 457 | if (!f) |
473 | return 0; | 458 | return 0; |
474 | success = write_host_entry(f, host, key, store_hash); | 459 | success = write_host_entry(f, host, NULL, key, store_hash); |
475 | fclose(f); | 460 | fclose(f); |
476 | return success; | 461 | return success; |
477 | } | 462 | } |
@@ -480,19 +465,20 @@ struct host_delete_ctx { | |||
480 | FILE *out; | 465 | FILE *out; |
481 | int quiet; | 466 | int quiet; |
482 | const char *host; | 467 | const char *host; |
483 | int *skip_keys; | 468 | int *skip_keys; /* XXX split for host/ip? might want to ensure both */ |
484 | struct sshkey * const *keys; | 469 | struct sshkey * const *keys; |
485 | size_t nkeys; | 470 | size_t nkeys; |
471 | int modified; | ||
486 | }; | 472 | }; |
487 | 473 | ||
488 | static int | 474 | static int |
489 | host_delete(struct hostkey_foreach_line *l, void *_ctx) | 475 | host_delete(struct hostkey_foreach_line *l, void *_ctx) |
490 | { | 476 | { |
491 | struct host_delete_ctx *ctx = (struct host_delete_ctx *)_ctx; | 477 | struct host_delete_ctx *ctx = (struct host_delete_ctx *)_ctx; |
492 | int loglevel = ctx->quiet ? SYSLOG_LEVEL_DEBUG1 : SYSLOG_LEVEL_INFO; | 478 | int loglevel = ctx->quiet ? SYSLOG_LEVEL_DEBUG1 : SYSLOG_LEVEL_VERBOSE; |
493 | size_t i; | 479 | size_t i; |
494 | 480 | ||
495 | if (l->status == HKF_STATUS_HOST_MATCHED) { | 481 | if (l->status == HKF_STATUS_MATCHED) { |
496 | if (l->marker != MRK_NONE) { | 482 | if (l->marker != MRK_NONE) { |
497 | /* Don't remove CA and revocation lines */ | 483 | /* Don't remove CA and revocation lines */ |
498 | fprintf(ctx->out, "%s\n", l->line); | 484 | fprintf(ctx->out, "%s\n", l->line); |
@@ -525,9 +511,10 @@ host_delete(struct hostkey_foreach_line *l, void *_ctx) | |||
525 | * Hostname matches and has no CA/revoke marker, delete it | 511 | * Hostname matches and has no CA/revoke marker, delete it |
526 | * by *not* writing the line to ctx->out. | 512 | * by *not* writing the line to ctx->out. |
527 | */ | 513 | */ |
528 | do_log2(loglevel, "%s%s%s:%ld: Host %s removed", | 514 | do_log2(loglevel, "%s%s%s:%ld: Removed %s key for host %s", |
529 | ctx->quiet ? __func__ : "", ctx->quiet ? ": " : "", | 515 | ctx->quiet ? __func__ : "", ctx->quiet ? ": " : "", |
530 | l->path, l->linenum, ctx->host); | 516 | l->path, l->linenum, sshkey_type(l->key), ctx->host); |
517 | ctx->modified = 1; | ||
531 | return 0; | 518 | return 0; |
532 | } | 519 | } |
533 | /* Retain non-matching hosts and invalid lines when deleting */ | 520 | /* Retain non-matching hosts and invalid lines when deleting */ |
@@ -541,13 +528,13 @@ host_delete(struct hostkey_foreach_line *l, void *_ctx) | |||
541 | } | 528 | } |
542 | 529 | ||
543 | int | 530 | int |
544 | hostfile_replace_entries(const char *filename, const char *host, | 531 | hostfile_replace_entries(const char *filename, const char *host, const char *ip, |
545 | struct sshkey **keys, size_t nkeys, int store_hash, int quiet) | 532 | struct sshkey **keys, size_t nkeys, int store_hash, int quiet, int hash_alg) |
546 | { | 533 | { |
547 | int r, fd, oerrno = 0; | 534 | int r, fd, oerrno = 0; |
548 | int loglevel = quiet ? SYSLOG_LEVEL_DEBUG1 : SYSLOG_LEVEL_INFO; | 535 | int loglevel = quiet ? SYSLOG_LEVEL_DEBUG1 : SYSLOG_LEVEL_VERBOSE; |
549 | struct host_delete_ctx ctx; | 536 | struct host_delete_ctx ctx; |
550 | char *temp = NULL, *back = NULL; | 537 | char *fp, *temp = NULL, *back = NULL; |
551 | mode_t omask; | 538 | mode_t omask; |
552 | size_t i; | 539 | size_t i; |
553 | 540 | ||
@@ -560,6 +547,7 @@ hostfile_replace_entries(const char *filename, const char *host, | |||
560 | return SSH_ERR_ALLOC_FAIL; | 547 | return SSH_ERR_ALLOC_FAIL; |
561 | ctx.keys = keys; | 548 | ctx.keys = keys; |
562 | ctx.nkeys = nkeys; | 549 | ctx.nkeys = nkeys; |
550 | ctx.modified = 0; | ||
563 | 551 | ||
564 | /* | 552 | /* |
565 | * Prepare temporary file for in-place deletion. | 553 | * Prepare temporary file for in-place deletion. |
@@ -585,7 +573,7 @@ hostfile_replace_entries(const char *filename, const char *host, | |||
585 | } | 573 | } |
586 | 574 | ||
587 | /* Remove all entries for the specified host from the file */ | 575 | /* Remove all entries for the specified host from the file */ |
588 | if ((r = hostkeys_foreach(filename, host_delete, &ctx, host, | 576 | if ((r = hostkeys_foreach(filename, host_delete, &ctx, host, ip, |
589 | HKF_WANT_PARSE_KEY)) != 0) { | 577 | HKF_WANT_PARSE_KEY)) != 0) { |
590 | error("%s: hostkeys_foreach failed: %s", __func__, ssh_err(r)); | 578 | error("%s: hostkeys_foreach failed: %s", __func__, ssh_err(r)); |
591 | goto fail; | 579 | goto fail; |
@@ -595,38 +583,54 @@ hostfile_replace_entries(const char *filename, const char *host, | |||
595 | for (i = 0; i < nkeys; i++) { | 583 | for (i = 0; i < nkeys; i++) { |
596 | if (ctx.skip_keys[i]) | 584 | if (ctx.skip_keys[i]) |
597 | continue; | 585 | continue; |
598 | do_log2(loglevel, "%s%sadd %s key to %s", | 586 | if ((fp = sshkey_fingerprint(keys[i], hash_alg, |
599 | quiet ? __func__ : "", quiet ? ": " : NULL, | 587 | SSH_FP_DEFAULT)) == NULL) { |
600 | sshkey_type(keys[i]), filename); | 588 | r = SSH_ERR_ALLOC_FAIL; |
601 | if (!write_host_entry(ctx.out, host, keys[i], store_hash)) { | 589 | goto fail; |
590 | } | ||
591 | do_log2(loglevel, "%s%sAdding new key for %s to %s: %s %s", | ||
592 | quiet ? __func__ : "", quiet ? ": " : "", host, filename, | ||
593 | sshkey_ssh_name(keys[i]), fp); | ||
594 | free(fp); | ||
595 | if (!write_host_entry(ctx.out, host, ip, keys[i], store_hash)) { | ||
602 | r = SSH_ERR_INTERNAL_ERROR; | 596 | r = SSH_ERR_INTERNAL_ERROR; |
603 | goto fail; | 597 | goto fail; |
604 | } | 598 | } |
599 | ctx.modified = 1; | ||
605 | } | 600 | } |
606 | fclose(ctx.out); | 601 | fclose(ctx.out); |
607 | ctx.out = NULL; | 602 | ctx.out = NULL; |
608 | 603 | ||
609 | /* Backup the original file and replace it with the temporary */ | 604 | if (ctx.modified) { |
610 | if (unlink(back) == -1 && errno != ENOENT) { | 605 | /* Backup the original file and replace it with the temporary */ |
611 | oerrno = errno; | 606 | if (unlink(back) == -1 && errno != ENOENT) { |
612 | error("%s: unlink %.100s: %s", __func__, back, strerror(errno)); | 607 | oerrno = errno; |
613 | r = SSH_ERR_SYSTEM_ERROR; | 608 | error("%s: unlink %.100s: %s", __func__, |
614 | goto fail; | 609 | back, strerror(errno)); |
615 | } | 610 | r = SSH_ERR_SYSTEM_ERROR; |
616 | if (link(filename, back) == -1) { | 611 | goto fail; |
617 | oerrno = errno; | 612 | } |
618 | error("%s: link %.100s to %.100s: %s", __func__, filename, back, | 613 | if (link(filename, back) == -1) { |
619 | strerror(errno)); | 614 | oerrno = errno; |
620 | r = SSH_ERR_SYSTEM_ERROR; | 615 | error("%s: link %.100s to %.100s: %s", __func__, |
621 | goto fail; | 616 | filename, back, strerror(errno)); |
622 | } | 617 | r = SSH_ERR_SYSTEM_ERROR; |
623 | if (rename(temp, filename) == -1) { | 618 | goto fail; |
624 | oerrno = errno; | 619 | } |
625 | error("%s: rename \"%s\" to \"%s\": %s", __func__, | 620 | if (rename(temp, filename) == -1) { |
626 | temp, filename, strerror(errno)); | 621 | oerrno = errno; |
627 | r = SSH_ERR_SYSTEM_ERROR; | 622 | error("%s: rename \"%s\" to \"%s\": %s", __func__, |
628 | goto fail; | 623 | temp, filename, strerror(errno)); |
624 | r = SSH_ERR_SYSTEM_ERROR; | ||
625 | goto fail; | ||
626 | } | ||
627 | } else { | ||
628 | /* No changes made; just delete the temporary file */ | ||
629 | if (unlink(temp) != 0) | ||
630 | error("%s: unlink \"%s\": %s", __func__, | ||
631 | temp, strerror(errno)); | ||
629 | } | 632 | } |
633 | |||
630 | /* success */ | 634 | /* success */ |
631 | r = 0; | 635 | r = 0; |
632 | fail: | 636 | fail: |
@@ -663,18 +667,20 @@ match_maybe_hashed(const char *host, const char *names, int *was_hashed) | |||
663 | 667 | ||
664 | int | 668 | int |
665 | hostkeys_foreach(const char *path, hostkeys_foreach_fn *callback, void *ctx, | 669 | hostkeys_foreach(const char *path, hostkeys_foreach_fn *callback, void *ctx, |
666 | const char *host, u_int options) | 670 | const char *host, const char *ip, u_int options) |
667 | { | 671 | { |
668 | FILE *f; | 672 | FILE *f; |
669 | char line[8192], oline[8192]; | 673 | char line[8192], oline[8192], ktype[128]; |
670 | u_long linenum = 0; | 674 | u_long linenum = 0; |
671 | char *cp, *cp2; | 675 | char *cp, *cp2; |
672 | u_int kbits; | 676 | u_int kbits; |
677 | int hashed; | ||
673 | int s, r = 0; | 678 | int s, r = 0; |
674 | struct hostkey_foreach_line lineinfo; | 679 | struct hostkey_foreach_line lineinfo; |
680 | size_t l; | ||
675 | 681 | ||
676 | memset(&lineinfo, 0, sizeof(lineinfo)); | 682 | memset(&lineinfo, 0, sizeof(lineinfo)); |
677 | if (host == NULL && (options & HKF_WANT_MATCH_HOST) != 0) | 683 | if (host == NULL && (options & HKF_WANT_MATCH) != 0) |
678 | return SSH_ERR_INVALID_ARGUMENT; | 684 | return SSH_ERR_INVALID_ARGUMENT; |
679 | if ((f = fopen(path, "r")) == NULL) | 685 | if ((f = fopen(path, "r")) == NULL) |
680 | return SSH_ERR_SYSTEM_ERROR; | 686 | return SSH_ERR_SYSTEM_ERROR; |
@@ -689,13 +695,15 @@ hostkeys_foreach(const char *path, hostkeys_foreach_fn *callback, void *ctx, | |||
689 | lineinfo.path = path; | 695 | lineinfo.path = path; |
690 | lineinfo.linenum = linenum; | 696 | lineinfo.linenum = linenum; |
691 | lineinfo.line = oline; | 697 | lineinfo.line = oline; |
698 | lineinfo.marker = MRK_NONE; | ||
692 | lineinfo.status = HKF_STATUS_OK; | 699 | lineinfo.status = HKF_STATUS_OK; |
700 | lineinfo.keytype = KEY_UNSPEC; | ||
693 | 701 | ||
694 | /* Skip any leading whitespace, comments and empty lines. */ | 702 | /* Skip any leading whitespace, comments and empty lines. */ |
695 | for (cp = line; *cp == ' ' || *cp == '\t'; cp++) | 703 | for (cp = line; *cp == ' ' || *cp == '\t'; cp++) |
696 | ; | 704 | ; |
697 | if (!*cp || *cp == '#' || *cp == '\n') { | 705 | if (!*cp || *cp == '#' || *cp == '\n') { |
698 | if ((options & HKF_WANT_MATCH_HOST) == 0) { | 706 | if ((options & HKF_WANT_MATCH) == 0) { |
699 | lineinfo.status = HKF_STATUS_COMMENT; | 707 | lineinfo.status = HKF_STATUS_COMMENT; |
700 | if ((r = callback(&lineinfo, ctx)) != 0) | 708 | if ((r = callback(&lineinfo, ctx)) != 0) |
701 | break; | 709 | break; |
@@ -706,7 +714,7 @@ hostkeys_foreach(const char *path, hostkeys_foreach_fn *callback, void *ctx, | |||
706 | if ((lineinfo.marker = check_markers(&cp)) == MRK_ERROR) { | 714 | if ((lineinfo.marker = check_markers(&cp)) == MRK_ERROR) { |
707 | verbose("%s: invalid marker at %s:%lu", | 715 | verbose("%s: invalid marker at %s:%lu", |
708 | __func__, path, linenum); | 716 | __func__, path, linenum); |
709 | if ((options & HKF_WANT_MATCH_HOST) == 0) | 717 | if ((options & HKF_WANT_MATCH) == 0) |
710 | goto bad; | 718 | goto bad; |
711 | continue; | 719 | continue; |
712 | } | 720 | } |
@@ -719,24 +727,47 @@ hostkeys_foreach(const char *path, hostkeys_foreach_fn *callback, void *ctx, | |||
719 | 727 | ||
720 | /* Check if the host name matches. */ | 728 | /* Check if the host name matches. */ |
721 | if (host != NULL) { | 729 | if (host != NULL) { |
722 | s = match_maybe_hashed(host, lineinfo.hosts, | 730 | if ((s = match_maybe_hashed(host, lineinfo.hosts, |
723 | &lineinfo.was_hashed); | 731 | &hashed)) == -1) { |
724 | if (s == 1) | ||
725 | lineinfo.status = HKF_STATUS_HOST_MATCHED; | ||
726 | else if ((options & HKF_WANT_MATCH_HOST) != 0) | ||
727 | continue; | ||
728 | else if (s == -1) { | ||
729 | debug2("%s: %s:%ld: bad host hash \"%.32s\"", | 732 | debug2("%s: %s:%ld: bad host hash \"%.32s\"", |
730 | __func__, path, linenum, lineinfo.hosts); | 733 | __func__, path, linenum, lineinfo.hosts); |
731 | goto bad; | 734 | goto bad; |
732 | } | 735 | } |
736 | if (s == 1) { | ||
737 | lineinfo.status = HKF_STATUS_MATCHED; | ||
738 | lineinfo.match |= HKF_MATCH_HOST | | ||
739 | (hashed ? HKF_MATCH_HOST_HASHED : 0); | ||
740 | } | ||
741 | /* Try matching IP address if supplied */ | ||
742 | if (ip != NULL) { | ||
743 | if ((s = match_maybe_hashed(ip, lineinfo.hosts, | ||
744 | &hashed)) == -1) { | ||
745 | debug2("%s: %s:%ld: bad ip hash " | ||
746 | "\"%.32s\"", __func__, path, | ||
747 | linenum, lineinfo.hosts); | ||
748 | goto bad; | ||
749 | } | ||
750 | if (s == 1) { | ||
751 | lineinfo.status = HKF_STATUS_MATCHED; | ||
752 | lineinfo.match |= HKF_MATCH_IP | | ||
753 | (hashed ? HKF_MATCH_IP_HASHED : 0); | ||
754 | } | ||
755 | } | ||
756 | /* | ||
757 | * Skip this line if host matching requested and | ||
758 | * neither host nor address matched. | ||
759 | */ | ||
760 | if ((options & HKF_WANT_MATCH) != 0 && | ||
761 | lineinfo.status != HKF_STATUS_MATCHED) | ||
762 | continue; | ||
733 | } | 763 | } |
734 | 764 | ||
735 | /* Got a match. Skip host name and any following whitespace */ | 765 | /* Got a match. Skip host name and any following whitespace */ |
736 | for (; *cp2 == ' ' || *cp2 == '\t'; cp2++) | 766 | for (; *cp2 == ' ' || *cp2 == '\t'; cp2++) |
737 | ; | 767 | ; |
738 | if (*cp2 == '\0' || *cp2 == '#') { | 768 | if (*cp2 == '\0' || *cp2 == '#') { |
739 | debug2("%s:%ld: truncated before key", path, linenum); | 769 | debug2("%s:%ld: truncated before key type", |
770 | path, linenum); | ||
740 | goto bad; | 771 | goto bad; |
741 | } | 772 | } |
742 | lineinfo.rawkey = cp = cp2; | 773 | lineinfo.rawkey = cp = cp2; |
@@ -749,7 +780,8 @@ hostkeys_foreach(const char *path, hostkeys_foreach_fn *callback, void *ctx, | |||
749 | */ | 780 | */ |
750 | if ((lineinfo.key = sshkey_new(KEY_UNSPEC)) == NULL) { | 781 | if ((lineinfo.key = sshkey_new(KEY_UNSPEC)) == NULL) { |
751 | error("%s: sshkey_new failed", __func__); | 782 | error("%s: sshkey_new failed", __func__); |
752 | return SSH_ERR_ALLOC_FAIL; | 783 | r = SSH_ERR_ALLOC_FAIL; |
784 | break; | ||
753 | } | 785 | } |
754 | if (!hostfile_read_key(&cp, &kbits, lineinfo.key)) { | 786 | if (!hostfile_read_key(&cp, &kbits, lineinfo.key)) { |
755 | #ifdef WITH_SSH1 | 787 | #ifdef WITH_SSH1 |
@@ -757,7 +789,8 @@ hostkeys_foreach(const char *path, hostkeys_foreach_fn *callback, void *ctx, | |||
757 | lineinfo.key = sshkey_new(KEY_RSA1); | 789 | lineinfo.key = sshkey_new(KEY_RSA1); |
758 | if (lineinfo.key == NULL) { | 790 | if (lineinfo.key == NULL) { |
759 | error("%s: sshkey_new fail", __func__); | 791 | error("%s: sshkey_new fail", __func__); |
760 | return SSH_ERR_ALLOC_FAIL; | 792 | r = SSH_ERR_ALLOC_FAIL; |
793 | break; | ||
761 | } | 794 | } |
762 | if (!hostfile_read_key(&cp, &kbits, | 795 | if (!hostfile_read_key(&cp, &kbits, |
763 | lineinfo.key)) | 796 | lineinfo.key)) |
@@ -766,9 +799,43 @@ hostkeys_foreach(const char *path, hostkeys_foreach_fn *callback, void *ctx, | |||
766 | goto bad; | 799 | goto bad; |
767 | #endif | 800 | #endif |
768 | } | 801 | } |
769 | if (!hostfile_check_key(kbits, lineinfo.key, host, | 802 | lineinfo.keytype = lineinfo.key->type; |
770 | path, linenum)) { | 803 | lineinfo.comment = cp; |
804 | } else { | ||
805 | /* Extract and parse key type */ | ||
806 | l = strcspn(lineinfo.rawkey, " \t"); | ||
807 | if (l <= 1 || l >= sizeof(ktype) || | ||
808 | lineinfo.rawkey[l] == '\0') | ||
809 | goto bad; | ||
810 | memcpy(ktype, lineinfo.rawkey, l); | ||
811 | ktype[l] = '\0'; | ||
812 | lineinfo.keytype = sshkey_type_from_name(ktype); | ||
813 | #ifdef WITH_SSH1 | ||
814 | /* | ||
815 | * Assume RSA1 if the first component is a short | ||
816 | * decimal number. | ||
817 | */ | ||
818 | if (lineinfo.keytype == KEY_UNSPEC && l < 8 && | ||
819 | strspn(ktype, "0123456789") == l) | ||
820 | lineinfo.keytype = KEY_RSA1; | ||
821 | #endif | ||
822 | /* | ||
823 | * Check that something other than whitespace follows | ||
824 | * the key type. This won't catch all corruption, but | ||
825 | * it does catch trivial truncation. | ||
826 | */ | ||
827 | cp2 += l; /* Skip past key type */ | ||
828 | for (; *cp2 == ' ' || *cp2 == '\t'; cp2++) | ||
829 | ; | ||
830 | if (*cp2 == '\0' || *cp2 == '#') { | ||
831 | debug2("%s:%ld: truncated after key type", | ||
832 | path, linenum); | ||
833 | lineinfo.keytype = KEY_UNSPEC; | ||
834 | } | ||
835 | if (lineinfo.keytype == KEY_UNSPEC) { | ||
771 | bad: | 836 | bad: |
837 | sshkey_free(lineinfo.key); | ||
838 | lineinfo.key = NULL; | ||
772 | lineinfo.status = HKF_STATUS_INVALID; | 839 | lineinfo.status = HKF_STATUS_INVALID; |
773 | if ((r = callback(&lineinfo, ctx)) != 0) | 840 | if ((r = callback(&lineinfo, ctx)) != 0) |
774 | break; | 841 | break; |
diff --git a/hostfile.h b/hostfile.h index 9080b5edb..bd2104373 100644 --- a/hostfile.h +++ b/hostfile.h | |||
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: hostfile.h,v 1.23 2015/01/26 03:04:45 djm Exp $ */ | 1 | /* $OpenBSD: hostfile.h,v 1.24 2015/02/16 22:08:57 djm Exp $ */ |
2 | 2 | ||
3 | /* | 3 | /* |
4 | * Author: Tatu Ylonen <ylo@cs.hut.fi> | 4 | * Author: Tatu Ylonen <ylo@cs.hut.fi> |
@@ -44,8 +44,9 @@ int hostfile_read_key(char **, u_int *, struct sshkey *); | |||
44 | int add_host_to_hostfile(const char *, const char *, | 44 | int add_host_to_hostfile(const char *, const char *, |
45 | const struct sshkey *, int); | 45 | const struct sshkey *, int); |
46 | 46 | ||
47 | int hostfile_replace_entries(const char *filename, const char *host, | 47 | int hostfile_replace_entries(const char *filename, |
48 | struct sshkey **keys, size_t nkeys, int store_hash, int quiet); | 48 | const char *host, const char *ip, struct sshkey **keys, size_t nkeys, |
49 | int store_hash, int quiet, int hash_alg); | ||
49 | 50 | ||
50 | #define HASH_MAGIC "|1|" | 51 | #define HASH_MAGIC "|1|" |
51 | #define HASH_DELIM '|' | 52 | #define HASH_DELIM '|' |
@@ -60,13 +61,19 @@ char *host_hash(const char *, const char *, u_int); | |||
60 | * hostnames. Allows access to the raw keyfile lines to allow | 61 | * hostnames. Allows access to the raw keyfile lines to allow |
61 | * streaming edits to the file to take place. | 62 | * streaming edits to the file to take place. |
62 | */ | 63 | */ |
63 | #define HKF_WANT_MATCH_HOST (1) /* return only matching hosts */ | 64 | #define HKF_WANT_MATCH (1) /* return only matching hosts/addrs */ |
64 | #define HKF_WANT_PARSE_KEY (1<<1) /* need key parsed */ | 65 | #define HKF_WANT_PARSE_KEY (1<<1) /* need key parsed */ |
65 | 66 | ||
66 | #define HKF_STATUS_OK 1 /* Line parsed, didn't match host */ | 67 | #define HKF_STATUS_OK 0 /* Line parsed, didn't match host */ |
67 | #define HKF_STATUS_INVALID 2 /* line had parse error */ | 68 | #define HKF_STATUS_INVALID 1 /* line had parse error */ |
68 | #define HKF_STATUS_COMMENT 3 /* valid line contained no key */ | 69 | #define HKF_STATUS_COMMENT 2 /* valid line contained no key */ |
69 | #define HKF_STATUS_HOST_MATCHED 4 /* hostname matched */ | 70 | #define HKF_STATUS_MATCHED 3 /* hostname or IP matched */ |
71 | |||
72 | #define HKF_MATCH_HOST (1) /* hostname matched */ | ||
73 | #define HKF_MATCH_IP (1<<1) /* address matched */ | ||
74 | #define HKF_MATCH_HOST_HASHED (1<<2) /* hostname was hashed */ | ||
75 | #define HKF_MATCH_IP_HASHED (1<<3) /* address was hashed */ | ||
76 | /* XXX HKF_MATCH_KEY_TYPE? */ | ||
70 | 77 | ||
71 | /* | 78 | /* |
72 | * The callback function receives this as an argument for each matching | 79 | * The callback function receives this as an argument for each matching |
@@ -76,12 +83,13 @@ char *host_hash(const char *, const char *, u_int); | |||
76 | struct hostkey_foreach_line { | 83 | struct hostkey_foreach_line { |
77 | const char *path; /* Path of file */ | 84 | const char *path; /* Path of file */ |
78 | u_long linenum; /* Line number */ | 85 | u_long linenum; /* Line number */ |
79 | int status; /* One of HKF_STATUS_* */ | 86 | u_int status; /* One of HKF_STATUS_* */ |
87 | u_int match; /* Zero or more of HKF_MATCH_* OR'd together */ | ||
80 | char *line; /* Entire key line; mutable by callback */ | 88 | char *line; /* Entire key line; mutable by callback */ |
81 | int marker; /* CA/revocation markers; indicated by MRK_* value */ | 89 | int marker; /* CA/revocation markers; indicated by MRK_* value */ |
82 | const char *hosts; /* Raw hosts text, may be hashed or list multiple */ | 90 | const char *hosts; /* Raw hosts text, may be hashed or list multiple */ |
83 | int was_hashed; /* Non-zero if hostname was hashed */ | ||
84 | const char *rawkey; /* Text of key and any comment following it */ | 91 | const char *rawkey; /* Text of key and any comment following it */ |
92 | int keytype; /* Type of key; KEY_UNSPEC for invalid/comment lines */ | ||
85 | struct sshkey *key; /* Key, if parsed ok and HKF_WANT_MATCH_HOST set */ | 93 | struct sshkey *key; /* Key, if parsed ok and HKF_WANT_MATCH_HOST set */ |
86 | const char *comment; /* Any comment following the key */ | 94 | const char *comment; /* Any comment following the key */ |
87 | }; | 95 | }; |
@@ -93,7 +101,8 @@ struct hostkey_foreach_line { | |||
93 | */ | 101 | */ |
94 | typedef int hostkeys_foreach_fn(struct hostkey_foreach_line *l, void *ctx); | 102 | typedef int hostkeys_foreach_fn(struct hostkey_foreach_line *l, void *ctx); |
95 | 103 | ||
104 | /* Iterate over a hostkeys file */ | ||
96 | int hostkeys_foreach(const char *path, hostkeys_foreach_fn *callback, void *ctx, | 105 | int hostkeys_foreach(const char *path, hostkeys_foreach_fn *callback, void *ctx, |
97 | const char *host, u_int options); | 106 | const char *host, const char *ip, u_int options); |
98 | 107 | ||
99 | #endif | 108 | #endif |
diff --git a/ssh-keygen.c b/ssh-keygen.c index 2c6a56839..9b2068254 100644 --- a/ssh-keygen.c +++ b/ssh-keygen.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: ssh-keygen.c,v 1.261 2015/01/30 01:10:33 djm Exp $ */ | 1 | /* $OpenBSD: ssh-keygen.c,v 1.262 2015/02/16 22:08:57 djm Exp $ */ |
2 | /* | 2 | /* |
3 | * Author: Tatu Ylonen <ylo@cs.hut.fi> | 3 | * Author: Tatu Ylonen <ylo@cs.hut.fi> |
4 | * Copyright (c) 1994 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland | 4 | * Copyright (c) 1994 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland |
@@ -1052,40 +1052,47 @@ known_hosts_hash(struct hostkey_foreach_line *l, void *_ctx) | |||
1052 | char *hashed, *cp, *hosts, *ohosts; | 1052 | char *hashed, *cp, *hosts, *ohosts; |
1053 | int has_wild = l->hosts && strcspn(l->hosts, "*?!") != strlen(l->hosts); | 1053 | int has_wild = l->hosts && strcspn(l->hosts, "*?!") != strlen(l->hosts); |
1054 | 1054 | ||
1055 | /* Retain invalid lines when hashing, but mark file as invalid. */ | 1055 | switch (l->status) { |
1056 | if (l->status == HKF_STATUS_INVALID) { | 1056 | case HKF_STATUS_OK: |
1057 | case HKF_STATUS_MATCHED: | ||
1058 | /* | ||
1059 | * Don't hash hosts already already hashed, with wildcard | ||
1060 | * characters or a CA/revocation marker. | ||
1061 | */ | ||
1062 | if ((l->match & HKF_MATCH_HOST_HASHED) != 0 || | ||
1063 | has_wild || l->marker != MRK_NONE) { | ||
1064 | fprintf(ctx->out, "%s\n", l->line); | ||
1065 | if (has_wild && !find_host) { | ||
1066 | fprintf(stderr, "%s:%ld: ignoring host name " | ||
1067 | "with wildcard: %.64s\n", l->path, | ||
1068 | l->linenum, l->hosts); | ||
1069 | } | ||
1070 | return 0; | ||
1071 | } | ||
1072 | /* | ||
1073 | * Split any comma-separated hostnames from the host list, | ||
1074 | * hash and store separately. | ||
1075 | */ | ||
1076 | ohosts = hosts = xstrdup(l->hosts); | ||
1077 | while ((cp = strsep(&hosts, ",")) != NULL && *cp != '\0') { | ||
1078 | if ((hashed = host_hash(cp, NULL, 0)) == NULL) | ||
1079 | fatal("hash_host failed"); | ||
1080 | fprintf(ctx->out, "%s %s\n", hashed, l->rawkey); | ||
1081 | ctx->has_unhashed = 1; | ||
1082 | } | ||
1083 | free(ohosts); | ||
1084 | return 0; | ||
1085 | case HKF_STATUS_INVALID: | ||
1086 | /* Retain invalid lines, but mark file as invalid. */ | ||
1057 | ctx->invalid = 1; | 1087 | ctx->invalid = 1; |
1058 | fprintf(stderr, "%s:%ld: invalid line\n", l->path, l->linenum); | 1088 | fprintf(stderr, "%s:%ld: invalid line\n", l->path, l->linenum); |
1089 | /* FALLTHROUGH */ | ||
1090 | default: | ||
1059 | fprintf(ctx->out, "%s\n", l->line); | 1091 | fprintf(ctx->out, "%s\n", l->line); |
1060 | return 0; | 1092 | return 0; |
1061 | } | 1093 | } |
1062 | 1094 | /* NOTREACHED */ | |
1063 | /* | 1095 | return -1; |
1064 | * Don't hash hosts already already hashed, with wildcard characters | ||
1065 | * or a CA/revocation marker. | ||
1066 | */ | ||
1067 | if (l->was_hashed || has_wild || l->marker != MRK_NONE) { | ||
1068 | fprintf(ctx->out, "%s\n", l->line); | ||
1069 | if (has_wild && !find_host) { | ||
1070 | fprintf(stderr, "%s:%ld: ignoring host name " | ||
1071 | "with wildcard: %.64s\n", l->path, | ||
1072 | l->linenum, l->hosts); | ||
1073 | } | ||
1074 | return 0; | ||
1075 | } | ||
1076 | /* | ||
1077 | * Split any comma-separated hostnames from the host list, | ||
1078 | * hash and store separately. | ||
1079 | */ | ||
1080 | ohosts = hosts = xstrdup(l->hosts); | ||
1081 | while ((cp = strsep(&hosts, ",")) != NULL && *cp != '\0') { | ||
1082 | if ((hashed = host_hash(cp, NULL, 0)) == NULL) | ||
1083 | fatal("hash_host failed"); | ||
1084 | fprintf(ctx->out, "%s %s\n", hashed, l->rawkey); | ||
1085 | ctx->has_unhashed = 1; | ||
1086 | } | ||
1087 | free(ohosts); | ||
1088 | return 0; | ||
1089 | } | 1096 | } |
1090 | 1097 | ||
1091 | static int | 1098 | static int |
@@ -1093,7 +1100,7 @@ known_hosts_find_delete(struct hostkey_foreach_line *l, void *_ctx) | |||
1093 | { | 1100 | { |
1094 | struct known_hosts_ctx *ctx = (struct known_hosts_ctx *)_ctx; | 1101 | struct known_hosts_ctx *ctx = (struct known_hosts_ctx *)_ctx; |
1095 | 1102 | ||
1096 | if (l->status == HKF_STATUS_HOST_MATCHED) { | 1103 | if (l->status == HKF_STATUS_MATCHED) { |
1097 | if (delete_host) { | 1104 | if (delete_host) { |
1098 | if (l->marker != MRK_NONE) { | 1105 | if (l->marker != MRK_NONE) { |
1099 | /* Don't remove CA and revocation lines */ | 1106 | /* Don't remove CA and revocation lines */ |
@@ -1180,7 +1187,7 @@ do_known_hosts(struct passwd *pw, const char *name) | |||
1180 | /* XXX support identity_file == "-" for stdin */ | 1187 | /* XXX support identity_file == "-" for stdin */ |
1181 | if ((r = hostkeys_foreach(identity_file, | 1188 | if ((r = hostkeys_foreach(identity_file, |
1182 | hash_hosts ? known_hosts_hash : known_hosts_find_delete, &ctx, | 1189 | hash_hosts ? known_hosts_hash : known_hosts_find_delete, &ctx, |
1183 | name, find_host ? HKF_WANT_MATCH_HOST : 0)) != 0) | 1190 | name, NULL, find_host ? HKF_WANT_MATCH : 0)) != 0) |
1184 | fatal("%s: hostkeys_foreach failed: %s", __func__, ssh_err(r)); | 1191 | fatal("%s: hostkeys_foreach failed: %s", __func__, ssh_err(r)); |
1185 | 1192 | ||
1186 | if (inplace) | 1193 | if (inplace) |