diff options
-rw-r--r-- | hostfile.c | 147 | ||||
-rw-r--r-- | hostfile.h | 43 |
2 files changed, 187 insertions, 3 deletions
diff --git a/hostfile.c b/hostfile.c index 40dbbd478..5f0366310 100644 --- a/hostfile.c +++ b/hostfile.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: hostfile.c,v 1.59 2015/01/15 09:40:00 djm Exp $ */ | 1 | /* $OpenBSD: hostfile.c,v 1.60 2015/01/18 21:40:23 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 |
@@ -42,6 +42,7 @@ | |||
42 | 42 | ||
43 | #include <netinet/in.h> | 43 | #include <netinet/in.h> |
44 | 44 | ||
45 | #include <errno.h> | ||
45 | #include <resolv.h> | 46 | #include <resolv.h> |
46 | #include <stdarg.h> | 47 | #include <stdarg.h> |
47 | #include <stdio.h> | 48 | #include <stdio.h> |
@@ -64,6 +65,8 @@ struct hostkeys { | |||
64 | u_int num_entries; | 65 | u_int num_entries; |
65 | }; | 66 | }; |
66 | 67 | ||
68 | /* XXX hmac is too easy to dictionary attack; use bcrypt? */ | ||
69 | |||
67 | static int | 70 | static int |
68 | extract_salt(const char *s, u_int l, u_char *salt, size_t salt_len) | 71 | extract_salt(const char *s, u_int l, u_char *salt, size_t salt_len) |
69 | { | 72 | { |
@@ -496,7 +499,147 @@ add_host_to_hostfile(const char *filename, const char *host, | |||
496 | __func__, filename, ssh_err(r)); | 499 | __func__, filename, ssh_err(r)); |
497 | } else | 500 | } else |
498 | success = 1; | 501 | success = 1; |
499 | fputs("\n", f); | 502 | fputc('\n', f); |
500 | fclose(f); | 503 | fclose(f); |
501 | return success; | 504 | return success; |
502 | } | 505 | } |
506 | |||
507 | static int | ||
508 | match_maybe_hashed(const char *host, const char *names, int *was_hashed) | ||
509 | { | ||
510 | int hashed = *names == HASH_DELIM; | ||
511 | const char *hashed_host; | ||
512 | size_t nlen = strlen(names); | ||
513 | |||
514 | if (was_hashed != NULL) | ||
515 | *was_hashed = hashed; | ||
516 | if (hashed) { | ||
517 | if ((hashed_host = host_hash(host, names, nlen)) == NULL) | ||
518 | return -1; | ||
519 | return nlen == strlen(hashed_host) && | ||
520 | strncmp(hashed_host, names, nlen) == 0; | ||
521 | } | ||
522 | return match_hostname(host, names, nlen) == 1; | ||
523 | } | ||
524 | |||
525 | int | ||
526 | hostkeys_foreach(const char *path, hostkeys_foreach_fn *callback, void *ctx, | ||
527 | const char *host, u_int options) | ||
528 | { | ||
529 | FILE *f; | ||
530 | char line[8192], oline[8192]; | ||
531 | u_long linenum = 0; | ||
532 | char *cp, *cp2; | ||
533 | u_int kbits; | ||
534 | int s, r = 0; | ||
535 | struct hostkey_foreach_line lineinfo; | ||
536 | |||
537 | memset(&lineinfo, 0, sizeof(lineinfo)); | ||
538 | if (host == NULL && (options & HKF_WANT_MATCH_HOST) != 0) | ||
539 | return SSH_ERR_INVALID_ARGUMENT; | ||
540 | if ((f = fopen(path, "r")) == NULL) | ||
541 | return SSH_ERR_SYSTEM_ERROR; | ||
542 | |||
543 | debug3("%s: reading file \"%s\"", __func__, path); | ||
544 | while (read_keyfile_line(f, path, line, sizeof(line), &linenum) == 0) { | ||
545 | line[strcspn(line, "\n")] = '\0'; | ||
546 | strlcpy(oline, line, sizeof(oline)); | ||
547 | |||
548 | sshkey_free(lineinfo.key); | ||
549 | memset(&lineinfo, 0, sizeof(lineinfo)); | ||
550 | lineinfo.path = path; | ||
551 | lineinfo.linenum = linenum; | ||
552 | lineinfo.line = oline; | ||
553 | lineinfo.status = HKF_STATUS_OK; | ||
554 | |||
555 | /* Skip any leading whitespace, comments and empty lines. */ | ||
556 | for (cp = line; *cp == ' ' || *cp == '\t'; cp++) | ||
557 | ; | ||
558 | if (!*cp || *cp == '#' || *cp == '\n') { | ||
559 | if ((options & HKF_WANT_MATCH_HOST) == 0) { | ||
560 | lineinfo.status = HKF_STATUS_COMMENT; | ||
561 | if ((r = callback(&lineinfo, ctx)) != 0) | ||
562 | break; | ||
563 | } | ||
564 | continue; | ||
565 | } | ||
566 | |||
567 | if ((lineinfo.marker = check_markers(&cp)) == MRK_ERROR) { | ||
568 | verbose("%s: invalid marker at %s:%lu", | ||
569 | __func__, path, linenum); | ||
570 | if ((options & HKF_WANT_MATCH_HOST) == 0) | ||
571 | goto bad; | ||
572 | continue; | ||
573 | } | ||
574 | |||
575 | /* Find the end of the host name portion. */ | ||
576 | for (cp2 = cp; *cp2 && *cp2 != ' ' && *cp2 != '\t'; cp2++) | ||
577 | ; | ||
578 | lineinfo.hosts = cp; | ||
579 | *cp2++ = '\0'; | ||
580 | |||
581 | /* Check if the host name matches. */ | ||
582 | if (host != NULL) { | ||
583 | s = match_maybe_hashed(host, lineinfo.hosts, | ||
584 | &lineinfo.was_hashed); | ||
585 | if (s == 1) | ||
586 | lineinfo.status = HKF_STATUS_HOST_MATCHED; | ||
587 | else if ((options & HKF_WANT_MATCH_HOST) != 0) | ||
588 | continue; | ||
589 | else if (s == -1) { | ||
590 | debug2("%s: %s:%ld: bad host hash \"%.32s\"", | ||
591 | __func__, path, linenum, lineinfo.hosts); | ||
592 | goto bad; | ||
593 | } | ||
594 | } | ||
595 | |||
596 | /* Got a match. Skip host name and any following whitespace */ | ||
597 | for (; *cp2 == ' ' || *cp2 == '\t'; cp2++) | ||
598 | ; | ||
599 | if (*cp2 == '\0' || *cp2 == '#') { | ||
600 | debug2("%s:%ld: truncated before key", path, linenum); | ||
601 | goto bad; | ||
602 | } | ||
603 | lineinfo.rawkey = cp = cp2; | ||
604 | |||
605 | if ((options & HKF_WANT_PARSE_KEY) != 0) { | ||
606 | /* | ||
607 | * Extract the key from the line. This will skip | ||
608 | * any leading whitespace. Ignore badly formatted | ||
609 | * lines. | ||
610 | */ | ||
611 | if ((lineinfo.key = sshkey_new(KEY_UNSPEC)) == NULL) { | ||
612 | error("%s: sshkey_new failed", __func__); | ||
613 | return SSH_ERR_ALLOC_FAIL; | ||
614 | } | ||
615 | if (!hostfile_read_key(&cp, &kbits, lineinfo.key)) { | ||
616 | #ifdef WITH_SSH1 | ||
617 | sshkey_free(lineinfo.key); | ||
618 | lineinfo.key = sshkey_new(KEY_RSA1); | ||
619 | if (lineinfo.key == NULL) { | ||
620 | error("%s: sshkey_new fail", __func__); | ||
621 | return SSH_ERR_ALLOC_FAIL; | ||
622 | } | ||
623 | if (!hostfile_read_key(&cp, &kbits, | ||
624 | lineinfo.key)) | ||
625 | goto bad; | ||
626 | #else | ||
627 | goto bad; | ||
628 | #endif | ||
629 | } | ||
630 | if (!hostfile_check_key(kbits, lineinfo.key, host, | ||
631 | path, linenum)) { | ||
632 | bad: | ||
633 | lineinfo.status = HKF_STATUS_INVALID; | ||
634 | if ((r = callback(&lineinfo, ctx)) != 0) | ||
635 | break; | ||
636 | continue; | ||
637 | } | ||
638 | } | ||
639 | if ((r = callback(&lineinfo, ctx)) != 0) | ||
640 | break; | ||
641 | } | ||
642 | sshkey_free(lineinfo.key); | ||
643 | fclose(f); | ||
644 | return r; | ||
645 | } | ||
diff --git a/hostfile.h b/hostfile.h index d90973f42..24c3813aa 100644 --- a/hostfile.h +++ b/hostfile.h | |||
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: hostfile.h,v 1.21 2015/01/15 09:40:00 djm Exp $ */ | 1 | /* $OpenBSD: hostfile.h,v 1.22 2015/01/18 21:40:24 djm Exp $ */ |
2 | 2 | ||
3 | /* | 3 | /* |
4 | * Author: Tatu Ylonen <ylo@cs.hut.fi> | 4 | * Author: Tatu Ylonen <ylo@cs.hut.fi> |
@@ -52,4 +52,45 @@ int add_host_to_hostfile(const char *, const char *, | |||
52 | 52 | ||
53 | char *host_hash(const char *, const char *, u_int); | 53 | char *host_hash(const char *, const char *, u_int); |
54 | 54 | ||
55 | /* | ||
56 | * Iterate through a hostkeys file, optionally parsing keys and matching | ||
57 | * hostnames. Allows access to the raw keyfile lines to allow | ||
58 | * streaming edits to the file to take place. | ||
59 | */ | ||
60 | #define HKF_WANT_MATCH_HOST (1) /* return only matching hosts */ | ||
61 | #define HKF_WANT_PARSE_KEY (1<<1) /* need key parsed */ | ||
62 | |||
63 | #define HKF_STATUS_OK 1 /* Line parsed, didn't match host */ | ||
64 | #define HKF_STATUS_INVALID 2 /* line had parse error */ | ||
65 | #define HKF_STATUS_COMMENT 3 /* valid line contained no key */ | ||
66 | #define HKF_STATUS_HOST_MATCHED 4 /* hostname matched */ | ||
67 | |||
68 | /* | ||
69 | * The callback function receives this as an argument for each matching | ||
70 | * hostkey line. The callback may "steal" the 'key' field by setting it to NULL. | ||
71 | * If a parse error occurred, then "hosts" and subsequent options may be NULL. | ||
72 | */ | ||
73 | struct hostkey_foreach_line { | ||
74 | const char *path; /* Path of file */ | ||
75 | u_long linenum; /* Line number */ | ||
76 | int status; /* One of HKF_STATUS_* */ | ||
77 | char *line; /* Entire key line; mutable by callback */ | ||
78 | int marker; /* CA/revocation markers; indicated by MRK_* value */ | ||
79 | const char *hosts; /* Raw hosts text, may be hashed or list multiple */ | ||
80 | int was_hashed; /* Non-zero if hostname was hashed */ | ||
81 | const char *rawkey; /* Text of key and any comment following it */ | ||
82 | struct sshkey *key; /* Key, if parsed ok and HKF_WANT_MATCH_HOST set */ | ||
83 | const char *comment; /* Any comment following the key */ | ||
84 | }; | ||
85 | |||
86 | /* | ||
87 | * Callback fires for each line (or matching line if a HKF_WANT_* option | ||
88 | * is set). The foreach loop will terminate if the callback returns a non- | ||
89 | * zero exit status. | ||
90 | */ | ||
91 | typedef int hostkeys_foreach_fn(struct hostkey_foreach_line *l, void *ctx); | ||
92 | |||
93 | int hostkeys_foreach(const char *path, hostkeys_foreach_fn *callback, void *ctx, | ||
94 | const char *host, u_int options); | ||
95 | |||
55 | #endif | 96 | #endif |