+static void
+print_host(FILE *f, const char *name, Key *public, int hash)
+{
+ if (print_fingerprint) {
+ enum fp_rep rep;
+ enum fp_type fptype;
+ char *fp;
+
+ fptype = print_bubblebabble ? SSH_FP_SHA1 : SSH_FP_MD5;
+ rep = print_bubblebabble ? SSH_FP_BUBBLEBABBLE : SSH_FP_HEX;
+ fp = key_fingerprint(public, fptype, rep);
+ printf("%u %s %s\n", key_size(public), fp, name);
+ xfree(fp);
+ } else {
+ if (hash && (name = host_hash(name, NULL, 0)) == NULL)
+ fatal("hash_host failed");
+ fprintf(f, "%s ", name);
+ if (!key_write(public, f))
+ fatal("key_write failed");
+ fprintf(f, "\n");
+ }
+}
+
+static void
+do_known_hosts(struct passwd *pw, const char *name)
+{
+ FILE *in, *out = stdout;
+ Key *public;
+ char *cp, *cp2, *kp, *kp2;
+ char line[16*1024], tmp[MAXPATHLEN], old[MAXPATHLEN];
+ int c, skip = 0, inplace = 0, num = 0, invalid = 0, has_unhashed = 0;
+
+ if (!have_identity) {
+ cp = tilde_expand_filename(_PATH_SSH_USER_HOSTFILE, pw->pw_uid);
+ if (strlcpy(identity_file, cp, sizeof(identity_file)) >=
+ sizeof(identity_file))
+ fatal("Specified known hosts path too long");
+ xfree(cp);
+ have_identity = 1;
+ }
+ if ((in = fopen(identity_file, "r")) == NULL)
+ fatal("fopen: %s", strerror(errno));
+
+ /*
+ * Find hosts goes to stdout, hash and deletions happen in-place
+ * A corner case is ssh-keygen -HF foo, which should go to stdout
+ */
+ if (!find_host && (hash_hosts || delete_host)) {
+ if (strlcpy(tmp, identity_file, sizeof(tmp)) >= sizeof(tmp) ||
+ strlcat(tmp, ".XXXXXXXXXX", sizeof(tmp)) >= sizeof(tmp) ||
+ strlcpy(old, identity_file, sizeof(old)) >= sizeof(old) ||
+ strlcat(old, ".old", sizeof(old)) >= sizeof(old))
+ fatal("known_hosts path too long");
+ umask(077);
+ if ((c = mkstemp(tmp)) == -1)
+ fatal("mkstemp: %s", strerror(errno));
+ if ((out = fdopen(c, "w")) == NULL) {
+ c = errno;
+ unlink(tmp);
+ fatal("fdopen: %s", strerror(c));
+ }
+ 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;
+ }
+ /* 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;
+
+ public = key_new(KEY_RSA1);
+ if (key_read(public, &kp) != 1) {
+ kp = kp2;
+ key_free(public);
+ public = key_new(KEY_UNSPEC);
+ if (key_read(public, &kp) != 1) {
+ error("line %d invalid key: %.40s...",
+ num, line);
+ key_free(public);
+ invalid = 1;
+ continue;
+ }
+ }
+
+ 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) {
+ printf("# Host %s found: "
+ "line %d type %s\n", name,
+ num, key_type(public));
+ print_host(out, cp, public, 0);
+ }
+ if (delete_host && !c)
+ print_host(out, cp, public, 0);
+ } else if (hash_hosts)
+ print_host(out, cp, public, 0);
+ } else {
+ if (find_host || delete_host) {
+ c = (match_hostname(name, cp,
+ strlen(cp)) == 1);
+ if (find_host && c) {
+ printf("# Host %s found: "
+ "line %d type %s\n", name,
+ num, key_type(public));
+ print_host(out, name, public,
+ hash_hosts);
+ }
+ if (delete_host && !c)
+ print_host(out, cp, public, 0);
+ } else if (hash_hosts) {
+ 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);
+ else
+ print_host(out, cp2, public, 1);
+ }
+ has_unhashed = 1;
+ }
+ }
+ key_free(public);
+ }
+ fclose(in);
+
+ if (invalid) {
+ fprintf(stderr, "%s is not a valid known_hosts file.\n",
+ identity_file);
+ if (inplace) {
+ fprintf(stderr, "Not replacing existing known_hosts "
+ "file because of errors\n");
+ fclose(out);
+ unlink(tmp);
+ }
+ exit(1);
+ }
+
+ if (inplace) {
+ fclose(out);
+
+ /* Backup existing file */
+ if (unlink(old) == -1 && errno != ENOENT)
+ fatal("unlink %.100s: %s", old, strerror(errno));
+ if (link(identity_file, old) == -1)
+ fatal("link %.100s to %.100s: %s", identity_file, old,
+ strerror(errno));
+ /* Move new one into place */
+ if (rename(tmp, identity_file) == -1) {
+ error("rename\"%s\" to \"%s\": %s", tmp, identity_file,
+ strerror(errno));
+ unlink(tmp);
+ unlink(old);
+ exit(1);
+ }
+
+ fprintf(stderr, "%s updated.\n", identity_file);
+ fprintf(stderr, "Original contents retained as %s\n", old);
+ if (has_unhashed) {
+ fprintf(stderr, "WARNING: %s contains unhashed "
+ "entries\n", old);
+ fprintf(stderr, "Delete this file to ensure privacy "
+ "of hostnames\n");
+ }
+ }
+
+ exit(0);
+}
+