/* 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. */ /* * $Id: hostfile.c,v 1.4 1999/02/21 19:52:20 ylo Exp $ * $Log: hostfile.c,v $ * Revision 1.4 1999/02/21 19:52:20 ylo * Intermediate commit of ssh1.2.27 stuff. * Main change is sprintf -> snprintf; however, there are also * many other changes. * * Revision 1.3 1998/07/08 00:43:25 kivinen * Added ip number to mach_hostname. Changed it to use match_host * instead of match_pattern. * * Revision 1.2 1997/03/19 21:13:51 kivinen * Enlarged line buffer in check_host_in_hostfile to 16384. * * Revision 1.1.1.1 1996/02/18 21:38:12 ylo * Imported ssh-1.2.13. * * Revision 1.2 1995/07/13 01:24:36 ylo * Removed "Last modified" header. * Added cvs log. * * $Endlog$ */ #include "includes.h" #include "packet.h" #include "ssh.h" #include "userfile.h" #include "xmalloc.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_mp_int(char **cpp, MP_INT *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 (mpz_set_str(value, *cpp, 10) != 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, MP_INT *e, MP_INT *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_mp_int(&cp, e)) return 0; /* Get public modulus. */ if (!auth_rsa_read_mp_int(&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 *ip, 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_host(host, ip, 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(uid_t uid, const char *filename, const char *host, unsigned int bits, MP_INT *e, MP_INT *n) { UserFile uf; char line[16384]; MP_INT ke, kn; unsigned int kbits, hostlen; char *cp, *cp2; HostStatus end_return; struct stat st; /* Open the file containing the list of known hosts. */ uf = userfile_open(uid, filename, O_RDONLY, 0); if (uf == NULL) { if (userfile_stat(uid, 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; } /* Initialize mp-int variables. */ mpz_init(&ke); mpz_init(&kn); /* 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 (userfile_gets(line, sizeof(line), uf)) { 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, NULL, 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 && mpz_cmp(&ke, e) == 0 && mpz_cmp(&kn, n) == 0) { /* Ok, they match. */ mpz_clear(&ke); mpz_clear(&kn); userfile_close(uf); 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. */ mpz_clear(&ke); mpz_clear(&kn); userfile_close(uf); /* 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. All I/O will be done with the give uid using userfile. */ int add_host_to_hostfile(uid_t uid, const char *filename, const char *host, unsigned int bits, MP_INT *e, MP_INT *n) { UserFile uf; char buf[1000], *cp; /* Open the file for appending. */ uf = userfile_open(uid, filename, O_WRONLY|O_APPEND|O_CREAT, 0600); if (uf == NULL) return 0; /* Print the host name and key to the file. */ snprintf(buf, sizeof(buf), "%.500s %u ", host, bits); userfile_write(uf, buf, strlen(buf)); cp = xmalloc(mpz_sizeinbase(e, 10) + 2); mpz_get_str(cp, 10, e); userfile_write(uf, cp, strlen(cp)); xfree(cp); userfile_write(uf, " ", 1); cp = xmalloc(mpz_sizeinbase(n, 10) + 2); mpz_get_str(cp, 10, n); userfile_write(uf, cp, strlen(cp)); xfree(cp); userfile_write(uf, "\n", 1); /* Close the file. */ userfile_close(uf); return 1; }