/* hostfile.c Author: Tatu Ylonen Copyright (c) 1995 Tatu Ylonen , Espoo, Finland All rights reserved Created: Thu Jun 29 07:10:56 1995 ylo Functions for manipulating the known hosts files. */ #include "includes.h" RCSID("$Id: hostfile.c,v 1.3 1999/10/03 21:50:03 provos Exp $"); #include "packet.h" #include "ssh.h" /* Reads a multiple-precision integer in hex from the buffer, and advances the pointer. The integer must already be initialized. This function is permitted to modify the buffer. This leaves *cpp to point just beyond the last processed (and maybe modified) character. Note that this may modify the buffer containing the number. */ int auth_rsa_read_bignum(char **cpp, BIGNUM *value) { char *cp = *cpp; int len, old; /* Skip any leading whitespace. */ for (; *cp == ' ' || *cp == '\t'; cp++) ; /* Check that it begins with a hex digit. */ if (*cp < '0' || *cp > '9') return 0; /* Save starting position. */ *cpp = cp; /* Move forward until all hex digits skipped. */ for (; *cp >= '0' && *cp <= '9'; cp++) ; /* Compute the length of the hex number. */ len = cp - *cpp; /* Save the old terminating character, and replace it by \0. */ old = *cp; *cp = 0; /* Parse the number. */ if (BN_dec2bn(&value, *cpp) == 0) return 0; /* Restore old terminating character. */ *cp = old; /* Move beyond the number and return success. */ *cpp = cp; return 1; } /* Parses an RSA key (number of bits, e, n) from a string. Moves the pointer over the key. Skips any whitespace at the beginning and at end. */ int auth_rsa_read_key(char **cpp, unsigned int *bitsp, BIGNUM *e, BIGNUM *n) { unsigned int bits; char *cp; /* Skip leading whitespace. */ for (cp = *cpp; *cp == ' ' || *cp == '\t'; cp++) ; /* Get number of bits. */ if (*cp < '0' || *cp > '9') return 0; /* Bad bit count... */ for (bits = 0; *cp >= '0' && *cp <= '9'; cp++) bits = 10 * bits + *cp - '0'; /* Get public exponent. */ if (!auth_rsa_read_bignum(&cp, e)) return 0; /* Get public modulus. */ if (!auth_rsa_read_bignum(&cp, n)) return 0; /* Skip trailing whitespace. */ for (; *cp == ' ' || *cp == '\t'; cp++) ; /* Return results. */ *cpp = cp; *bitsp = bits; return 1; } /* Tries to match the host name (which must be in all lowercase) against the comma-separated sequence of subpatterns (each possibly preceded by ! to indicate negation). Returns true if there is a positive match; zero otherwise. */ int match_hostname(const char *host, const char *pattern, unsigned int len) { char sub[1024]; int negated; int got_positive; unsigned int i, subi; got_positive = 0; for (i = 0; i < len;) { /* Check if the subpattern is negated. */ if (pattern[i] == '!') { negated = 1; i++; } else negated = 0; /* Extract the subpattern up to a comma or end. Convert the subpattern to lowercase. */ for (subi = 0; i < len && subi < sizeof(sub) - 1 && pattern[i] != ','; subi++, i++) sub[subi] = isupper(pattern[i]) ? tolower(pattern[i]) : pattern[i]; /* If subpattern too long, return failure (no match). */ if (subi >= sizeof(sub) - 1) return 0; /* If the subpattern was terminated by a comma, skip the comma. */ if (i < len && pattern[i] == ',') i++; /* Null-terminate the subpattern. */ sub[subi] = '\0'; /* Try to match the subpattern against the host name. */ if (match_pattern(host, sub)) { if (negated) return 0; /* Fail if host matches any negated subpattern. */ else got_positive = 1; } } /* Return success if got a positive match. If there was a negative match, we have already returned zero and never get here. */ return got_positive; } /* Checks whether the given host (which must be in all lowercase) is already in the list of our known hosts. Returns HOST_OK if the host is known and has the specified key, HOST_NEW if the host is not known, and HOST_CHANGED if the host is known but used to have a different host key. */ HostStatus check_host_in_hostfile(const char *filename, const char *host, unsigned int bits, BIGNUM *e, BIGNUM *n, BIGNUM *ke, BIGNUM *kn) { FILE *f; char line[8192]; unsigned int kbits, hostlen; char *cp, *cp2; HostStatus end_return; struct stat st; /* Open the file containing the list of known hosts. */ f = fopen(filename, "r"); if (!f) { if (stat(filename, &st) >= 0) { packet_send_debug("Could not open %.900s for reading.", filename); packet_send_debug("If your home directory is on an NFS volume, it may need to be world-readable."); } return HOST_NEW; } /* Cache the length of the host name. */ hostlen = strlen(host); /* Return value when the loop terminates. This is set to HOST_CHANGED if we have seen a different key for the host and have not found the proper one. */ end_return = HOST_NEW; /* Go trough the file. */ while (fgets(line, sizeof(line), f)) { cp = line; /* Skip any leading whitespace. */ for (; *cp == ' ' || *cp == '\t'; cp++) ; /* Ignore comment lines and empty lines. */ if (!*cp || *cp == '#' || *cp == '\n') continue; /* Find the end of the host name portion. */ for (cp2 = cp; *cp2 && *cp2 != ' ' && *cp2 != '\t'; cp2++) ; /* Check if the host name matches. */ if (!match_hostname(host, cp, (unsigned int)(cp2 - cp))) continue; /* Got a match. Skip host name. */ cp = cp2; /* Extract the key from the line. This will skip any leading whitespace. Ignore badly formatted lines. */ if (!auth_rsa_read_key(&cp, &kbits, ke, kn)) continue; /* Check if the current key is the same as the previous one. */ if (kbits == bits && BN_cmp(ke, e) == 0 && BN_cmp(kn, n) == 0) { /* Ok, they match. */ fclose(f); return HOST_OK; } /* They do not match. We will continue to go through the file; however, we note that we will not return that it is new. */ end_return = HOST_CHANGED; } /* Clear variables and close the file. */ fclose(f); /* Return either HOST_NEW or HOST_CHANGED, depending on whether we saw a different key for the host. */ return end_return; } /* Appends an entry to the host file. Returns false if the entry could not be appended. */ int add_host_to_hostfile(const char *filename, const char *host, unsigned int bits, BIGNUM *e, BIGNUM *n) { FILE *f; char *buf; /* Open the file for appending. */ f = fopen(filename, "a"); if (!f) return 0; /* Print the host name and key to the file. */ fprintf(f, "%s %u ", host, bits); buf = BN_bn2dec(e); assert(buf != NULL); fprintf(f, "%s ", buf); free (buf); buf = BN_bn2dec(n); assert(buf != NULL); fprintf(f, "%s\n", buf); free (buf); /* Close the file. */ fclose(f); return 1; }