make ssh-keygen use hostkeys_foreach(). Removes some
authordjm <djm@openbsd.org>
Sun, 18 Jan 2015 21:49:42 +0000 (21:49 +0000)
committerdjm <djm@openbsd.org>
Sun, 18 Jan 2015 21:49:42 +0000 (21:49 +0000)
horrendous code; ok markus@

usr.bin/ssh/ssh-keygen.c

index bcbd553..64519d2 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: ssh-keygen.c,v 1.255 2015/01/18 13:22:28 djm Exp $ */
+/* $OpenBSD: ssh-keygen.c,v 1.256 2015/01/18 21:49:42 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1994 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -1012,50 +1012,110 @@ do_gen_all_hostkeys(struct passwd *pw)
                printf("\n");
 }
 
-static void
-printhost(FILE *f, const char *name, struct sshkey *public,
-    int ca, int revoked, int hash)
+struct known_hosts_ctx {
+       FILE *out;
+       const char *host;
+       int has_unhashed, found_key, inplace, invalid;
+};
+
+static int
+known_hosts_hash(struct hostkey_foreach_line *l, void *_ctx)
 {
-       if (print_fingerprint) {
-               enum sshkey_fp_rep rep;
-               int fptype;
-               char *fp, *ra;
-
-               fptype = print_bubblebabble ?
-                   SSH_DIGEST_SHA1 : fingerprint_hash;
-               rep = print_bubblebabble ? SSH_FP_BUBBLEBABBLE : SSH_FP_DEFAULT;
-               fp = sshkey_fingerprint(public, fptype, rep);
-               ra = sshkey_fingerprint(public, fingerprint_hash,
-                   SSH_FP_RANDOMART);
-               printf("%u %s %s (%s)\n", sshkey_size(public), fp, name,
-                   sshkey_type(public));
-               if (log_level >= SYSLOG_LEVEL_VERBOSE)
-                       printf("%s\n", ra);
-               free(ra);
-               free(fp);
-       } else {
-               int r;
+       struct known_hosts_ctx *ctx = (struct known_hosts_ctx *)_ctx;
+       char *hashed, *cp, *hosts, *ohosts;
+       int has_wild = l->hosts && strcspn(l->hosts, "*?!") != strlen(l->hosts);
 
-               if (hash && (name = host_hash(name, NULL, 0)) == NULL)
+       /* Retain invalid lines when hashing, but mark file as invalid. */
+       if (l->status == HKF_STATUS_INVALID) {
+               ctx->invalid = 1;
+               fprintf(stderr, "%s:%ld: invalid line\n", l->path, l->linenum);
+               fprintf(ctx->out, "%s\n", l->line);
+               return 0;
+       }
+
+       /*
+        * Don't hash hosts already already hashed, with wildcard characters
+        * or a CA/revocation marker.
+        */
+       if (l->was_hashed || has_wild || l->marker != MRK_NONE) {
+               fprintf(ctx->out, "%s\n", l->line);
+               if (has_wild && !find_host) {
+                       fprintf(stderr, "%s:%ld: ignoring host name "
+                           "with wildcard: %.64s\n", l->path,
+                           l->linenum, l->hosts);
+                       ctx->has_unhashed = 1;
+               }
+               return 0;
+       }
+       /*
+        * Split any comma-separated hostnames from the host list,
+        * hash and store separately.
+        */
+       ohosts = hosts = xstrdup(l->hosts);
+       while ((cp = strsep(&hosts, ",")) != NULL && *cp != '\0') {
+               if ((hashed = host_hash(cp, NULL, 0)) == NULL)
                        fatal("hash_host failed");
-               fprintf(f, "%s%s%s ", ca ? CA_MARKER " " : "",
-                   revoked ? REVOKE_MARKER " " : "" , name);
-               if ((r = sshkey_write(public, f)) != 0)
-                       fatal("key_write failed: %s", ssh_err(r));
-               fprintf(f, "\n");
+               fprintf(ctx->out, "%s %s\n", hashed, l->rawkey);
+               ctx->has_unhashed = 1;
        }
+       free(ohosts);
+       return 0;
+}
+
+static int
+known_hosts_find_delete(struct hostkey_foreach_line *l, void *_ctx)
+{
+       struct known_hosts_ctx *ctx = (struct known_hosts_ctx *)_ctx;
+
+       if (l->status == HKF_STATUS_HOST_MATCHED) {
+               if (delete_host) {
+                       if (l->marker != MRK_NONE) {
+                               /* Don't remove CA and revocation lines */
+                               fprintf(ctx->out, "%s\n", l->line);
+                       } else {
+                               /*
+                                * Hostname matches and has no CA/revoke
+                                * marker, delete it by *not* writing the
+                                * line to ctx->out.
+                                */
+                               ctx->found_key = 1;
+                               if (!quiet)
+                                       printf("# Host %s found: line %ld\n",
+                                           ctx->host, l->linenum);
+                       }
+                       return 0;
+               } else if (find_host) {
+                       ctx->found_key = 1;
+                       if (!quiet) {
+                               printf("# Host %s found: line %ld %s\n",
+                                   ctx->host,
+                                   l->linenum, l->marker == MRK_CA ? "CA" :
+                                   (l->marker == MRK_REVOKE ? "REVOKED" : ""));
+                       }
+                       if (hash_hosts)
+                               known_hosts_hash(l, ctx);
+                       else
+                               fprintf(ctx->out, "%s\n", l->line);
+                       return 0;
+               }
+       } else if (delete_host) {
+               /* Retain non-matching hosts when deleting */
+               if (l->status == HKF_STATUS_INVALID) {
+                       ctx->invalid = 1;
+                       fprintf(stderr, "%s:%ld: invalid line\n",
+                           l->path, l->linenum);
+               }
+               fprintf(ctx->out, "%s\n", l->line);
+       }
+       return 0;
 }
 
 static void
 do_known_hosts(struct passwd *pw, const char *name)
 {
-       FILE *in, *out = stdout;
-       struct sshkey *pub;
-       char *cp, *cp2, *kp, *kp2;
-       char line[16*1024], tmp[PATH_MAX], old[PATH_MAX];
-       int c, skip = 0, inplace = 0, num = 0, invalid = 0, has_unhashed = 0;
-       int r, ca, revoked;
-       int found_key = 0;
+       char *cp, tmp[MAXPATHLEN], old[MAXPATHLEN];
+       int r, fd, oerrno;
+       struct known_hosts_ctx ctx;
 
        if (!have_identity) {
                cp = tilde_expand_filename(_PATH_SSH_USER_HOSTFILE, pw->pw_uid);
@@ -1065,10 +1125,11 @@ do_known_hosts(struct passwd *pw, const char *name)
                free(cp);
                have_identity = 1;
        }
-       if ((in = fopen(identity_file, "r")) == NULL)
-               fatal("%s: %s: %s", __progname, identity_file, strerror(errno));
 
-       /* XXX this code is a mess; refactor -djm */
+       memset(&ctx, 0, sizeof(ctx));
+       ctx.out = stdout;
+       ctx.host = name;
+
        /*
         * Find hosts goes to stdout, hash and deletions happen in-place
         * A corner case is ssh-keygen -HF foo, which should go to stdout
@@ -1080,184 +1141,39 @@ do_known_hosts(struct passwd *pw, const char *name)
                    strlcat(old, ".old", sizeof(old)) >= sizeof(old))
                        fatal("known_hosts path too long");
                umask(077);
-               if ((c = mkstemp(tmp)) == -1)
+               if ((fd = mkstemp(tmp)) == -1)
                        fatal("mkstemp: %s", strerror(errno));
-               if ((out = fdopen(c, "w")) == NULL) {
-                       c = errno;
+               if ((ctx.out = fdopen(fd, "w")) == NULL) {
+                       oerrno = errno;
                        unlink(tmp);
-                       fatal("fdopen: %s", strerror(c));
+                       fatal("fdopen: %s", strerror(oerrno));
                }
-               inplace = 1;
+               ctx.inplace = 1;
        }
 
-       while (fgets(line, sizeof(line), in)) {
-               if ((cp = strchr(line, '\n')) == NULL) {
-                       error("line %d too long: %.40s...", num + 1, line);
-                       skip = 1;
-                       invalid = 1;
-                       continue;
-               }
-               num++;
-               if (skip) {
-                       skip = 0;
-                       continue;
-               }
-               *cp = '\0';
-
-               /* Skip leading whitespace, empty and comment lines. */
-               for (cp = line; *cp == ' ' || *cp == '\t'; cp++)
-                       ;
-               if (!*cp || *cp == '\n' || *cp == '#') {
-                       if (inplace)
-                               fprintf(out, "%s\n", cp);
-                       continue;
-               }
-               /* Check whether this is a CA key or revocation marker */
-               if (strncasecmp(cp, CA_MARKER, sizeof(CA_MARKER) - 1) == 0 &&
-                   (cp[sizeof(CA_MARKER) - 1] == ' ' ||
-                   cp[sizeof(CA_MARKER) - 1] == '\t')) {
-                       ca = 1;
-                       cp += sizeof(CA_MARKER);
-               } else
-                       ca = 0;
-               if (strncasecmp(cp, REVOKE_MARKER,
-                   sizeof(REVOKE_MARKER) - 1) == 0 &&
-                   (cp[sizeof(REVOKE_MARKER) - 1] == ' ' ||
-                   cp[sizeof(REVOKE_MARKER) - 1] == '\t')) {
-                       revoked = 1;
-                       cp += sizeof(REVOKE_MARKER);
-               } else
-                       revoked = 0;
-
-               /* Find the end of the host name portion. */
-               for (kp = cp; *kp && *kp != ' ' && *kp != '\t'; kp++)
-                       ;
-
-               if (*kp == '\0' || *(kp + 1) == '\0') {
-                       error("line %d missing key: %.40s...",
-                           num, line);
-                       invalid = 1;
-                       continue;
-               }
-               *kp++ = '\0';
-               kp2 = kp;
+       /* XXX support identity_file == "-" for stdin */
+       if ((r = hostkeys_foreach(identity_file,
+           hash_hosts ? known_hosts_hash : known_hosts_find_delete, &ctx,
+           name, find_host ? HKF_WANT_MATCH_HOST : 0)) != 0)
+               fatal("%s: hostkeys_foreach failed: %s", __func__, ssh_err(r));
 
-               if ((pub = sshkey_new(KEY_RSA1)) == NULL)
-                       fatal("sshkey_new failed");
-               if ((r = sshkey_read(pub, &kp)) != 0) {
-                       kp = kp2;
-                       sshkey_free(pub);
-                       if ((pub = sshkey_new(KEY_UNSPEC)) == NULL)
-                               fatal("sshkey_new failed");
-                       if ((r = sshkey_read(pub, &kp)) != 0) {
-                               error("line %d invalid key: %.40s...",
-                                   num, line);
-                               sshkey_free(pub);
-                               invalid = 1;
-                               continue;
-                       }
-               }
+       if (ctx.inplace)
+               fclose(ctx.out);
 
-               if (*cp == HASH_DELIM) {
-                       if (find_host || delete_host) {
-                               cp2 = host_hash(name, cp, strlen(cp));
-                               if (cp2 == NULL) {
-                                       error("line %d: invalid hashed "
-                                           "name: %.64s...", num, line);
-                                       invalid = 1;
-                                       continue;
-                               }
-                               c = (strcmp(cp2, cp) == 0);
-                               if (find_host && c) {
-                                       if (!quiet)
-                                               printf("# Host %s found: "
-                                                   "line %d type %s%s\n", name,
-                                                   num, sshkey_type(pub),
-                                                   ca ? " (CA key)" :
-                                                   revoked? " (revoked)" : "");
-                                       printhost(out, cp, pub, ca, revoked, 0);
-                                       found_key = 1;
-                               }
-                               if (delete_host) {
-                                       if (!c || ca || revoked) {
-                                               printhost(out, cp, pub,
-                                                   ca, revoked, 0);
-                                       } else {
-                                               printf("# Host %s found: "
-                                                   "line %d type %s\n", name,
-                                                   num, sshkey_type(pub));
-                                       }
-                               }
-                       } else if (hash_hosts)
-                               printhost(out, cp, pub, ca, revoked, 0);
-               } else {
-                       if (find_host || delete_host) {
-                               c = (match_hostname(name, cp,
-                                   strlen(cp)) == 1);
-                               if (find_host && c) {
-                                       if (!quiet)
-                                               printf("# Host %s found: "
-                                                   "line %d type %s%s\n", name,
-                                                   num, sshkey_type(pub),
-                                                   ca ? " (CA key)" : "");
-                                       printhost(out, name, pub, ca, revoked,
-                                           hash_hosts && !(ca || revoked));
-                                       found_key = 1;
-                               }
-                               if (delete_host) {
-                                       if (!c || ca || revoked) {
-                                               printhost(out, cp, pub,
-                                                   ca, revoked, 0);
-                                       } else {
-                                               printf("# Host %s found: "
-                                                   "line %d type %s\n", name,
-                                                   num, sshkey_type(pub));
-                                       }
-                               }
-                       } else if (hash_hosts && (ca || revoked)) {
-                               /* Don't hash CA and revoked keys' hostnames */
-                               printhost(out, cp, pub, ca, revoked, 0);
-                               has_unhashed = 1;
-                       } else if (hash_hosts) {
-                               /* Hash each hostname separately */
-                               for (cp2 = strsep(&cp, ",");
-                                   cp2 != NULL && *cp2 != '\0';
-                                   cp2 = strsep(&cp, ",")) {
-                                       if (strcspn(cp2, "*?!") !=
-                                           strlen(cp2)) {
-                                               fprintf(stderr, "Warning: "
-                                                   "ignoring host name with "
-                                                   "metacharacters: %.64s\n",
-                                                   cp2);
-                                               printhost(out, cp2, pub, ca,
-                                                   revoked, 0);
-                                               has_unhashed = 1;
-                                       } else {
-                                               printhost(out, cp2, pub, ca,
-                                                   revoked, 1);
-                                       }
-                               }
-                       }
-               }
-               sshkey_free(pub);
-       }
-       fclose(in);
-
-       if (invalid) {
+       if (ctx.invalid) {
                fprintf(stderr, "%s is not a valid known_hosts file.\n",
                    identity_file);
-               if (inplace) {
+               if (ctx.inplace) {
                        fprintf(stderr, "Not replacing existing known_hosts "
                            "file because of errors\n");
-                       fclose(out);
                        unlink(tmp);
                }
                exit(1);
-       }
-
-       if (inplace) {
-               fclose(out);
-
+       } else if (delete_host && !ctx.found_key) {
+               fprintf(stderr, "Host %s not found in %s\n",
+                   name, identity_file);
+               unlink(tmp);
+       } else if (ctx.inplace) {
                /* Backup existing file */
                if (unlink(old) == -1 && errno != ENOENT)
                        fatal("unlink %.100s: %s", old, strerror(errno));
@@ -1275,7 +1191,7 @@ do_known_hosts(struct passwd *pw, const char *name)
 
                fprintf(stderr, "%s updated.\n", identity_file);
                fprintf(stderr, "Original contents retained as %s\n", old);
-               if (has_unhashed) {
+               if (ctx.has_unhashed) {
                        fprintf(stderr, "WARNING: %s contains unhashed "
                            "entries\n", old);
                        fprintf(stderr, "Delete this file to ensure privacy "
@@ -1283,7 +1199,7 @@ do_known_hosts(struct passwd *pw, const char *name)
                }
        }
 
-       exit (find_host && !found_key);
+       exit (find_host && !ctx.found_key);
 }
 
 /*