summaryrefslogtreecommitdiff
path: root/usr.bin/ssh/hostfile.c
blob: 9b36e39e8283be71df727a0c8d55d96672230ee3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
/*

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.2 1999/09/28 04:45:36 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)
{
  FILE *f;
  char line[8192];
  unsigned int kbits, hostlen;
  char *cp, *cp2;
  HostStatus end_return;
  struct stat st;
  BIGNUM *ke, *kn;

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

  /* Initialize mp-int variables. */
  ke = BN_new();
  kn = BN_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. */
	  BN_clear_free(ke);
	  BN_clear_free(kn);
	  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. */
  BN_clear_free(ke);
  BN_clear_free(kn);
  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;
}