diff options
Diffstat (limited to 'hostfile.c')
-rw-r--r-- | hostfile.c | 625 |
1 files changed, 490 insertions, 135 deletions
diff --git a/hostfile.c b/hostfile.c index ee2daf45f..b235795e6 100644 --- a/hostfile.c +++ b/hostfile.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: hostfile.c,v 1.57 2014/06/24 01:13:21 djm Exp $ */ | 1 | /* $OpenBSD: hostfile.c,v 1.64 2015/02/16 22:08:57 djm Exp $ */ |
2 | /* | 2 | /* |
3 | * Author: Tatu Ylonen <ylo@cs.hut.fi> | 3 | * Author: Tatu Ylonen <ylo@cs.hut.fi> |
4 | * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland | 4 | * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland |
@@ -39,22 +39,26 @@ | |||
39 | #include "includes.h" | 39 | #include "includes.h" |
40 | 40 | ||
41 | #include <sys/types.h> | 41 | #include <sys/types.h> |
42 | #include <sys/stat.h> | ||
42 | 43 | ||
43 | #include <netinet/in.h> | 44 | #include <netinet/in.h> |
44 | 45 | ||
46 | #include <errno.h> | ||
45 | #include <resolv.h> | 47 | #include <resolv.h> |
46 | #include <stdarg.h> | 48 | #include <stdarg.h> |
47 | #include <stdio.h> | 49 | #include <stdio.h> |
48 | #include <stdlib.h> | 50 | #include <stdlib.h> |
49 | #include <string.h> | 51 | #include <string.h> |
50 | #include <stdarg.h> | 52 | #include <stdarg.h> |
53 | #include <unistd.h> | ||
51 | 54 | ||
52 | #include "xmalloc.h" | 55 | #include "xmalloc.h" |
53 | #include "match.h" | 56 | #include "match.h" |
54 | #include "key.h" | 57 | #include "sshkey.h" |
55 | #include "hostfile.h" | 58 | #include "hostfile.h" |
56 | #include "log.h" | 59 | #include "log.h" |
57 | #include "misc.h" | 60 | #include "misc.h" |
61 | #include "ssherr.h" | ||
58 | #include "digest.h" | 62 | #include "digest.h" |
59 | #include "hmac.h" | 63 | #include "hmac.h" |
60 | 64 | ||
@@ -63,6 +67,8 @@ struct hostkeys { | |||
63 | u_int num_entries; | 67 | u_int num_entries; |
64 | }; | 68 | }; |
65 | 69 | ||
70 | /* XXX hmac is too easy to dictionary attack; use bcrypt? */ | ||
71 | |||
66 | static int | 72 | static int |
67 | extract_salt(const char *s, u_int l, u_char *salt, size_t salt_len) | 73 | extract_salt(const char *s, u_int l, u_char *salt, size_t salt_len) |
68 | { | 74 | { |
@@ -155,15 +161,16 @@ host_hash(const char *host, const char *name_from_hostfile, u_int src_len) | |||
155 | */ | 161 | */ |
156 | 162 | ||
157 | int | 163 | int |
158 | hostfile_read_key(char **cpp, int *bitsp, Key *ret) | 164 | hostfile_read_key(char **cpp, u_int *bitsp, struct sshkey *ret) |
159 | { | 165 | { |
160 | char *cp; | 166 | char *cp; |
167 | int r; | ||
161 | 168 | ||
162 | /* Skip leading whitespace. */ | 169 | /* Skip leading whitespace. */ |
163 | for (cp = *cpp; *cp == ' ' || *cp == '\t'; cp++) | 170 | for (cp = *cpp; *cp == ' ' || *cp == '\t'; cp++) |
164 | ; | 171 | ; |
165 | 172 | ||
166 | if (key_read(ret, &cp) != 1) | 173 | if ((r = sshkey_read(ret, &cp)) != 0) |
167 | return 0; | 174 | return 0; |
168 | 175 | ||
169 | /* Skip trailing whitespace. */ | 176 | /* Skip trailing whitespace. */ |
@@ -172,28 +179,8 @@ hostfile_read_key(char **cpp, int *bitsp, Key *ret) | |||
172 | 179 | ||
173 | /* Return results. */ | 180 | /* Return results. */ |
174 | *cpp = cp; | 181 | *cpp = cp; |
175 | if (bitsp != NULL) { | 182 | if (bitsp != NULL) |
176 | if ((*bitsp = key_size(ret)) <= 0) | 183 | *bitsp = sshkey_size(ret); |
177 | return 0; | ||
178 | } | ||
179 | return 1; | ||
180 | } | ||
181 | |||
182 | static int | ||
183 | hostfile_check_key(int bits, const Key *key, const char *host, | ||
184 | const char *filename, u_long linenum) | ||
185 | { | ||
186 | #ifdef WITH_SSH1 | ||
187 | if (key == NULL || key->type != KEY_RSA1 || key->rsa == NULL) | ||
188 | return 1; | ||
189 | if (bits != BN_num_bits(key->rsa->n)) { | ||
190 | logit("Warning: %s, line %lu: keysize mismatch for host %s: " | ||
191 | "actual %d vs. announced %d.", | ||
192 | filename, linenum, host, BN_num_bits(key->rsa->n), bits); | ||
193 | logit("Warning: replace %d with %d in %s, line %lu.", | ||
194 | bits, BN_num_bits(key->rsa->n), filename, linenum); | ||
195 | } | ||
196 | #endif | ||
197 | return 1; | 184 | return 1; |
198 | } | 185 | } |
199 | 186 | ||
@@ -241,95 +228,65 @@ init_hostkeys(void) | |||
241 | return ret; | 228 | return ret; |
242 | } | 229 | } |
243 | 230 | ||
244 | void | 231 | struct load_callback_ctx { |
245 | load_hostkeys(struct hostkeys *hostkeys, const char *host, const char *path) | 232 | const char *host; |
246 | { | 233 | u_long num_loaded; |
247 | FILE *f; | 234 | struct hostkeys *hostkeys; |
248 | char line[8192]; | 235 | }; |
249 | u_long linenum = 0, num_loaded = 0; | ||
250 | char *cp, *cp2, *hashed_host; | ||
251 | HostkeyMarker marker; | ||
252 | Key *key; | ||
253 | int kbits; | ||
254 | |||
255 | if ((f = fopen(path, "r")) == NULL) | ||
256 | return; | ||
257 | debug3("%s: loading entries for host \"%.100s\" from file \"%s\"", | ||
258 | __func__, host, path); | ||
259 | while (read_keyfile_line(f, path, line, sizeof(line), &linenum) == 0) { | ||
260 | cp = line; | ||
261 | |||
262 | /* Skip any leading whitespace, comments and empty lines. */ | ||
263 | for (; *cp == ' ' || *cp == '\t'; cp++) | ||
264 | ; | ||
265 | if (!*cp || *cp == '#' || *cp == '\n') | ||
266 | continue; | ||
267 | |||
268 | if ((marker = check_markers(&cp)) == MRK_ERROR) { | ||
269 | verbose("%s: invalid marker at %s:%lu", | ||
270 | __func__, path, linenum); | ||
271 | continue; | ||
272 | } | ||
273 | 236 | ||
274 | /* Find the end of the host name portion. */ | 237 | static int |
275 | for (cp2 = cp; *cp2 && *cp2 != ' ' && *cp2 != '\t'; cp2++) | 238 | record_hostkey(struct hostkey_foreach_line *l, void *_ctx) |
276 | ; | 239 | { |
240 | struct load_callback_ctx *ctx = (struct load_callback_ctx *)_ctx; | ||
241 | struct hostkeys *hostkeys = ctx->hostkeys; | ||
242 | struct hostkey_entry *tmp; | ||
277 | 243 | ||
278 | /* Check if the host name matches. */ | 244 | if (l->status == HKF_STATUS_INVALID) { |
279 | if (match_hostname(host, cp, (u_int) (cp2 - cp)) != 1) { | 245 | error("%s:%ld: parse error in hostkeys file", |
280 | if (*cp != HASH_DELIM) | 246 | l->path, l->linenum); |
281 | continue; | 247 | return 0; |
282 | hashed_host = host_hash(host, cp, (u_int) (cp2 - cp)); | 248 | } |
283 | if (hashed_host == NULL) { | ||
284 | debug("Invalid hashed host line %lu of %s", | ||
285 | linenum, path); | ||
286 | continue; | ||
287 | } | ||
288 | if (strncmp(hashed_host, cp, (u_int) (cp2 - cp)) != 0) | ||
289 | continue; | ||
290 | } | ||
291 | 249 | ||
292 | /* Got a match. Skip host name. */ | 250 | debug3("%s: found %skey type %s in file %s:%lu", __func__, |
293 | cp = cp2; | 251 | l->marker == MRK_NONE ? "" : |
252 | (l->marker == MRK_CA ? "ca " : "revoked "), | ||
253 | sshkey_type(l->key), l->path, l->linenum); | ||
254 | if ((tmp = reallocarray(hostkeys->entries, | ||
255 | hostkeys->num_entries + 1, sizeof(*hostkeys->entries))) == NULL) | ||
256 | return SSH_ERR_ALLOC_FAIL; | ||
257 | hostkeys->entries = tmp; | ||
258 | hostkeys->entries[hostkeys->num_entries].host = xstrdup(ctx->host); | ||
259 | hostkeys->entries[hostkeys->num_entries].file = xstrdup(l->path); | ||
260 | hostkeys->entries[hostkeys->num_entries].line = l->linenum; | ||
261 | hostkeys->entries[hostkeys->num_entries].key = l->key; | ||
262 | l->key = NULL; /* steal it */ | ||
263 | hostkeys->entries[hostkeys->num_entries].marker = l->marker; | ||
264 | hostkeys->num_entries++; | ||
265 | ctx->num_loaded++; | ||
294 | 266 | ||
295 | /* | 267 | return 0; |
296 | * Extract the key from the line. This will skip any leading | 268 | } |
297 | * whitespace. Ignore badly formatted lines. | ||
298 | */ | ||
299 | key = key_new(KEY_UNSPEC); | ||
300 | if (!hostfile_read_key(&cp, &kbits, key)) { | ||
301 | key_free(key); | ||
302 | #ifdef WITH_SSH1 | ||
303 | key = key_new(KEY_RSA1); | ||
304 | if (!hostfile_read_key(&cp, &kbits, key)) { | ||
305 | key_free(key); | ||
306 | continue; | ||
307 | } | ||
308 | #else | ||
309 | continue; | ||
310 | #endif | ||
311 | } | ||
312 | if (!hostfile_check_key(kbits, key, host, path, linenum)) | ||
313 | continue; | ||
314 | 269 | ||
315 | debug3("%s: found %skey type %s in file %s:%lu", __func__, | 270 | void |
316 | marker == MRK_NONE ? "" : | 271 | load_hostkeys(struct hostkeys *hostkeys, const char *host, const char *path) |
317 | (marker == MRK_CA ? "ca " : "revoked "), | 272 | { |
318 | key_type(key), path, linenum); | 273 | int r; |
319 | hostkeys->entries = xrealloc(hostkeys->entries, | 274 | struct load_callback_ctx ctx; |
320 | hostkeys->num_entries + 1, sizeof(*hostkeys->entries)); | 275 | |
321 | hostkeys->entries[hostkeys->num_entries].host = xstrdup(host); | 276 | ctx.host = host; |
322 | hostkeys->entries[hostkeys->num_entries].file = xstrdup(path); | 277 | ctx.num_loaded = 0; |
323 | hostkeys->entries[hostkeys->num_entries].line = linenum; | 278 | ctx.hostkeys = hostkeys; |
324 | hostkeys->entries[hostkeys->num_entries].key = key; | 279 | |
325 | hostkeys->entries[hostkeys->num_entries].marker = marker; | 280 | if ((r = hostkeys_foreach(path, record_hostkey, &ctx, host, NULL, |
326 | hostkeys->num_entries++; | 281 | HKF_WANT_MATCH|HKF_WANT_PARSE_KEY)) != 0) { |
327 | num_loaded++; | 282 | if (r != SSH_ERR_SYSTEM_ERROR && errno != ENOENT) |
283 | debug("%s: hostkeys_foreach failed for %s: %s", | ||
284 | __func__, path, ssh_err(r)); | ||
328 | } | 285 | } |
329 | debug3("%s: loaded %lu keys", __func__, num_loaded); | 286 | if (ctx.num_loaded != 0) |
330 | fclose(f); | 287 | debug3("%s: loaded %lu keys from %s", __func__, |
331 | return; | 288 | ctx.num_loaded, host); |
332 | } | 289 | } |
333 | 290 | ||
334 | void | 291 | void |
335 | free_hostkeys(struct hostkeys *hostkeys) | 292 | free_hostkeys(struct hostkeys *hostkeys) |
@@ -339,7 +296,7 @@ free_hostkeys(struct hostkeys *hostkeys) | |||
339 | for (i = 0; i < hostkeys->num_entries; i++) { | 296 | for (i = 0; i < hostkeys->num_entries; i++) { |
340 | free(hostkeys->entries[i].host); | 297 | free(hostkeys->entries[i].host); |
341 | free(hostkeys->entries[i].file); | 298 | free(hostkeys->entries[i].file); |
342 | key_free(hostkeys->entries[i].key); | 299 | sshkey_free(hostkeys->entries[i].key); |
343 | explicit_bzero(hostkeys->entries + i, sizeof(*hostkeys->entries)); | 300 | explicit_bzero(hostkeys->entries + i, sizeof(*hostkeys->entries)); |
344 | } | 301 | } |
345 | free(hostkeys->entries); | 302 | free(hostkeys->entries); |
@@ -348,18 +305,18 @@ free_hostkeys(struct hostkeys *hostkeys) | |||
348 | } | 305 | } |
349 | 306 | ||
350 | static int | 307 | static int |
351 | check_key_not_revoked(struct hostkeys *hostkeys, Key *k) | 308 | check_key_not_revoked(struct hostkeys *hostkeys, struct sshkey *k) |
352 | { | 309 | { |
353 | int is_cert = key_is_cert(k); | 310 | int is_cert = sshkey_is_cert(k); |
354 | u_int i; | 311 | u_int i; |
355 | 312 | ||
356 | for (i = 0; i < hostkeys->num_entries; i++) { | 313 | for (i = 0; i < hostkeys->num_entries; i++) { |
357 | if (hostkeys->entries[i].marker != MRK_REVOKE) | 314 | if (hostkeys->entries[i].marker != MRK_REVOKE) |
358 | continue; | 315 | continue; |
359 | if (key_equal_public(k, hostkeys->entries[i].key)) | 316 | if (sshkey_equal_public(k, hostkeys->entries[i].key)) |
360 | return -1; | 317 | return -1; |
361 | if (is_cert && | 318 | if (is_cert && |
362 | key_equal_public(k->cert->signature_key, | 319 | sshkey_equal_public(k->cert->signature_key, |
363 | hostkeys->entries[i].key)) | 320 | hostkeys->entries[i].key)) |
364 | return -1; | 321 | return -1; |
365 | } | 322 | } |
@@ -383,11 +340,11 @@ check_key_not_revoked(struct hostkeys *hostkeys, Key *k) | |||
383 | */ | 340 | */ |
384 | static HostStatus | 341 | static HostStatus |
385 | check_hostkeys_by_key_or_type(struct hostkeys *hostkeys, | 342 | check_hostkeys_by_key_or_type(struct hostkeys *hostkeys, |
386 | Key *k, int keytype, const struct hostkey_entry **found) | 343 | struct sshkey *k, int keytype, const struct hostkey_entry **found) |
387 | { | 344 | { |
388 | u_int i; | 345 | u_int i; |
389 | HostStatus end_return = HOST_NEW; | 346 | HostStatus end_return = HOST_NEW; |
390 | int want_cert = key_is_cert(k); | 347 | int want_cert = sshkey_is_cert(k); |
391 | HostkeyMarker want_marker = want_cert ? MRK_CA : MRK_NONE; | 348 | HostkeyMarker want_marker = want_cert ? MRK_CA : MRK_NONE; |
392 | int proto = (k ? k->type : keytype) == KEY_RSA1 ? 1 : 2; | 349 | int proto = (k ? k->type : keytype) == KEY_RSA1 ? 1 : 2; |
393 | 350 | ||
@@ -411,7 +368,7 @@ check_hostkeys_by_key_or_type(struct hostkeys *hostkeys, | |||
411 | break; | 368 | break; |
412 | } | 369 | } |
413 | if (want_cert) { | 370 | if (want_cert) { |
414 | if (key_equal_public(k->cert->signature_key, | 371 | if (sshkey_equal_public(k->cert->signature_key, |
415 | hostkeys->entries[i].key)) { | 372 | hostkeys->entries[i].key)) { |
416 | /* A matching CA exists */ | 373 | /* A matching CA exists */ |
417 | end_return = HOST_OK; | 374 | end_return = HOST_OK; |
@@ -420,7 +377,7 @@ check_hostkeys_by_key_or_type(struct hostkeys *hostkeys, | |||
420 | break; | 377 | break; |
421 | } | 378 | } |
422 | } else { | 379 | } else { |
423 | if (key_equal(k, hostkeys->entries[i].key)) { | 380 | if (sshkey_equal(k, hostkeys->entries[i].key)) { |
424 | end_return = HOST_OK; | 381 | end_return = HOST_OK; |
425 | if (found != NULL) | 382 | if (found != NULL) |
426 | *found = hostkeys->entries + i; | 383 | *found = hostkeys->entries + i; |
@@ -439,9 +396,9 @@ check_hostkeys_by_key_or_type(struct hostkeys *hostkeys, | |||
439 | } | 396 | } |
440 | return end_return; | 397 | return end_return; |
441 | } | 398 | } |
442 | 399 | ||
443 | HostStatus | 400 | HostStatus |
444 | check_key_in_hostkeys(struct hostkeys *hostkeys, Key *key, | 401 | check_key_in_hostkeys(struct hostkeys *hostkeys, struct sshkey *key, |
445 | const struct hostkey_entry **found) | 402 | const struct hostkey_entry **found) |
446 | { | 403 | { |
447 | if (key == NULL) | 404 | if (key == NULL) |
@@ -457,40 +414,438 @@ lookup_key_in_hostkeys_by_type(struct hostkeys *hostkeys, int keytype, | |||
457 | found) == HOST_FOUND); | 414 | found) == HOST_FOUND); |
458 | } | 415 | } |
459 | 416 | ||
417 | static int | ||
418 | write_host_entry(FILE *f, const char *host, const char *ip, | ||
419 | const struct sshkey *key, int store_hash) | ||
420 | { | ||
421 | int r, success = 0; | ||
422 | char *hashed_host = NULL; | ||
423 | |||
424 | if (store_hash) { | ||
425 | if ((hashed_host = host_hash(host, NULL, 0)) == NULL) { | ||
426 | error("%s: host_hash failed", __func__); | ||
427 | return 0; | ||
428 | } | ||
429 | fprintf(f, "%s ", hashed_host); | ||
430 | } else if (ip != NULL) | ||
431 | fprintf(f, "%s,%s ", host, ip); | ||
432 | else | ||
433 | fprintf(f, "%s ", host); | ||
434 | |||
435 | if ((r = sshkey_write(key, f)) == 0) | ||
436 | success = 1; | ||
437 | else | ||
438 | error("%s: sshkey_write failed: %s", __func__, ssh_err(r)); | ||
439 | fputc('\n', f); | ||
440 | return success; | ||
441 | } | ||
442 | |||
460 | /* | 443 | /* |
461 | * Appends an entry to the host file. Returns false if the entry could not | 444 | * Appends an entry to the host file. Returns false if the entry could not |
462 | * be appended. | 445 | * be appended. |
463 | */ | 446 | */ |
464 | |||
465 | int | 447 | int |
466 | add_host_to_hostfile(const char *filename, const char *host, const Key *key, | 448 | add_host_to_hostfile(const char *filename, const char *host, |
467 | int store_hash) | 449 | const struct sshkey *key, int store_hash) |
468 | { | 450 | { |
469 | FILE *f; | 451 | FILE *f; |
470 | int success = 0; | 452 | int success; |
471 | char *hashed_host = NULL; | ||
472 | 453 | ||
473 | if (key == NULL) | 454 | if (key == NULL) |
474 | return 1; /* XXX ? */ | 455 | return 1; /* XXX ? */ |
475 | f = fopen(filename, "a"); | 456 | f = fopen(filename, "a"); |
476 | if (!f) | 457 | if (!f) |
477 | return 0; | 458 | return 0; |
459 | success = write_host_entry(f, host, NULL, key, store_hash); | ||
460 | fclose(f); | ||
461 | return success; | ||
462 | } | ||
478 | 463 | ||
479 | if (store_hash) { | 464 | struct host_delete_ctx { |
480 | if ((hashed_host = host_hash(host, NULL, 0)) == NULL) { | 465 | FILE *out; |
481 | error("add_host_to_hostfile: host_hash failed"); | 466 | int quiet; |
482 | fclose(f); | 467 | const char *host; |
468 | int *skip_keys; /* XXX split for host/ip? might want to ensure both */ | ||
469 | struct sshkey * const *keys; | ||
470 | size_t nkeys; | ||
471 | int modified; | ||
472 | }; | ||
473 | |||
474 | static int | ||
475 | host_delete(struct hostkey_foreach_line *l, void *_ctx) | ||
476 | { | ||
477 | struct host_delete_ctx *ctx = (struct host_delete_ctx *)_ctx; | ||
478 | int loglevel = ctx->quiet ? SYSLOG_LEVEL_DEBUG1 : SYSLOG_LEVEL_VERBOSE; | ||
479 | size_t i; | ||
480 | |||
481 | if (l->status == HKF_STATUS_MATCHED) { | ||
482 | if (l->marker != MRK_NONE) { | ||
483 | /* Don't remove CA and revocation lines */ | ||
484 | fprintf(ctx->out, "%s\n", l->line); | ||
485 | return 0; | ||
486 | } | ||
487 | |||
488 | /* XXX might need a knob for this later */ | ||
489 | /* Don't remove RSA1 keys */ | ||
490 | if (l->key->type == KEY_RSA1) { | ||
491 | fprintf(ctx->out, "%s\n", l->line); | ||
483 | return 0; | 492 | return 0; |
484 | } | 493 | } |
494 | |||
495 | /* | ||
496 | * If this line contains one of the keys that we will be | ||
497 | * adding later, then don't change it and mark the key for | ||
498 | * skipping. | ||
499 | */ | ||
500 | for (i = 0; i < ctx->nkeys; i++) { | ||
501 | if (sshkey_equal(ctx->keys[i], l->key)) { | ||
502 | ctx->skip_keys[i] = 1; | ||
503 | fprintf(ctx->out, "%s\n", l->line); | ||
504 | debug3("%s: %s key already at %s:%ld", __func__, | ||
505 | sshkey_type(l->key), l->path, l->linenum); | ||
506 | return 0; | ||
507 | } | ||
508 | } | ||
509 | |||
510 | /* | ||
511 | * Hostname matches and has no CA/revoke marker, delete it | ||
512 | * by *not* writing the line to ctx->out. | ||
513 | */ | ||
514 | do_log2(loglevel, "%s%s%s:%ld: Removed %s key for host %s", | ||
515 | ctx->quiet ? __func__ : "", ctx->quiet ? ": " : "", | ||
516 | l->path, l->linenum, sshkey_type(l->key), ctx->host); | ||
517 | ctx->modified = 1; | ||
518 | return 0; | ||
519 | } | ||
520 | /* Retain non-matching hosts and invalid lines when deleting */ | ||
521 | if (l->status == HKF_STATUS_INVALID) { | ||
522 | do_log2(loglevel, "%s%s%s:%ld: invalid known_hosts entry", | ||
523 | ctx->quiet ? __func__ : "", ctx->quiet ? ": " : "", | ||
524 | l->path, l->linenum); | ||
485 | } | 525 | } |
486 | fprintf(f, "%s ", store_hash ? hashed_host : host); | 526 | fprintf(ctx->out, "%s\n", l->line); |
527 | return 0; | ||
528 | } | ||
487 | 529 | ||
488 | if (key_write(key, f)) { | 530 | int |
489 | success = 1; | 531 | hostfile_replace_entries(const char *filename, const char *host, const char *ip, |
532 | struct sshkey **keys, size_t nkeys, int store_hash, int quiet, int hash_alg) | ||
533 | { | ||
534 | int r, fd, oerrno = 0; | ||
535 | int loglevel = quiet ? SYSLOG_LEVEL_DEBUG1 : SYSLOG_LEVEL_VERBOSE; | ||
536 | struct host_delete_ctx ctx; | ||
537 | char *fp, *temp = NULL, *back = NULL; | ||
538 | mode_t omask; | ||
539 | size_t i; | ||
540 | |||
541 | omask = umask(077); | ||
542 | |||
543 | memset(&ctx, 0, sizeof(ctx)); | ||
544 | ctx.host = host; | ||
545 | ctx.quiet = quiet; | ||
546 | if ((ctx.skip_keys = calloc(nkeys, sizeof(*ctx.skip_keys))) == NULL) | ||
547 | return SSH_ERR_ALLOC_FAIL; | ||
548 | ctx.keys = keys; | ||
549 | ctx.nkeys = nkeys; | ||
550 | ctx.modified = 0; | ||
551 | |||
552 | /* | ||
553 | * Prepare temporary file for in-place deletion. | ||
554 | */ | ||
555 | if ((r = asprintf(&temp, "%s.XXXXXXXXXXX", filename)) < 0 || | ||
556 | (r = asprintf(&back, "%s.old", filename)) < 0) { | ||
557 | r = SSH_ERR_ALLOC_FAIL; | ||
558 | goto fail; | ||
559 | } | ||
560 | |||
561 | if ((fd = mkstemp(temp)) == -1) { | ||
562 | oerrno = errno; | ||
563 | error("%s: mkstemp: %s", __func__, strerror(oerrno)); | ||
564 | r = SSH_ERR_SYSTEM_ERROR; | ||
565 | goto fail; | ||
566 | } | ||
567 | if ((ctx.out = fdopen(fd, "w")) == NULL) { | ||
568 | oerrno = errno; | ||
569 | close(fd); | ||
570 | error("%s: fdopen: %s", __func__, strerror(oerrno)); | ||
571 | r = SSH_ERR_SYSTEM_ERROR; | ||
572 | goto fail; | ||
573 | } | ||
574 | |||
575 | /* Remove all entries for the specified host from the file */ | ||
576 | if ((r = hostkeys_foreach(filename, host_delete, &ctx, host, ip, | ||
577 | HKF_WANT_PARSE_KEY)) != 0) { | ||
578 | error("%s: hostkeys_foreach failed: %s", __func__, ssh_err(r)); | ||
579 | goto fail; | ||
580 | } | ||
581 | |||
582 | /* Add the requested keys */ | ||
583 | for (i = 0; i < nkeys; i++) { | ||
584 | if (ctx.skip_keys[i]) | ||
585 | continue; | ||
586 | if ((fp = sshkey_fingerprint(keys[i], hash_alg, | ||
587 | SSH_FP_DEFAULT)) == NULL) { | ||
588 | r = SSH_ERR_ALLOC_FAIL; | ||
589 | goto fail; | ||
590 | } | ||
591 | do_log2(loglevel, "%s%sAdding new key for %s to %s: %s %s", | ||
592 | quiet ? __func__ : "", quiet ? ": " : "", host, filename, | ||
593 | sshkey_ssh_name(keys[i]), fp); | ||
594 | free(fp); | ||
595 | if (!write_host_entry(ctx.out, host, ip, keys[i], store_hash)) { | ||
596 | r = SSH_ERR_INTERNAL_ERROR; | ||
597 | goto fail; | ||
598 | } | ||
599 | ctx.modified = 1; | ||
600 | } | ||
601 | fclose(ctx.out); | ||
602 | ctx.out = NULL; | ||
603 | |||
604 | if (ctx.modified) { | ||
605 | /* Backup the original file and replace it with the temporary */ | ||
606 | if (unlink(back) == -1 && errno != ENOENT) { | ||
607 | oerrno = errno; | ||
608 | error("%s: unlink %.100s: %s", __func__, | ||
609 | back, strerror(errno)); | ||
610 | r = SSH_ERR_SYSTEM_ERROR; | ||
611 | goto fail; | ||
612 | } | ||
613 | if (link(filename, back) == -1) { | ||
614 | oerrno = errno; | ||
615 | error("%s: link %.100s to %.100s: %s", __func__, | ||
616 | filename, back, strerror(errno)); | ||
617 | r = SSH_ERR_SYSTEM_ERROR; | ||
618 | goto fail; | ||
619 | } | ||
620 | if (rename(temp, filename) == -1) { | ||
621 | oerrno = errno; | ||
622 | error("%s: rename \"%s\" to \"%s\": %s", __func__, | ||
623 | temp, filename, strerror(errno)); | ||
624 | r = SSH_ERR_SYSTEM_ERROR; | ||
625 | goto fail; | ||
626 | } | ||
490 | } else { | 627 | } else { |
491 | error("add_host_to_hostfile: saving key in %s failed", filename); | 628 | /* No changes made; just delete the temporary file */ |
629 | if (unlink(temp) != 0) | ||
630 | error("%s: unlink \"%s\": %s", __func__, | ||
631 | temp, strerror(errno)); | ||
632 | } | ||
633 | |||
634 | /* success */ | ||
635 | r = 0; | ||
636 | fail: | ||
637 | if (temp != NULL && r != 0) | ||
638 | unlink(temp); | ||
639 | free(temp); | ||
640 | free(back); | ||
641 | if (ctx.out != NULL) | ||
642 | fclose(ctx.out); | ||
643 | free(ctx.skip_keys); | ||
644 | umask(omask); | ||
645 | if (r == SSH_ERR_SYSTEM_ERROR) | ||
646 | errno = oerrno; | ||
647 | return r; | ||
648 | } | ||
649 | |||
650 | static int | ||
651 | match_maybe_hashed(const char *host, const char *names, int *was_hashed) | ||
652 | { | ||
653 | int hashed = *names == HASH_DELIM; | ||
654 | const char *hashed_host; | ||
655 | size_t nlen = strlen(names); | ||
656 | |||
657 | if (was_hashed != NULL) | ||
658 | *was_hashed = hashed; | ||
659 | if (hashed) { | ||
660 | if ((hashed_host = host_hash(host, names, nlen)) == NULL) | ||
661 | return -1; | ||
662 | return nlen == strlen(hashed_host) && | ||
663 | strncmp(hashed_host, names, nlen) == 0; | ||
664 | } | ||
665 | return match_hostname(host, names, nlen) == 1; | ||
666 | } | ||
667 | |||
668 | int | ||
669 | hostkeys_foreach(const char *path, hostkeys_foreach_fn *callback, void *ctx, | ||
670 | const char *host, const char *ip, u_int options) | ||
671 | { | ||
672 | FILE *f; | ||
673 | char line[8192], oline[8192], ktype[128]; | ||
674 | u_long linenum = 0; | ||
675 | char *cp, *cp2; | ||
676 | u_int kbits; | ||
677 | int hashed; | ||
678 | int s, r = 0; | ||
679 | struct hostkey_foreach_line lineinfo; | ||
680 | size_t l; | ||
681 | |||
682 | memset(&lineinfo, 0, sizeof(lineinfo)); | ||
683 | if (host == NULL && (options & HKF_WANT_MATCH) != 0) | ||
684 | return SSH_ERR_INVALID_ARGUMENT; | ||
685 | if ((f = fopen(path, "r")) == NULL) | ||
686 | return SSH_ERR_SYSTEM_ERROR; | ||
687 | |||
688 | debug3("%s: reading file \"%s\"", __func__, path); | ||
689 | while (read_keyfile_line(f, path, line, sizeof(line), &linenum) == 0) { | ||
690 | line[strcspn(line, "\n")] = '\0'; | ||
691 | strlcpy(oline, line, sizeof(oline)); | ||
692 | |||
693 | sshkey_free(lineinfo.key); | ||
694 | memset(&lineinfo, 0, sizeof(lineinfo)); | ||
695 | lineinfo.path = path; | ||
696 | lineinfo.linenum = linenum; | ||
697 | lineinfo.line = oline; | ||
698 | lineinfo.marker = MRK_NONE; | ||
699 | lineinfo.status = HKF_STATUS_OK; | ||
700 | lineinfo.keytype = KEY_UNSPEC; | ||
701 | |||
702 | /* Skip any leading whitespace, comments and empty lines. */ | ||
703 | for (cp = line; *cp == ' ' || *cp == '\t'; cp++) | ||
704 | ; | ||
705 | if (!*cp || *cp == '#' || *cp == '\n') { | ||
706 | if ((options & HKF_WANT_MATCH) == 0) { | ||
707 | lineinfo.status = HKF_STATUS_COMMENT; | ||
708 | if ((r = callback(&lineinfo, ctx)) != 0) | ||
709 | break; | ||
710 | } | ||
711 | continue; | ||
712 | } | ||
713 | |||
714 | if ((lineinfo.marker = check_markers(&cp)) == MRK_ERROR) { | ||
715 | verbose("%s: invalid marker at %s:%lu", | ||
716 | __func__, path, linenum); | ||
717 | if ((options & HKF_WANT_MATCH) == 0) | ||
718 | goto bad; | ||
719 | continue; | ||
720 | } | ||
721 | |||
722 | /* Find the end of the host name portion. */ | ||
723 | for (cp2 = cp; *cp2 && *cp2 != ' ' && *cp2 != '\t'; cp2++) | ||
724 | ; | ||
725 | lineinfo.hosts = cp; | ||
726 | *cp2++ = '\0'; | ||
727 | |||
728 | /* Check if the host name matches. */ | ||
729 | if (host != NULL) { | ||
730 | if ((s = match_maybe_hashed(host, lineinfo.hosts, | ||
731 | &hashed)) == -1) { | ||
732 | debug2("%s: %s:%ld: bad host hash \"%.32s\"", | ||
733 | __func__, path, linenum, lineinfo.hosts); | ||
734 | goto bad; | ||
735 | } | ||
736 | if (s == 1) { | ||
737 | lineinfo.status = HKF_STATUS_MATCHED; | ||
738 | lineinfo.match |= HKF_MATCH_HOST | | ||
739 | (hashed ? HKF_MATCH_HOST_HASHED : 0); | ||
740 | } | ||
741 | /* Try matching IP address if supplied */ | ||
742 | if (ip != NULL) { | ||
743 | if ((s = match_maybe_hashed(ip, lineinfo.hosts, | ||
744 | &hashed)) == -1) { | ||
745 | debug2("%s: %s:%ld: bad ip hash " | ||
746 | "\"%.32s\"", __func__, path, | ||
747 | linenum, lineinfo.hosts); | ||
748 | goto bad; | ||
749 | } | ||
750 | if (s == 1) { | ||
751 | lineinfo.status = HKF_STATUS_MATCHED; | ||
752 | lineinfo.match |= HKF_MATCH_IP | | ||
753 | (hashed ? HKF_MATCH_IP_HASHED : 0); | ||
754 | } | ||
755 | } | ||
756 | /* | ||
757 | * Skip this line if host matching requested and | ||
758 | * neither host nor address matched. | ||
759 | */ | ||
760 | if ((options & HKF_WANT_MATCH) != 0 && | ||
761 | lineinfo.status != HKF_STATUS_MATCHED) | ||
762 | continue; | ||
763 | } | ||
764 | |||
765 | /* Got a match. Skip host name and any following whitespace */ | ||
766 | for (; *cp2 == ' ' || *cp2 == '\t'; cp2++) | ||
767 | ; | ||
768 | if (*cp2 == '\0' || *cp2 == '#') { | ||
769 | debug2("%s:%ld: truncated before key type", | ||
770 | path, linenum); | ||
771 | goto bad; | ||
772 | } | ||
773 | lineinfo.rawkey = cp = cp2; | ||
774 | |||
775 | if ((options & HKF_WANT_PARSE_KEY) != 0) { | ||
776 | /* | ||
777 | * Extract the key from the line. This will skip | ||
778 | * any leading whitespace. Ignore badly formatted | ||
779 | * lines. | ||
780 | */ | ||
781 | if ((lineinfo.key = sshkey_new(KEY_UNSPEC)) == NULL) { | ||
782 | error("%s: sshkey_new failed", __func__); | ||
783 | r = SSH_ERR_ALLOC_FAIL; | ||
784 | break; | ||
785 | } | ||
786 | if (!hostfile_read_key(&cp, &kbits, lineinfo.key)) { | ||
787 | #ifdef WITH_SSH1 | ||
788 | sshkey_free(lineinfo.key); | ||
789 | lineinfo.key = sshkey_new(KEY_RSA1); | ||
790 | if (lineinfo.key == NULL) { | ||
791 | error("%s: sshkey_new fail", __func__); | ||
792 | r = SSH_ERR_ALLOC_FAIL; | ||
793 | break; | ||
794 | } | ||
795 | if (!hostfile_read_key(&cp, &kbits, | ||
796 | lineinfo.key)) | ||
797 | goto bad; | ||
798 | #else | ||
799 | goto bad; | ||
800 | #endif | ||
801 | } | ||
802 | lineinfo.keytype = lineinfo.key->type; | ||
803 | lineinfo.comment = cp; | ||
804 | } else { | ||
805 | /* Extract and parse key type */ | ||
806 | l = strcspn(lineinfo.rawkey, " \t"); | ||
807 | if (l <= 1 || l >= sizeof(ktype) || | ||
808 | lineinfo.rawkey[l] == '\0') | ||
809 | goto bad; | ||
810 | memcpy(ktype, lineinfo.rawkey, l); | ||
811 | ktype[l] = '\0'; | ||
812 | lineinfo.keytype = sshkey_type_from_name(ktype); | ||
813 | #ifdef WITH_SSH1 | ||
814 | /* | ||
815 | * Assume RSA1 if the first component is a short | ||
816 | * decimal number. | ||
817 | */ | ||
818 | if (lineinfo.keytype == KEY_UNSPEC && l < 8 && | ||
819 | strspn(ktype, "0123456789") == l) | ||
820 | lineinfo.keytype = KEY_RSA1; | ||
821 | #endif | ||
822 | /* | ||
823 | * Check that something other than whitespace follows | ||
824 | * the key type. This won't catch all corruption, but | ||
825 | * it does catch trivial truncation. | ||
826 | */ | ||
827 | cp2 += l; /* Skip past key type */ | ||
828 | for (; *cp2 == ' ' || *cp2 == '\t'; cp2++) | ||
829 | ; | ||
830 | if (*cp2 == '\0' || *cp2 == '#') { | ||
831 | debug2("%s:%ld: truncated after key type", | ||
832 | path, linenum); | ||
833 | lineinfo.keytype = KEY_UNSPEC; | ||
834 | } | ||
835 | if (lineinfo.keytype == KEY_UNSPEC) { | ||
836 | bad: | ||
837 | sshkey_free(lineinfo.key); | ||
838 | lineinfo.key = NULL; | ||
839 | lineinfo.status = HKF_STATUS_INVALID; | ||
840 | if ((r = callback(&lineinfo, ctx)) != 0) | ||
841 | break; | ||
842 | continue; | ||
843 | } | ||
844 | } | ||
845 | if ((r = callback(&lineinfo, ctx)) != 0) | ||
846 | break; | ||
492 | } | 847 | } |
493 | fprintf(f, "\n"); | 848 | sshkey_free(lineinfo.key); |
494 | fclose(f); | 849 | fclose(f); |
495 | return success; | 850 | return r; |
496 | } | 851 | } |