hostfile.c 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909
  1. /* $OpenBSD: hostfile.c,v 1.82 2020/06/26 05:42:16 djm Exp $ */
  2. /*
  3. * Author: Tatu Ylonen <ylo@cs.hut.fi>
  4. * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
  5. * All rights reserved
  6. * Functions for manipulating the known hosts files.
  7. *
  8. * As far as I am concerned, the code I have written for this software
  9. * can be used freely for any purpose. Any derived versions of this
  10. * software must be clearly marked as such, and if the derived work is
  11. * incompatible with the protocol description in the RFC file, it must be
  12. * called by a name other than "ssh" or "Secure Shell".
  13. *
  14. *
  15. * Copyright (c) 1999, 2000 Markus Friedl. All rights reserved.
  16. * Copyright (c) 1999 Niels Provos. All rights reserved.
  17. *
  18. * Redistribution and use in source and binary forms, with or without
  19. * modification, are permitted provided that the following conditions
  20. * are met:
  21. * 1. Redistributions of source code must retain the above copyright
  22. * notice, this list of conditions and the following disclaimer.
  23. * 2. Redistributions in binary form must reproduce the above copyright
  24. * notice, this list of conditions and the following disclaimer in the
  25. * documentation and/or other materials provided with the distribution.
  26. *
  27. * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
  28. * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  29. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
  30. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
  31. * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  32. * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  33. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  34. * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  35. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  36. * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  37. */
  38. #include "includes.h"
  39. #include <sys/types.h>
  40. #include <sys/stat.h>
  41. #include <netinet/in.h>
  42. #include <errno.h>
  43. #include <resolv.h>
  44. #include <stdarg.h>
  45. #include <stdio.h>
  46. #include <stdlib.h>
  47. #include <string.h>
  48. #include <unistd.h>
  49. #include "xmalloc.h"
  50. #include "match.h"
  51. #include "sshkey.h"
  52. #include "hostfile.h"
  53. #include "log.h"
  54. #include "misc.h"
  55. #include "pathnames.h"
  56. #include "ssherr.h"
  57. #include "digest.h"
  58. #include "hmac.h"
  59. #include "sshbuf.h"
  60. /* XXX hmac is too easy to dictionary attack; use bcrypt? */
  61. static int
  62. extract_salt(const char *s, u_int l, u_char *salt, size_t salt_len)
  63. {
  64. char *p, *b64salt;
  65. u_int b64len;
  66. int ret;
  67. if (l < sizeof(HASH_MAGIC) - 1) {
  68. debug2("extract_salt: string too short");
  69. return (-1);
  70. }
  71. if (strncmp(s, HASH_MAGIC, sizeof(HASH_MAGIC) - 1) != 0) {
  72. debug2("extract_salt: invalid magic identifier");
  73. return (-1);
  74. }
  75. s += sizeof(HASH_MAGIC) - 1;
  76. l -= sizeof(HASH_MAGIC) - 1;
  77. if ((p = memchr(s, HASH_DELIM, l)) == NULL) {
  78. debug2("extract_salt: missing salt termination character");
  79. return (-1);
  80. }
  81. b64len = p - s;
  82. /* Sanity check */
  83. if (b64len == 0 || b64len > 1024) {
  84. debug2("extract_salt: bad encoded salt length %u", b64len);
  85. return (-1);
  86. }
  87. b64salt = xmalloc(1 + b64len);
  88. memcpy(b64salt, s, b64len);
  89. b64salt[b64len] = '\0';
  90. ret = __b64_pton(b64salt, salt, salt_len);
  91. free(b64salt);
  92. if (ret == -1) {
  93. debug2("extract_salt: salt decode error");
  94. return (-1);
  95. }
  96. if (ret != (int)ssh_hmac_bytes(SSH_DIGEST_SHA1)) {
  97. debug2("extract_salt: expected salt len %zd, got %d",
  98. ssh_hmac_bytes(SSH_DIGEST_SHA1), ret);
  99. return (-1);
  100. }
  101. return (0);
  102. }
  103. char *
  104. host_hash(const char *host, const char *name_from_hostfile, u_int src_len)
  105. {
  106. struct ssh_hmac_ctx *ctx;
  107. u_char salt[256], result[256];
  108. char uu_salt[512], uu_result[512];
  109. static char encoded[1024];
  110. u_int len;
  111. len = ssh_digest_bytes(SSH_DIGEST_SHA1);
  112. if (name_from_hostfile == NULL) {
  113. /* Create new salt */
  114. arc4random_buf(salt, len);
  115. } else {
  116. /* Extract salt from known host entry */
  117. if (extract_salt(name_from_hostfile, src_len, salt,
  118. sizeof(salt)) == -1)
  119. return (NULL);
  120. }
  121. if ((ctx = ssh_hmac_start(SSH_DIGEST_SHA1)) == NULL ||
  122. ssh_hmac_init(ctx, salt, len) < 0 ||
  123. ssh_hmac_update(ctx, host, strlen(host)) < 0 ||
  124. ssh_hmac_final(ctx, result, sizeof(result)))
  125. fatal("%s: ssh_hmac failed", __func__);
  126. ssh_hmac_free(ctx);
  127. if (__b64_ntop(salt, len, uu_salt, sizeof(uu_salt)) == -1 ||
  128. __b64_ntop(result, len, uu_result, sizeof(uu_result)) == -1)
  129. fatal("%s: __b64_ntop failed", __func__);
  130. snprintf(encoded, sizeof(encoded), "%s%s%c%s", HASH_MAGIC, uu_salt,
  131. HASH_DELIM, uu_result);
  132. return (encoded);
  133. }
  134. /*
  135. * Parses an RSA (number of bits, e, n) or DSA key from a string. Moves the
  136. * pointer over the key. Skips any whitespace at the beginning and at end.
  137. */
  138. int
  139. hostfile_read_key(char **cpp, u_int *bitsp, struct sshkey *ret)
  140. {
  141. char *cp;
  142. /* Skip leading whitespace. */
  143. for (cp = *cpp; *cp == ' ' || *cp == '\t'; cp++)
  144. ;
  145. if (sshkey_read(ret, &cp) != 0)
  146. return 0;
  147. /* Skip trailing whitespace. */
  148. for (; *cp == ' ' || *cp == '\t'; cp++)
  149. ;
  150. /* Return results. */
  151. *cpp = cp;
  152. if (bitsp != NULL)
  153. *bitsp = sshkey_size(ret);
  154. return 1;
  155. }
  156. static HostkeyMarker
  157. check_markers(char **cpp)
  158. {
  159. char marker[32], *sp, *cp = *cpp;
  160. int ret = MRK_NONE;
  161. while (*cp == '@') {
  162. /* Only one marker is allowed */
  163. if (ret != MRK_NONE)
  164. return MRK_ERROR;
  165. /* Markers are terminated by whitespace */
  166. if ((sp = strchr(cp, ' ')) == NULL &&
  167. (sp = strchr(cp, '\t')) == NULL)
  168. return MRK_ERROR;
  169. /* Extract marker for comparison */
  170. if (sp <= cp + 1 || sp >= cp + sizeof(marker))
  171. return MRK_ERROR;
  172. memcpy(marker, cp, sp - cp);
  173. marker[sp - cp] = '\0';
  174. if (strcmp(marker, CA_MARKER) == 0)
  175. ret = MRK_CA;
  176. else if (strcmp(marker, REVOKE_MARKER) == 0)
  177. ret = MRK_REVOKE;
  178. else
  179. return MRK_ERROR;
  180. /* Skip past marker and any whitespace that follows it */
  181. cp = sp;
  182. for (; *cp == ' ' || *cp == '\t'; cp++)
  183. ;
  184. }
  185. *cpp = cp;
  186. return ret;
  187. }
  188. struct hostkeys *
  189. init_hostkeys(void)
  190. {
  191. struct hostkeys *ret = xcalloc(1, sizeof(*ret));
  192. ret->entries = NULL;
  193. return ret;
  194. }
  195. struct load_callback_ctx {
  196. const char *host;
  197. u_long num_loaded;
  198. struct hostkeys *hostkeys;
  199. };
  200. static int
  201. record_hostkey(struct hostkey_foreach_line *l, void *_ctx)
  202. {
  203. struct load_callback_ctx *ctx = (struct load_callback_ctx *)_ctx;
  204. struct hostkeys *hostkeys = ctx->hostkeys;
  205. struct hostkey_entry *tmp;
  206. if (l->status == HKF_STATUS_INVALID) {
  207. /* XXX make this verbose() in the future */
  208. debug("%s:%ld: parse error in hostkeys file",
  209. l->path, l->linenum);
  210. return 0;
  211. }
  212. debug3("%s: found %skey type %s in file %s:%lu", __func__,
  213. l->marker == MRK_NONE ? "" :
  214. (l->marker == MRK_CA ? "ca " : "revoked "),
  215. sshkey_type(l->key), l->path, l->linenum);
  216. if ((tmp = recallocarray(hostkeys->entries, hostkeys->num_entries,
  217. hostkeys->num_entries + 1, sizeof(*hostkeys->entries))) == NULL)
  218. return SSH_ERR_ALLOC_FAIL;
  219. hostkeys->entries = tmp;
  220. hostkeys->entries[hostkeys->num_entries].host = xstrdup(ctx->host);
  221. hostkeys->entries[hostkeys->num_entries].file = xstrdup(l->path);
  222. hostkeys->entries[hostkeys->num_entries].line = l->linenum;
  223. hostkeys->entries[hostkeys->num_entries].key = l->key;
  224. l->key = NULL; /* steal it */
  225. hostkeys->entries[hostkeys->num_entries].marker = l->marker;
  226. hostkeys->num_entries++;
  227. ctx->num_loaded++;
  228. return 0;
  229. }
  230. void
  231. load_hostkeys(struct hostkeys *hostkeys, const char *host, const char *path)
  232. {
  233. int r;
  234. struct load_callback_ctx ctx;
  235. ctx.host = host;
  236. ctx.num_loaded = 0;
  237. ctx.hostkeys = hostkeys;
  238. if ((r = hostkeys_foreach(path, record_hostkey, &ctx, host, NULL,
  239. HKF_WANT_MATCH|HKF_WANT_PARSE_KEY)) != 0) {
  240. if (r != SSH_ERR_SYSTEM_ERROR && errno != ENOENT)
  241. debug("%s: hostkeys_foreach failed for %s: %s",
  242. __func__, path, ssh_err(r));
  243. }
  244. if (ctx.num_loaded != 0)
  245. debug3("%s: loaded %lu keys from %s", __func__,
  246. ctx.num_loaded, host);
  247. }
  248. void
  249. free_hostkeys(struct hostkeys *hostkeys)
  250. {
  251. u_int i;
  252. for (i = 0; i < hostkeys->num_entries; i++) {
  253. free(hostkeys->entries[i].host);
  254. free(hostkeys->entries[i].file);
  255. sshkey_free(hostkeys->entries[i].key);
  256. explicit_bzero(hostkeys->entries + i, sizeof(*hostkeys->entries));
  257. }
  258. free(hostkeys->entries);
  259. freezero(hostkeys, sizeof(*hostkeys));
  260. }
  261. static int
  262. check_key_not_revoked(struct hostkeys *hostkeys, struct sshkey *k)
  263. {
  264. int is_cert = sshkey_is_cert(k);
  265. u_int i;
  266. for (i = 0; i < hostkeys->num_entries; i++) {
  267. if (hostkeys->entries[i].marker != MRK_REVOKE)
  268. continue;
  269. if (sshkey_equal_public(k, hostkeys->entries[i].key))
  270. return -1;
  271. if (is_cert && k != NULL &&
  272. sshkey_equal_public(k->cert->signature_key,
  273. hostkeys->entries[i].key))
  274. return -1;
  275. }
  276. return 0;
  277. }
  278. /*
  279. * Match keys against a specified key, or look one up by key type.
  280. *
  281. * If looking for a keytype (key == NULL) and one is found then return
  282. * HOST_FOUND, otherwise HOST_NEW.
  283. *
  284. * If looking for a key (key != NULL):
  285. * 1. If the key is a cert and a matching CA is found, return HOST_OK
  286. * 2. If the key is not a cert and a matching key is found, return HOST_OK
  287. * 3. If no key matches but a key with a different type is found, then
  288. * return HOST_CHANGED
  289. * 4. If no matching keys are found, then return HOST_NEW.
  290. *
  291. * Finally, check any found key is not revoked.
  292. */
  293. static HostStatus
  294. check_hostkeys_by_key_or_type(struct hostkeys *hostkeys,
  295. struct sshkey *k, int keytype, int nid, const struct hostkey_entry **found)
  296. {
  297. u_int i;
  298. HostStatus end_return = HOST_NEW;
  299. int want_cert = sshkey_is_cert(k);
  300. HostkeyMarker want_marker = want_cert ? MRK_CA : MRK_NONE;
  301. if (found != NULL)
  302. *found = NULL;
  303. for (i = 0; i < hostkeys->num_entries; i++) {
  304. if (hostkeys->entries[i].marker != want_marker)
  305. continue;
  306. if (k == NULL) {
  307. if (hostkeys->entries[i].key->type != keytype)
  308. continue;
  309. if (nid != -1 &&
  310. sshkey_type_plain(keytype) == KEY_ECDSA &&
  311. hostkeys->entries[i].key->ecdsa_nid != nid)
  312. continue;
  313. end_return = HOST_FOUND;
  314. if (found != NULL)
  315. *found = hostkeys->entries + i;
  316. k = hostkeys->entries[i].key;
  317. break;
  318. }
  319. if (want_cert) {
  320. if (sshkey_equal_public(k->cert->signature_key,
  321. hostkeys->entries[i].key)) {
  322. /* A matching CA exists */
  323. end_return = HOST_OK;
  324. if (found != NULL)
  325. *found = hostkeys->entries + i;
  326. break;
  327. }
  328. } else {
  329. if (sshkey_equal(k, hostkeys->entries[i].key)) {
  330. end_return = HOST_OK;
  331. if (found != NULL)
  332. *found = hostkeys->entries + i;
  333. break;
  334. }
  335. /* A non-matching key exists */
  336. end_return = HOST_CHANGED;
  337. if (found != NULL)
  338. *found = hostkeys->entries + i;
  339. }
  340. }
  341. if (check_key_not_revoked(hostkeys, k) != 0) {
  342. end_return = HOST_REVOKED;
  343. if (found != NULL)
  344. *found = NULL;
  345. }
  346. return end_return;
  347. }
  348. HostStatus
  349. check_key_in_hostkeys(struct hostkeys *hostkeys, struct sshkey *key,
  350. const struct hostkey_entry **found)
  351. {
  352. if (key == NULL)
  353. fatal("no key to look up");
  354. return check_hostkeys_by_key_or_type(hostkeys, key, 0, -1, found);
  355. }
  356. int
  357. lookup_key_in_hostkeys_by_type(struct hostkeys *hostkeys, int keytype, int nid,
  358. const struct hostkey_entry **found)
  359. {
  360. return (check_hostkeys_by_key_or_type(hostkeys, NULL, keytype, nid,
  361. found) == HOST_FOUND);
  362. }
  363. int
  364. lookup_marker_in_hostkeys(struct hostkeys *hostkeys, int want_marker)
  365. {
  366. u_int i;
  367. for (i = 0; i < hostkeys->num_entries; i++) {
  368. if (hostkeys->entries[i].marker == (HostkeyMarker)want_marker)
  369. return 1;
  370. }
  371. return 0;
  372. }
  373. static int
  374. write_host_entry(FILE *f, const char *host, const char *ip,
  375. const struct sshkey *key, int store_hash)
  376. {
  377. int r, success = 0;
  378. char *hashed_host = NULL, *lhost;
  379. lhost = xstrdup(host);
  380. lowercase(lhost);
  381. if (store_hash) {
  382. if ((hashed_host = host_hash(lhost, NULL, 0)) == NULL) {
  383. error("%s: host_hash failed", __func__);
  384. free(lhost);
  385. return 0;
  386. }
  387. fprintf(f, "%s ", hashed_host);
  388. } else if (ip != NULL)
  389. fprintf(f, "%s,%s ", lhost, ip);
  390. else {
  391. fprintf(f, "%s ", lhost);
  392. }
  393. free(lhost);
  394. if ((r = sshkey_write(key, f)) == 0)
  395. success = 1;
  396. else
  397. error("%s: sshkey_write failed: %s", __func__, ssh_err(r));
  398. fputc('\n', f);
  399. /* If hashing is enabled, the IP address needs to go on its own line */
  400. if (success && store_hash && ip != NULL)
  401. success = write_host_entry(f, ip, NULL, key, 1);
  402. return success;
  403. }
  404. /*
  405. * Create user ~/.ssh directory if it doesn't exist and we want to write to it.
  406. * If notify is set, a message will be emitted if the directory is created.
  407. */
  408. void
  409. hostfile_create_user_ssh_dir(const char *filename, int notify)
  410. {
  411. char *dotsshdir = NULL, *p;
  412. size_t len;
  413. struct stat st;
  414. if ((p = strrchr(filename, '/')) == NULL)
  415. return;
  416. len = p - filename;
  417. dotsshdir = tilde_expand_filename("~/" _PATH_SSH_USER_DIR, getuid());
  418. if (strlen(dotsshdir) > len || strncmp(filename, dotsshdir, len) != 0)
  419. goto out; /* not ~/.ssh prefixed */
  420. if (stat(dotsshdir, &st) == 0)
  421. goto out; /* dir already exists */
  422. else if (errno != ENOENT)
  423. error("Could not stat %s: %s", dotsshdir, strerror(errno));
  424. else {
  425. #ifdef WITH_SELINUX
  426. ssh_selinux_setfscreatecon(dotsshdir);
  427. #endif
  428. if (mkdir(dotsshdir, 0700) == -1)
  429. error("Could not create directory '%.200s' (%s).",
  430. dotsshdir, strerror(errno));
  431. else if (notify)
  432. logit("Created directory '%s'.", dotsshdir);
  433. #ifdef WITH_SELINUX
  434. ssh_selinux_setfscreatecon(NULL);
  435. #endif
  436. }
  437. out:
  438. free(dotsshdir);
  439. }
  440. /*
  441. * Appends an entry to the host file. Returns false if the entry could not
  442. * be appended.
  443. */
  444. int
  445. add_host_to_hostfile(const char *filename, const char *host,
  446. const struct sshkey *key, int store_hash)
  447. {
  448. FILE *f;
  449. int success;
  450. if (key == NULL)
  451. return 1; /* XXX ? */
  452. hostfile_create_user_ssh_dir(filename, 0);
  453. f = fopen(filename, "a");
  454. if (!f)
  455. return 0;
  456. success = write_host_entry(f, host, NULL, key, store_hash);
  457. fclose(f);
  458. return success;
  459. }
  460. struct host_delete_ctx {
  461. FILE *out;
  462. int quiet;
  463. const char *host, *ip;
  464. u_int *match_keys; /* mask of HKF_MATCH_* for this key */
  465. struct sshkey * const *keys;
  466. size_t nkeys;
  467. int modified;
  468. };
  469. static int
  470. host_delete(struct hostkey_foreach_line *l, void *_ctx)
  471. {
  472. struct host_delete_ctx *ctx = (struct host_delete_ctx *)_ctx;
  473. int loglevel = ctx->quiet ? SYSLOG_LEVEL_DEBUG1 : SYSLOG_LEVEL_VERBOSE;
  474. size_t i;
  475. /* Don't remove CA and revocation lines */
  476. if (l->status == HKF_STATUS_MATCHED && l->marker == MRK_NONE) {
  477. /*
  478. * If this line contains one of the keys that we will be
  479. * adding later, then don't change it and mark the key for
  480. * skipping.
  481. */
  482. for (i = 0; i < ctx->nkeys; i++) {
  483. if (!sshkey_equal(ctx->keys[i], l->key))
  484. continue;
  485. ctx->match_keys[i] |= l->match;
  486. fprintf(ctx->out, "%s\n", l->line);
  487. debug3("%s: %s key already at %s:%ld", __func__,
  488. sshkey_type(l->key), l->path, l->linenum);
  489. return 0;
  490. }
  491. /*
  492. * Hostname matches and has no CA/revoke marker, delete it
  493. * by *not* writing the line to ctx->out.
  494. */
  495. do_log2(loglevel, "%s%s%s:%ld: Removed %s key for host %s",
  496. ctx->quiet ? __func__ : "", ctx->quiet ? ": " : "",
  497. l->path, l->linenum, sshkey_type(l->key), ctx->host);
  498. ctx->modified = 1;
  499. return 0;
  500. }
  501. /* Retain non-matching hosts and invalid lines when deleting */
  502. if (l->status == HKF_STATUS_INVALID) {
  503. do_log2(loglevel, "%s%s%s:%ld: invalid known_hosts entry",
  504. ctx->quiet ? __func__ : "", ctx->quiet ? ": " : "",
  505. l->path, l->linenum);
  506. }
  507. fprintf(ctx->out, "%s\n", l->line);
  508. return 0;
  509. }
  510. int
  511. hostfile_replace_entries(const char *filename, const char *host, const char *ip,
  512. struct sshkey **keys, size_t nkeys, int store_hash, int quiet, int hash_alg)
  513. {
  514. int r, fd, oerrno = 0;
  515. int loglevel = quiet ? SYSLOG_LEVEL_DEBUG1 : SYSLOG_LEVEL_VERBOSE;
  516. struct host_delete_ctx ctx;
  517. char *fp, *temp = NULL, *back = NULL;
  518. const char *what;
  519. mode_t omask;
  520. size_t i;
  521. u_int want;
  522. omask = umask(077);
  523. memset(&ctx, 0, sizeof(ctx));
  524. ctx.host = host;
  525. ctx.ip = ip;
  526. ctx.quiet = quiet;
  527. if ((ctx.match_keys = calloc(nkeys, sizeof(*ctx.match_keys))) == NULL)
  528. return SSH_ERR_ALLOC_FAIL;
  529. ctx.keys = keys;
  530. ctx.nkeys = nkeys;
  531. ctx.modified = 0;
  532. /*
  533. * Prepare temporary file for in-place deletion.
  534. */
  535. if ((r = asprintf(&temp, "%s.XXXXXXXXXXX", filename)) == -1 ||
  536. (r = asprintf(&back, "%s.old", filename)) == -1) {
  537. r = SSH_ERR_ALLOC_FAIL;
  538. goto fail;
  539. }
  540. if ((fd = mkstemp(temp)) == -1) {
  541. oerrno = errno;
  542. error("%s: mkstemp: %s", __func__, strerror(oerrno));
  543. r = SSH_ERR_SYSTEM_ERROR;
  544. goto fail;
  545. }
  546. if ((ctx.out = fdopen(fd, "w")) == NULL) {
  547. oerrno = errno;
  548. close(fd);
  549. error("%s: fdopen: %s", __func__, strerror(oerrno));
  550. r = SSH_ERR_SYSTEM_ERROR;
  551. goto fail;
  552. }
  553. /* Remove stale/mismatching entries for the specified host */
  554. if ((r = hostkeys_foreach(filename, host_delete, &ctx, host, ip,
  555. HKF_WANT_PARSE_KEY)) != 0) {
  556. oerrno = errno;
  557. error("%s: hostkeys_foreach failed: %s", __func__, ssh_err(r));
  558. goto fail;
  559. }
  560. /* Re-add the requested keys */
  561. want = HKF_MATCH_HOST | (ip == NULL ? 0 : HKF_MATCH_IP);
  562. for (i = 0; i < nkeys; i++) {
  563. if ((want & ctx.match_keys[i]) == want)
  564. continue;
  565. if ((fp = sshkey_fingerprint(keys[i], hash_alg,
  566. SSH_FP_DEFAULT)) == NULL) {
  567. r = SSH_ERR_ALLOC_FAIL;
  568. goto fail;
  569. }
  570. /* write host/ip */
  571. what = "";
  572. if (ctx.match_keys[i] == 0) {
  573. what = "Adding new key";
  574. if (!write_host_entry(ctx.out, host, ip,
  575. keys[i], store_hash)) {
  576. r = SSH_ERR_INTERNAL_ERROR;
  577. goto fail;
  578. }
  579. } else if ((want & ~ctx.match_keys[i]) == HKF_MATCH_HOST) {
  580. what = "Fixing match (hostname)";
  581. if (!write_host_entry(ctx.out, host, NULL,
  582. keys[i], store_hash)) {
  583. r = SSH_ERR_INTERNAL_ERROR;
  584. goto fail;
  585. }
  586. } else if ((want & ~ctx.match_keys[i]) == HKF_MATCH_IP) {
  587. what = "Fixing match (address)";
  588. if (!write_host_entry(ctx.out, ip, NULL,
  589. keys[i], store_hash)) {
  590. r = SSH_ERR_INTERNAL_ERROR;
  591. goto fail;
  592. }
  593. }
  594. do_log2(loglevel, "%s%s%s for %s%s%s to %s: %s %s",
  595. quiet ? __func__ : "", quiet ? ": " : "", what,
  596. host, ip == NULL ? "" : ",", ip == NULL ? "" : ip, filename,
  597. sshkey_ssh_name(keys[i]), fp);
  598. free(fp);
  599. ctx.modified = 1;
  600. }
  601. fclose(ctx.out);
  602. ctx.out = NULL;
  603. if (ctx.modified) {
  604. /* Backup the original file and replace it with the temporary */
  605. if (unlink(back) == -1 && errno != ENOENT) {
  606. oerrno = errno;
  607. error("%s: unlink %.100s: %s", __func__,
  608. back, strerror(errno));
  609. r = SSH_ERR_SYSTEM_ERROR;
  610. goto fail;
  611. }
  612. if (link(filename, back) == -1) {
  613. oerrno = errno;
  614. error("%s: link %.100s to %.100s: %s", __func__,
  615. filename, back, strerror(errno));
  616. r = SSH_ERR_SYSTEM_ERROR;
  617. goto fail;
  618. }
  619. if (rename(temp, filename) == -1) {
  620. oerrno = errno;
  621. error("%s: rename \"%s\" to \"%s\": %s", __func__,
  622. temp, filename, strerror(errno));
  623. r = SSH_ERR_SYSTEM_ERROR;
  624. goto fail;
  625. }
  626. } else {
  627. /* No changes made; just delete the temporary file */
  628. if (unlink(temp) != 0)
  629. error("%s: unlink \"%s\": %s", __func__,
  630. temp, strerror(errno));
  631. }
  632. /* success */
  633. r = 0;
  634. fail:
  635. if (temp != NULL && r != 0)
  636. unlink(temp);
  637. free(temp);
  638. free(back);
  639. if (ctx.out != NULL)
  640. fclose(ctx.out);
  641. free(ctx.match_keys);
  642. umask(omask);
  643. if (r == SSH_ERR_SYSTEM_ERROR)
  644. errno = oerrno;
  645. return r;
  646. }
  647. static int
  648. match_maybe_hashed(const char *host, const char *names, int *was_hashed)
  649. {
  650. int hashed = *names == HASH_DELIM;
  651. const char *hashed_host;
  652. size_t nlen = strlen(names);
  653. if (was_hashed != NULL)
  654. *was_hashed = hashed;
  655. if (hashed) {
  656. if ((hashed_host = host_hash(host, names, nlen)) == NULL)
  657. return -1;
  658. return nlen == strlen(hashed_host) &&
  659. strncmp(hashed_host, names, nlen) == 0;
  660. }
  661. return match_hostname(host, names) == 1;
  662. }
  663. int
  664. hostkeys_foreach(const char *path, hostkeys_foreach_fn *callback, void *ctx,
  665. const char *host, const char *ip, u_int options)
  666. {
  667. FILE *f;
  668. char *line = NULL, ktype[128];
  669. u_long linenum = 0;
  670. char *cp, *cp2;
  671. u_int kbits;
  672. int hashed;
  673. int s, r = 0;
  674. struct hostkey_foreach_line lineinfo;
  675. size_t linesize = 0, l;
  676. memset(&lineinfo, 0, sizeof(lineinfo));
  677. if (host == NULL && (options & HKF_WANT_MATCH) != 0)
  678. return SSH_ERR_INVALID_ARGUMENT;
  679. if ((f = fopen(path, "r")) == NULL)
  680. return SSH_ERR_SYSTEM_ERROR;
  681. debug3("%s: reading file \"%s\"", __func__, path);
  682. while (getline(&line, &linesize, f) != -1) {
  683. linenum++;
  684. line[strcspn(line, "\n")] = '\0';
  685. free(lineinfo.line);
  686. sshkey_free(lineinfo.key);
  687. memset(&lineinfo, 0, sizeof(lineinfo));
  688. lineinfo.path = path;
  689. lineinfo.linenum = linenum;
  690. lineinfo.line = xstrdup(line);
  691. lineinfo.marker = MRK_NONE;
  692. lineinfo.status = HKF_STATUS_OK;
  693. lineinfo.keytype = KEY_UNSPEC;
  694. /* Skip any leading whitespace, comments and empty lines. */
  695. for (cp = line; *cp == ' ' || *cp == '\t'; cp++)
  696. ;
  697. if (!*cp || *cp == '#' || *cp == '\n') {
  698. if ((options & HKF_WANT_MATCH) == 0) {
  699. lineinfo.status = HKF_STATUS_COMMENT;
  700. if ((r = callback(&lineinfo, ctx)) != 0)
  701. break;
  702. }
  703. continue;
  704. }
  705. if ((lineinfo.marker = check_markers(&cp)) == MRK_ERROR) {
  706. verbose("%s: invalid marker at %s:%lu",
  707. __func__, path, linenum);
  708. if ((options & HKF_WANT_MATCH) == 0)
  709. goto bad;
  710. continue;
  711. }
  712. /* Find the end of the host name portion. */
  713. for (cp2 = cp; *cp2 && *cp2 != ' ' && *cp2 != '\t'; cp2++)
  714. ;
  715. lineinfo.hosts = cp;
  716. *cp2++ = '\0';
  717. /* Check if the host name matches. */
  718. if (host != NULL) {
  719. if ((s = match_maybe_hashed(host, lineinfo.hosts,
  720. &hashed)) == -1) {
  721. debug2("%s: %s:%ld: bad host hash \"%.32s\"",
  722. __func__, path, linenum, lineinfo.hosts);
  723. goto bad;
  724. }
  725. if (s == 1) {
  726. lineinfo.status = HKF_STATUS_MATCHED;
  727. lineinfo.match |= HKF_MATCH_HOST |
  728. (hashed ? HKF_MATCH_HOST_HASHED : 0);
  729. }
  730. /* Try matching IP address if supplied */
  731. if (ip != NULL) {
  732. if ((s = match_maybe_hashed(ip, lineinfo.hosts,
  733. &hashed)) == -1) {
  734. debug2("%s: %s:%ld: bad ip hash "
  735. "\"%.32s\"", __func__, path,
  736. linenum, lineinfo.hosts);
  737. goto bad;
  738. }
  739. if (s == 1) {
  740. lineinfo.status = HKF_STATUS_MATCHED;
  741. lineinfo.match |= HKF_MATCH_IP |
  742. (hashed ? HKF_MATCH_IP_HASHED : 0);
  743. }
  744. }
  745. /*
  746. * Skip this line if host matching requested and
  747. * neither host nor address matched.
  748. */
  749. if ((options & HKF_WANT_MATCH) != 0 &&
  750. lineinfo.status != HKF_STATUS_MATCHED)
  751. continue;
  752. }
  753. /* Got a match. Skip host name and any following whitespace */
  754. for (; *cp2 == ' ' || *cp2 == '\t'; cp2++)
  755. ;
  756. if (*cp2 == '\0' || *cp2 == '#') {
  757. debug2("%s:%ld: truncated before key type",
  758. path, linenum);
  759. goto bad;
  760. }
  761. lineinfo.rawkey = cp = cp2;
  762. if ((options & HKF_WANT_PARSE_KEY) != 0) {
  763. /*
  764. * Extract the key from the line. This will skip
  765. * any leading whitespace. Ignore badly formatted
  766. * lines.
  767. */
  768. if ((lineinfo.key = sshkey_new(KEY_UNSPEC)) == NULL) {
  769. error("%s: sshkey_new failed", __func__);
  770. r = SSH_ERR_ALLOC_FAIL;
  771. break;
  772. }
  773. if (!hostfile_read_key(&cp, &kbits, lineinfo.key)) {
  774. goto bad;
  775. }
  776. lineinfo.keytype = lineinfo.key->type;
  777. lineinfo.comment = cp;
  778. } else {
  779. /* Extract and parse key type */
  780. l = strcspn(lineinfo.rawkey, " \t");
  781. if (l <= 1 || l >= sizeof(ktype) ||
  782. lineinfo.rawkey[l] == '\0')
  783. goto bad;
  784. memcpy(ktype, lineinfo.rawkey, l);
  785. ktype[l] = '\0';
  786. lineinfo.keytype = sshkey_type_from_name(ktype);
  787. /*
  788. * Assume legacy RSA1 if the first component is a short
  789. * decimal number.
  790. */
  791. if (lineinfo.keytype == KEY_UNSPEC && l < 8 &&
  792. strspn(ktype, "0123456789") == l)
  793. goto bad;
  794. /*
  795. * Check that something other than whitespace follows
  796. * the key type. This won't catch all corruption, but
  797. * it does catch trivial truncation.
  798. */
  799. cp2 += l; /* Skip past key type */
  800. for (; *cp2 == ' ' || *cp2 == '\t'; cp2++)
  801. ;
  802. if (*cp2 == '\0' || *cp2 == '#') {
  803. debug2("%s:%ld: truncated after key type",
  804. path, linenum);
  805. lineinfo.keytype = KEY_UNSPEC;
  806. }
  807. if (lineinfo.keytype == KEY_UNSPEC) {
  808. bad:
  809. sshkey_free(lineinfo.key);
  810. lineinfo.key = NULL;
  811. lineinfo.status = HKF_STATUS_INVALID;
  812. if ((r = callback(&lineinfo, ctx)) != 0)
  813. break;
  814. continue;
  815. }
  816. }
  817. if ((r = callback(&lineinfo, ctx)) != 0)
  818. break;
  819. }
  820. sshkey_free(lineinfo.key);
  821. free(lineinfo.line);
  822. free(line);
  823. fclose(f);
  824. return r;
  825. }