[BACK]Return to hostfile.c CVS log [TXT][DIR] Up to [local] / src / usr.bin / ssh

File: [local] / src / usr.bin / ssh / hostfile.c (download)

Revision 1.6, Tue Nov 16 15:44:41 1999 UTC (24 years, 6 months ago) by provos
Branch: MAIN
Changes since 1.5: +2 -2 lines

in known_hosts key lookup the entry for the bits does not need to match, all
the information is contained in n and e.  This solves the problem with buggy
servers announcing the wrong modulus length.  markus and me.

/*

hostfile.c

Author: Tatu Ylonen <ylo@cs.hut.fi>

Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, 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.6 1999/11/16 15:44:41 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,
		       BIGNUM *e, BIGNUM *n, BIGNUM *ke, BIGNUM *kn)
{
  FILE *f;
  char line[8192];
  int linenum = 0;
  unsigned int bits, kbits, hostlen;
  char *cp, *cp2;
  HostStatus end_return;

  /* Open the file containing the list of known hosts. */
  f = fopen(filename, "r");
  if (!f)
    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;

  /* size of modulus 'n' */
  bits = BN_num_bits(n);

  /* Go trough the file. */
  while (fgets(line, sizeof(line), f))
    {
      cp = line;
      linenum++;

      /* 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;

      if (kbits != BN_num_bits(kn)) {
        error("Warning: error in %s, line %d: keysize mismatch for host %s: "
	      "actual size %d vs. announced %d.",
	      filename, linenum, host, BN_num_bits(kn), kbits);
        error("Warning: replace %d with %d in %s, line %d.",
	      kbits, BN_num_bits(kn), filename, linenum);
      }

      /* Check if the current key is the same as the given key. */
      if (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,
		     BIGNUM *e, BIGNUM *n)
{
  FILE *f;
  char *buf;
  unsigned int bits;
 
  /* Open the file for appending. */
  f = fopen(filename, "a");
  if (!f)
    return 0;

  /* size of modulus 'n' */
  bits = BN_num_bits(n);

  /* Print the host name and key to the file. */
  fprintf(f, "%s %u ", host, bits);
  buf = BN_bn2dec(e);
  if (buf == NULL) {
    error("add_host_to_hostfile: BN_bn2dec(e) failed");
    fclose(f);
    return 0;
  }
  fprintf(f, "%s ", buf);
  free (buf);
  buf = BN_bn2dec(n);
  if (buf == NULL) {
    error("add_host_to_hostfile: BN_bn2dec(n) failed");
    fclose(f);
    return 0;
  }
  fprintf(f, "%s\n", buf);
  free (buf);

  /* Close the file. */
  fclose(f);
  return 1;
}