openssh/hostfile.c

940 lines
24 KiB
C
Raw Normal View History

/* $OpenBSD: hostfile.c,v 1.87 2020/12/20 23:36:51 djm Exp $ */
1999-10-27 03:42:43 +00:00
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
* All rights reserved
- (djm) Merge OpenBSD changes: - markus@cvs.openbsd.org 2000/09/05 02:59:57 [session.c] print hostname (not hushlogin) - markus@cvs.openbsd.org 2000/09/05 13:18:48 [authfile.c ssh-add.c] enable ssh-add -d for DSA keys - markus@cvs.openbsd.org 2000/09/05 13:20:49 [sftp-server.c] cleanup - markus@cvs.openbsd.org 2000/09/06 03:46:41 [authfile.h] prototype - deraadt@cvs.openbsd.org 2000/09/07 14:27:56 [ALL] cleanup copyright notices on all files. I have attempted to be accurate with the details. everything is now under Tatu's licence (which I copied from his readme), and/or the core-sdi bsd-ish thing for deattack, or various openbsd developers under a 2-term bsd licence. We're not changing any rules, just being accurate. - markus@cvs.openbsd.org 2000/09/07 14:40:30 [channels.c channels.h clientloop.c serverloop.c ssh.c] cleanup window and packet sizes for ssh2 flow control; ok niels - markus@cvs.openbsd.org 2000/09/07 14:53:00 [scp.c] typo - markus@cvs.openbsd.org 2000/09/07 15:13:37 [auth-options.c auth-options.h auth-rh-rsa.c auth-rsa.c auth.c] [authfile.h canohost.c channels.h compat.c hostfile.h log.c match.h] [pty.c readconf.c] some more Copyright fixes - markus@cvs.openbsd.org 2000/09/08 03:02:51 [README.openssh2] bye bye - deraadt@cvs.openbsd.org 2000/09/11 18:38:33 [LICENCE cipher.c] a few more comments about it being ARC4 not RC4 - markus@cvs.openbsd.org 2000/09/12 14:53:11 [log-client.c log-server.c log.c ssh.1 ssh.c ssh.h sshd.8 sshd.c] multiple debug levels - markus@cvs.openbsd.org 2000/09/14 14:25:15 [clientloop.c] typo - deraadt@cvs.openbsd.org 2000/09/15 01:13:51 [ssh-agent.c] check return value for setenv(3) for failure, and deal appropriately
2000-09-16 02:29:08 +00:00
* Functions for manipulating the known hosts files.
*
- (djm) Merge OpenBSD changes: - markus@cvs.openbsd.org 2000/09/05 02:59:57 [session.c] print hostname (not hushlogin) - markus@cvs.openbsd.org 2000/09/05 13:18:48 [authfile.c ssh-add.c] enable ssh-add -d for DSA keys - markus@cvs.openbsd.org 2000/09/05 13:20:49 [sftp-server.c] cleanup - markus@cvs.openbsd.org 2000/09/06 03:46:41 [authfile.h] prototype - deraadt@cvs.openbsd.org 2000/09/07 14:27:56 [ALL] cleanup copyright notices on all files. I have attempted to be accurate with the details. everything is now under Tatu's licence (which I copied from his readme), and/or the core-sdi bsd-ish thing for deattack, or various openbsd developers under a 2-term bsd licence. We're not changing any rules, just being accurate. - markus@cvs.openbsd.org 2000/09/07 14:40:30 [channels.c channels.h clientloop.c serverloop.c ssh.c] cleanup window and packet sizes for ssh2 flow control; ok niels - markus@cvs.openbsd.org 2000/09/07 14:53:00 [scp.c] typo - markus@cvs.openbsd.org 2000/09/07 15:13:37 [auth-options.c auth-options.h auth-rh-rsa.c auth-rsa.c auth.c] [authfile.h canohost.c channels.h compat.c hostfile.h log.c match.h] [pty.c readconf.c] some more Copyright fixes - markus@cvs.openbsd.org 2000/09/08 03:02:51 [README.openssh2] bye bye - deraadt@cvs.openbsd.org 2000/09/11 18:38:33 [LICENCE cipher.c] a few more comments about it being ARC4 not RC4 - markus@cvs.openbsd.org 2000/09/12 14:53:11 [log-client.c log-server.c log.c ssh.1 ssh.c ssh.h sshd.8 sshd.c] multiple debug levels - markus@cvs.openbsd.org 2000/09/14 14:25:15 [clientloop.c] typo - deraadt@cvs.openbsd.org 2000/09/15 01:13:51 [ssh-agent.c] check return value for setenv(3) for failure, and deal appropriately
2000-09-16 02:29:08 +00:00
* As far as I am concerned, the code I have written for this software
* can be used freely for any purpose. Any derived versions of this
* software must be clearly marked as such, and if the derived work is
* incompatible with the protocol description in the RFC file, it must be
* called by a name other than "ssh" or "Secure Shell".
*
*
* Copyright (c) 1999, 2000 Markus Friedl. All rights reserved.
- (djm) Merge OpenBSD changes: - markus@cvs.openbsd.org 2000/09/05 02:59:57 [session.c] print hostname (not hushlogin) - markus@cvs.openbsd.org 2000/09/05 13:18:48 [authfile.c ssh-add.c] enable ssh-add -d for DSA keys - markus@cvs.openbsd.org 2000/09/05 13:20:49 [sftp-server.c] cleanup - markus@cvs.openbsd.org 2000/09/06 03:46:41 [authfile.h] prototype - deraadt@cvs.openbsd.org 2000/09/07 14:27:56 [ALL] cleanup copyright notices on all files. I have attempted to be accurate with the details. everything is now under Tatu's licence (which I copied from his readme), and/or the core-sdi bsd-ish thing for deattack, or various openbsd developers under a 2-term bsd licence. We're not changing any rules, just being accurate. - markus@cvs.openbsd.org 2000/09/07 14:40:30 [channels.c channels.h clientloop.c serverloop.c ssh.c] cleanup window and packet sizes for ssh2 flow control; ok niels - markus@cvs.openbsd.org 2000/09/07 14:53:00 [scp.c] typo - markus@cvs.openbsd.org 2000/09/07 15:13:37 [auth-options.c auth-options.h auth-rh-rsa.c auth-rsa.c auth.c] [authfile.h canohost.c channels.h compat.c hostfile.h log.c match.h] [pty.c readconf.c] some more Copyright fixes - markus@cvs.openbsd.org 2000/09/08 03:02:51 [README.openssh2] bye bye - deraadt@cvs.openbsd.org 2000/09/11 18:38:33 [LICENCE cipher.c] a few more comments about it being ARC4 not RC4 - markus@cvs.openbsd.org 2000/09/12 14:53:11 [log-client.c log-server.c log.c ssh.1 ssh.c ssh.h sshd.8 sshd.c] multiple debug levels - markus@cvs.openbsd.org 2000/09/14 14:25:15 [clientloop.c] typo - deraadt@cvs.openbsd.org 2000/09/15 01:13:51 [ssh-agent.c] check return value for setenv(3) for failure, and deal appropriately
2000-09-16 02:29:08 +00:00
* Copyright (c) 1999 Niels Provos. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
1999-10-27 03:42:43 +00:00
#include "includes.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <errno.h>
#include <resolv.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
- deraadt@cvs.openbsd.org 2006/08/03 03:34:42 [OVERVIEW atomicio.c atomicio.h auth-bsdauth.c auth-chall.c auth-krb5.c] [auth-options.c auth-options.h auth-passwd.c auth-rh-rsa.c auth-rhosts.c] [auth-rsa.c auth-skey.c auth.c auth.h auth1.c auth2-chall.c auth2-gss.c] [auth2-hostbased.c auth2-kbdint.c auth2-none.c auth2-passwd.c ] [auth2-pubkey.c auth2.c authfd.c authfd.h authfile.c bufaux.c bufbn.c] [buffer.c buffer.h canohost.c channels.c channels.h cipher-3des1.c] [cipher-bf1.c cipher-ctr.c cipher.c cleanup.c clientloop.c compat.c] [compress.c deattack.c dh.c dispatch.c dns.c dns.h fatal.c groupaccess.c] [groupaccess.h gss-genr.c gss-serv-krb5.c gss-serv.c hostfile.c kex.c] [kex.h kexdh.c kexdhc.c kexdhs.c kexgex.c kexgexc.c kexgexs.c key.c] [key.h log.c log.h mac.c match.c md-sha256.c misc.c misc.h moduli.c] [monitor.c monitor_fdpass.c monitor_mm.c monitor_mm.h monitor_wrap.c] [monitor_wrap.h msg.c nchan.c packet.c progressmeter.c readconf.c] [readconf.h readpass.c rsa.c scard.c scard.h scp.c servconf.c servconf.h] [serverloop.c session.c session.h sftp-client.c sftp-common.c] [sftp-common.h sftp-glob.c sftp-server.c sftp.c ssh-add.c ssh-agent.c] [ssh-dss.c ssh-gss.h ssh-keygen.c ssh-keyscan.c ssh-keysign.c ssh-rsa.c] [ssh.c ssh.h sshconnect.c sshconnect.h sshconnect1.c sshconnect2.c] [sshd.c sshlogin.c sshlogin.h sshpty.c sshpty.h sshtty.c ttymodes.c] [uidswap.c uidswap.h uuencode.c uuencode.h xmalloc.c xmalloc.h] [loginrec.c loginrec.h openbsd-compat/port-aix.c openbsd-compat/port-tun.h] almost entirely get rid of the culture of ".h files that include .h files" ok djm, sort of ok stevesk makes the pain stop in one easy step NB. portable commit contains everything *except* removing includes.h, as that will take a fair bit more work as we move headers that are required for portability workarounds to defines.h. (also, this step wasn't "easy")
2006-08-05 02:39:39 +00:00
#include "xmalloc.h"
#include "match.h"
#include "sshkey.h"
#include "hostfile.h"
Hopefully things did not get mixed around too much. It compiles under Linux and works. So that is at least a good sign. =) 20010122 - (bal) OpenBSD Resync - markus@cvs.openbsd.org 2001/01/19 12:45:26 GMT 2001 by markus [servconf.c ssh.h sshd.c] only auth-chall.c needs #ifdef SKEY - markus@cvs.openbsd.org 2001/01/19 15:55:10 GMT 2001 by markus [auth-krb4.c auth-options.c auth-rh-rsa.c auth-rhosts.c auth-rsa.c auth1.c auth2.c channels.c clientloop.c dh.c dispatch.c nchan.c packet.c pathname.h readconf.c scp.c servconf.c serverloop.c session.c ssh-add.c ssh-keygen.c ssh-keyscan.c ssh.c ssh.h ssh1.h sshconnect1.c sshd.c ttymodes.c] move ssh1 definitions to ssh1.h, pathnames to pathnames.h - markus@cvs.openbsd.org 2001/01/19 16:48:14 [sshd.8] fix typo; from stevesk@ - markus@cvs.openbsd.org 2001/01/19 16:50:58 [ssh-dss.c] clear and free digest, make consistent with other code (use dlen); from stevesk@ - markus@cvs.openbsd.org 2001/01/20 15:55:20 GMT 2001 by markus [auth-options.c auth-options.h auth-rsa.c auth2.c] pass the filename to auth_parse_options() - markus@cvs.openbsd.org 2001/01/20 17:59:40 GMT 2001 [readconf.c] fix SIGSEGV from -o ""; problem noted by jehsom@togetherweb.com - stevesk@cvs.openbsd.org 2001/01/20 18:20:29 [sshconnect2.c] dh_new_group() does not return NULL. ok markus@ - markus@cvs.openbsd.org 2001/01/20 21:33:42 [ssh-add.c] do not loop forever if askpass does not exist; from andrew@pimlott.ne.mediaone.net - djm@cvs.openbsd.org 2001/01/20 23:00:56 [servconf.c] Check for NULL return from strdelim; ok markus - djm@cvs.openbsd.org 2001/01/20 23:02:07 [readconf.c] KNF; ok markus - jakob@cvs.openbsd.org 2001/01/21 9:00:33 [ssh-keygen.1] remove -R flag; ok markus@ - markus@cvs.openbsd.org 2001/01/21 19:05:40 [atomicio.c automicio.h auth-chall.c auth-krb4.c auth-options.c auth-options.h auth-passwd.c auth-rh-rsa.c auth-rhosts.c auth-rsa.c auth.c auth.h auth1.c auth2-chall.c auth2.c authfd.c authfile.c bufaux.c bufaux.h buffer.c canahost.c canahost.h channels.c cipher.c cli.c clientloop.c clientloop.h compat.c compress.c deattack.c dh.c dispatch.c groupaccess.c hmac.c hostfile.c kex.c key.c key.h log-client.c log-server.c log.c log.h login.c login.h match.c misc.c misc.h nchan.c packet.c pty.c radix.h readconf.c readpass.c readpass.h rsa.c scp.c servconf.c serverloop.c serverloop.h session.c sftp-server.c ssh-add.c ssh-agent.c ssh-dss.c ssh-keygen.c ssh-keyscan.c ssh-rsa.c ssh.c ssh.h sshconnect.c sshconnect.h sshconnect1.c sshconnect2.c sshd.c tildexpand.c tildexpand.h ttysmodes.c uidswap.c xmalloc.c] split ssh.h and try to cleanup the #include mess. remove unnecessary #includes. rename util.[ch] -> misc.[ch] - (bal) renamed 'PIDDIR' to '_PATH_SSH_PIDDIR' to match OpenBSD tree - (bal) Moved #ifdef KRB4 in auth-krb4.c above the #include to resolve conflict when compiling for non-kerb install - (bal) removed the #ifdef SKEY in auth1.c to match Markus' changes on 1/19.
2001-01-22 05:34:40 +00:00
#include "log.h"
#include "misc.h"
#include "pathnames.h"
#include "ssherr.h"
#include "digest.h"
#include "hmac.h"
struct hostkeys {
struct hostkey_entry *entries;
u_int num_entries;
};
/* XXX hmac is too easy to dictionary attack; use bcrypt? */
static int
extract_salt(const char *s, u_int l, u_char *salt, size_t salt_len)
{
char *p, *b64salt;
u_int b64len;
int ret;
if (l < sizeof(HASH_MAGIC) - 1) {
debug2("extract_salt: string too short");
return (-1);
}
if (strncmp(s, HASH_MAGIC, sizeof(HASH_MAGIC) - 1) != 0) {
debug2("extract_salt: invalid magic identifier");
return (-1);
}
s += sizeof(HASH_MAGIC) - 1;
l -= sizeof(HASH_MAGIC) - 1;
if ((p = memchr(s, HASH_DELIM, l)) == NULL) {
debug2("extract_salt: missing salt termination character");
return (-1);
}
b64len = p - s;
/* Sanity check */
if (b64len == 0 || b64len > 1024) {
debug2("extract_salt: bad encoded salt length %u", b64len);
return (-1);
}
b64salt = xmalloc(1 + b64len);
memcpy(b64salt, s, b64len);
b64salt[b64len] = '\0';
ret = __b64_pton(b64salt, salt, salt_len);
free(b64salt);
if (ret == -1) {
debug2("extract_salt: salt decode error");
return (-1);
}
if (ret != (int)ssh_hmac_bytes(SSH_DIGEST_SHA1)) {
debug2("extract_salt: expected salt len %zd, got %d",
ssh_hmac_bytes(SSH_DIGEST_SHA1), ret);
return (-1);
}
return (0);
}
char *
host_hash(const char *host, const char *name_from_hostfile, u_int src_len)
{
struct ssh_hmac_ctx *ctx;
u_char salt[256], result[256];
char uu_salt[512], uu_result[512];
static char encoded[1024];
u_int len;
len = ssh_digest_bytes(SSH_DIGEST_SHA1);
if (name_from_hostfile == NULL) {
/* Create new salt */
arc4random_buf(salt, len);
} else {
/* Extract salt from known host entry */
if (extract_salt(name_from_hostfile, src_len, salt,
sizeof(salt)) == -1)
return (NULL);
}
if ((ctx = ssh_hmac_start(SSH_DIGEST_SHA1)) == NULL ||
ssh_hmac_init(ctx, salt, len) < 0 ||
ssh_hmac_update(ctx, host, strlen(host)) < 0 ||
ssh_hmac_final(ctx, result, sizeof(result)))
fatal_f("ssh_hmac failed");
ssh_hmac_free(ctx);
if (__b64_ntop(salt, len, uu_salt, sizeof(uu_salt)) == -1 ||
__b64_ntop(result, len, uu_result, sizeof(uu_result)) == -1)
fatal_f("__b64_ntop failed");
snprintf(encoded, sizeof(encoded), "%s%s%c%s", HASH_MAGIC, uu_salt,
HASH_DELIM, uu_result);
return (encoded);
}
1999-10-27 03:42:43 +00:00
/*
* Parses an RSA (number of bits, e, n) or DSA key from a string. Moves the
* pointer over the key. Skips any whitespace at the beginning and at end.
*/
1999-10-27 03:42:43 +00:00
int
hostfile_read_key(char **cpp, u_int *bitsp, struct sshkey *ret)
1999-10-27 03:42:43 +00:00
{
char *cp;
/* Skip leading whitespace. */
for (cp = *cpp; *cp == ' ' || *cp == '\t'; cp++)
;
if (sshkey_read(ret, &cp) != 0)
return 0;
/* Skip trailing whitespace. */
for (; *cp == ' ' || *cp == '\t'; cp++)
;
/* Return results. */
*cpp = cp;
if (bitsp != NULL)
*bitsp = sshkey_size(ret);
return 1;
1999-10-27 03:42:43 +00:00
}
static HostkeyMarker
check_markers(char **cpp)
{
char marker[32], *sp, *cp = *cpp;
int ret = MRK_NONE;
while (*cp == '@') {
/* Only one marker is allowed */
if (ret != MRK_NONE)
return MRK_ERROR;
/* Markers are terminated by whitespace */
if ((sp = strchr(cp, ' ')) == NULL &&
(sp = strchr(cp, '\t')) == NULL)
return MRK_ERROR;
/* Extract marker for comparison */
if (sp <= cp + 1 || sp >= cp + sizeof(marker))
return MRK_ERROR;
memcpy(marker, cp, sp - cp);
marker[sp - cp] = '\0';
if (strcmp(marker, CA_MARKER) == 0)
ret = MRK_CA;
else if (strcmp(marker, REVOKE_MARKER) == 0)
ret = MRK_REVOKE;
else
return MRK_ERROR;
/* Skip past marker and any whitespace that follows it */
cp = sp;
for (; *cp == ' ' || *cp == '\t'; cp++)
;
}
*cpp = cp;
return ret;
}
struct hostkeys *
init_hostkeys(void)
{
struct hostkeys *ret = xcalloc(1, sizeof(*ret));
1999-10-27 03:42:43 +00:00
ret->entries = NULL;
return ret;
}
struct load_callback_ctx {
const char *host;
u_long num_loaded;
struct hostkeys *hostkeys;
};
static int
record_hostkey(struct hostkey_foreach_line *l, void *_ctx)
1999-10-27 03:42:43 +00:00
{
struct load_callback_ctx *ctx = (struct load_callback_ctx *)_ctx;
struct hostkeys *hostkeys = ctx->hostkeys;
struct hostkey_entry *tmp;
if (l->status == HKF_STATUS_INVALID) {
/* XXX make this verbose() in the future */
debug("%s:%ld: parse error in hostkeys file",
l->path, l->linenum);
return 0;
}
debug3_f("found %skey type %s in file %s:%lu",
l->marker == MRK_NONE ? "" :
(l->marker == MRK_CA ? "ca " : "revoked "),
sshkey_type(l->key), l->path, l->linenum);
if ((tmp = recallocarray(hostkeys->entries, hostkeys->num_entries,
hostkeys->num_entries + 1, sizeof(*hostkeys->entries))) == NULL)
return SSH_ERR_ALLOC_FAIL;
hostkeys->entries = tmp;
hostkeys->entries[hostkeys->num_entries].host = xstrdup(ctx->host);
hostkeys->entries[hostkeys->num_entries].file = xstrdup(l->path);
hostkeys->entries[hostkeys->num_entries].line = l->linenum;
hostkeys->entries[hostkeys->num_entries].key = l->key;
l->key = NULL; /* steal it */
hostkeys->entries[hostkeys->num_entries].marker = l->marker;
hostkeys->entries[hostkeys->num_entries].note = l->note;
hostkeys->num_entries++;
ctx->num_loaded++;
return 0;
}
void
load_hostkeys_file(struct hostkeys *hostkeys, const char *host,
const char *path, FILE *f, u_int note)
{
int r;
struct load_callback_ctx ctx;
ctx.host = host;
ctx.num_loaded = 0;
ctx.hostkeys = hostkeys;
if ((r = hostkeys_foreach_file(path, f, record_hostkey, &ctx, host,
NULL, HKF_WANT_MATCH|HKF_WANT_PARSE_KEY, note)) != 0) {
if (r != SSH_ERR_SYSTEM_ERROR && errno != ENOENT)
debug_fr(r, "hostkeys_foreach failed for %s", path);
}
if (ctx.num_loaded != 0)
debug3_f("loaded %lu keys from %s", ctx.num_loaded, host);
2014-10-20 03:43:01 +00:00
}
void
load_hostkeys(struct hostkeys *hostkeys, const char *host, const char *path,
u_int note)
{
FILE *f;
if ((f = fopen(path, "r")) == NULL) {
debug_f("fopen %s: %s", path, strerror(errno));
return;
}
load_hostkeys_file(hostkeys, host, path, f, note);
fclose(f);
}
void
free_hostkeys(struct hostkeys *hostkeys)
{
u_int i;
for (i = 0; i < hostkeys->num_entries; i++) {
free(hostkeys->entries[i].host);
free(hostkeys->entries[i].file);
sshkey_free(hostkeys->entries[i].key);
explicit_bzero(hostkeys->entries + i, sizeof(*hostkeys->entries));
}
free(hostkeys->entries);
freezero(hostkeys, sizeof(*hostkeys));
}
static int
check_key_not_revoked(struct hostkeys *hostkeys, struct sshkey *k)
{
int is_cert = sshkey_is_cert(k);
u_int i;
for (i = 0; i < hostkeys->num_entries; i++) {
if (hostkeys->entries[i].marker != MRK_REVOKE)
continue;
if (sshkey_equal_public(k, hostkeys->entries[i].key))
return -1;
if (is_cert && k != NULL &&
sshkey_equal_public(k->cert->signature_key,
hostkeys->entries[i].key))
return -1;
}
return 0;
}
/*
* Match keys against a specified key, or look one up by key type.
*
* If looking for a keytype (key == NULL) and one is found then return
* HOST_FOUND, otherwise HOST_NEW.
*
* If looking for a key (key != NULL):
* 1. If the key is a cert and a matching CA is found, return HOST_OK
* 2. If the key is not a cert and a matching key is found, return HOST_OK
* 3. If no key matches but a key with a different type is found, then
* return HOST_CHANGED
* 4. If no matching keys are found, then return HOST_NEW.
*
* Finally, check any found key is not revoked.
*/
static HostStatus
check_hostkeys_by_key_or_type(struct hostkeys *hostkeys,
struct sshkey *k, int keytype, int nid, const struct hostkey_entry **found)
{
u_int i;
HostStatus end_return = HOST_NEW;
int want_cert = sshkey_is_cert(k);
HostkeyMarker want_marker = want_cert ? MRK_CA : MRK_NONE;
if (found != NULL)
*found = NULL;
for (i = 0; i < hostkeys->num_entries; i++) {
if (hostkeys->entries[i].marker != want_marker)
continue;
if (k == NULL) {
if (hostkeys->entries[i].key->type != keytype)
continue;
if (nid != -1 &&
sshkey_type_plain(keytype) == KEY_ECDSA &&
hostkeys->entries[i].key->ecdsa_nid != nid)
continue;
end_return = HOST_FOUND;
if (found != NULL)
*found = hostkeys->entries + i;
k = hostkeys->entries[i].key;
break;
}
if (want_cert) {
if (sshkey_equal_public(k->cert->signature_key,
hostkeys->entries[i].key)) {
/* A matching CA exists */
end_return = HOST_OK;
if (found != NULL)
*found = hostkeys->entries + i;
break;
}
} else {
if (sshkey_equal(k, hostkeys->entries[i].key)) {
end_return = HOST_OK;
if (found != NULL)
*found = hostkeys->entries + i;
break;
}
/* A non-maching key exists */
end_return = HOST_CHANGED;
if (found != NULL)
*found = hostkeys->entries + i;
}
1999-10-27 03:42:43 +00:00
}
if (check_key_not_revoked(hostkeys, k) != 0) {
end_return = HOST_REVOKED;
if (found != NULL)
*found = NULL;
}
return end_return;
1999-10-27 03:42:43 +00:00
}
2014-10-20 03:43:01 +00:00
HostStatus
check_key_in_hostkeys(struct hostkeys *hostkeys, struct sshkey *key,
const struct hostkey_entry **found)
{
if (key == NULL)
fatal("no key to look up");
return check_hostkeys_by_key_or_type(hostkeys, key, 0, -1, found);
}
int
lookup_key_in_hostkeys_by_type(struct hostkeys *hostkeys, int keytype, int nid,
const struct hostkey_entry **found)
{
return (check_hostkeys_by_key_or_type(hostkeys, NULL, keytype, nid,
found) == HOST_FOUND);
}
int
lookup_marker_in_hostkeys(struct hostkeys *hostkeys, int want_marker)
{
u_int i;
for (i = 0; i < hostkeys->num_entries; i++) {
if (hostkeys->entries[i].marker == (HostkeyMarker)want_marker)
return 1;
}
return 0;
}
static int
write_host_entry(FILE *f, const char *host, const char *ip,
const struct sshkey *key, int store_hash)
{
int r, success = 0;
char *hashed_host = NULL, *lhost;
lhost = xstrdup(host);
lowercase(lhost);
if (store_hash) {
if ((hashed_host = host_hash(lhost, NULL, 0)) == NULL) {
error_f("host_hash failed");
free(lhost);
return 0;
}
fprintf(f, "%s ", hashed_host);
} else if (ip != NULL)
fprintf(f, "%s,%s ", lhost, ip);
else {
fprintf(f, "%s ", lhost);
}
free(lhost);
if ((r = sshkey_write(key, f)) == 0)
success = 1;
else
error_fr(r, "sshkey_write");
fputc('\n', f);
/* If hashing is enabled, the IP address needs to go on its own line */
if (success && store_hash && ip != NULL)
success = write_host_entry(f, ip, NULL, key, 1);
return success;
}
/*
* Create user ~/.ssh directory if it doesn't exist and we want to write to it.
* If notify is set, a message will be emitted if the directory is created.
*/
void
hostfile_create_user_ssh_dir(const char *filename, int notify)
{
char *dotsshdir = NULL, *p;
size_t len;
struct stat st;
if ((p = strrchr(filename, '/')) == NULL)
return;
len = p - filename;
dotsshdir = tilde_expand_filename("~/" _PATH_SSH_USER_DIR, getuid());
if (strlen(dotsshdir) > len || strncmp(filename, dotsshdir, len) != 0)
goto out; /* not ~/.ssh prefixed */
if (stat(dotsshdir, &st) == 0)
goto out; /* dir already exists */
else if (errno != ENOENT)
error("Could not stat %s: %s", dotsshdir, strerror(errno));
else {
#ifdef WITH_SELINUX
ssh_selinux_setfscreatecon(dotsshdir);
#endif
if (mkdir(dotsshdir, 0700) == -1)
error("Could not create directory '%.200s' (%s).",
dotsshdir, strerror(errno));
else if (notify)
logit("Created directory '%s'.", dotsshdir);
#ifdef WITH_SELINUX
ssh_selinux_setfscreatecon(NULL);
#endif
}
out:
free(dotsshdir);
}
/*
* Appends an entry to the host file. Returns false if the entry could not
* be appended.
*/
1999-10-27 03:42:43 +00:00
int
add_host_to_hostfile(const char *filename, const char *host,
const struct sshkey *key, int store_hash)
1999-10-27 03:42:43 +00:00
{
FILE *f;
int success;
if (key == NULL)
return 1; /* XXX ? */
hostfile_create_user_ssh_dir(filename, 0);
f = fopen(filename, "a");
if (!f)
return 0;
success = write_host_entry(f, host, NULL, key, store_hash);
fclose(f);
return success;
}
struct host_delete_ctx {
FILE *out;
int quiet;
const char *host, *ip;
u_int *match_keys; /* mask of HKF_MATCH_* for this key */
struct sshkey * const *keys;
size_t nkeys;
int modified;
};
static int
host_delete(struct hostkey_foreach_line *l, void *_ctx)
{
struct host_delete_ctx *ctx = (struct host_delete_ctx *)_ctx;
int loglevel = ctx->quiet ? SYSLOG_LEVEL_DEBUG1 : SYSLOG_LEVEL_VERBOSE;
size_t i;
/* Don't remove CA and revocation lines */
if (l->status == HKF_STATUS_MATCHED && l->marker == MRK_NONE) {
/*
* If this line contains one of the keys that we will be
* adding later, then don't change it and mark the key for
* skipping.
*/
for (i = 0; i < ctx->nkeys; i++) {
if (!sshkey_equal(ctx->keys[i], l->key))
continue;
ctx->match_keys[i] |= l->match;
fprintf(ctx->out, "%s\n", l->line);
debug3_f("%s key already at %s:%ld",
sshkey_type(l->key), l->path, l->linenum);
return 0;
}
/*
* Hostname matches and has no CA/revoke marker, delete it
* by *not* writing the line to ctx->out.
*/
do_log2(loglevel, "%s%s%s:%ld: Removed %s key for host %s",
ctx->quiet ? __func__ : "", ctx->quiet ? ": " : "",
l->path, l->linenum, sshkey_type(l->key), ctx->host);
ctx->modified = 1;
return 0;
}
/* Retain non-matching hosts and invalid lines when deleting */
if (l->status == HKF_STATUS_INVALID) {
do_log2(loglevel, "%s%s%s:%ld: invalid known_hosts entry",
ctx->quiet ? __func__ : "", ctx->quiet ? ": " : "",
l->path, l->linenum);
}
fprintf(ctx->out, "%s\n", l->line);
return 0;
}
int
hostfile_replace_entries(const char *filename, const char *host, const char *ip,
struct sshkey **keys, size_t nkeys, int store_hash, int quiet, int hash_alg)
{
int r, fd, oerrno = 0;
int loglevel = quiet ? SYSLOG_LEVEL_DEBUG1 : SYSLOG_LEVEL_VERBOSE;
struct host_delete_ctx ctx;
char *fp, *temp = NULL, *back = NULL;
const char *what;
mode_t omask;
size_t i;
u_int want;
2015-01-26 13:36:53 +00:00
omask = umask(077);
memset(&ctx, 0, sizeof(ctx));
ctx.host = host;
ctx.ip = ip;
ctx.quiet = quiet;
if ((ctx.match_keys = calloc(nkeys, sizeof(*ctx.match_keys))) == NULL)
return SSH_ERR_ALLOC_FAIL;
ctx.keys = keys;
ctx.nkeys = nkeys;
ctx.modified = 0;
/*
* Prepare temporary file for in-place deletion.
*/
if ((r = asprintf(&temp, "%s.XXXXXXXXXXX", filename)) == -1 ||
(r = asprintf(&back, "%s.old", filename)) == -1) {
r = SSH_ERR_ALLOC_FAIL;
goto fail;
}
if ((fd = mkstemp(temp)) == -1) {
oerrno = errno;
error_f("mkstemp: %s", strerror(oerrno));
r = SSH_ERR_SYSTEM_ERROR;
goto fail;
}
if ((ctx.out = fdopen(fd, "w")) == NULL) {
oerrno = errno;
close(fd);
error_f("fdopen: %s", strerror(oerrno));
r = SSH_ERR_SYSTEM_ERROR;
goto fail;
}
/* Remove stale/mismatching entries for the specified host */
if ((r = hostkeys_foreach(filename, host_delete, &ctx, host, ip,
HKF_WANT_PARSE_KEY, 0)) != 0) {
oerrno = errno;
error_fr(r, "hostkeys_foreach");
goto fail;
}
/* Re-add the requested keys */
want = HKF_MATCH_HOST | (ip == NULL ? 0 : HKF_MATCH_IP);
for (i = 0; i < nkeys; i++) {
if ((want & ctx.match_keys[i]) == want)
continue;
if ((fp = sshkey_fingerprint(keys[i], hash_alg,
SSH_FP_DEFAULT)) == NULL) {
r = SSH_ERR_ALLOC_FAIL;
goto fail;
}
/* write host/ip */
what = "";
if (ctx.match_keys[i] == 0) {
what = "Adding new key";
if (!write_host_entry(ctx.out, host, ip,
keys[i], store_hash)) {
r = SSH_ERR_INTERNAL_ERROR;
goto fail;
}
} else if ((want & ~ctx.match_keys[i]) == HKF_MATCH_HOST) {
what = "Fixing match (hostname)";
if (!write_host_entry(ctx.out, host, NULL,
keys[i], store_hash)) {
r = SSH_ERR_INTERNAL_ERROR;
goto fail;
}
} else if ((want & ~ctx.match_keys[i]) == HKF_MATCH_IP) {
what = "Fixing match (address)";
if (!write_host_entry(ctx.out, ip, NULL,
keys[i], store_hash)) {
r = SSH_ERR_INTERNAL_ERROR;
goto fail;
}
}
do_log2(loglevel, "%s%s%s for %s%s%s to %s: %s %s",
quiet ? __func__ : "", quiet ? ": " : "", what,
host, ip == NULL ? "" : ",", ip == NULL ? "" : ip, filename,
sshkey_ssh_name(keys[i]), fp);
free(fp);
ctx.modified = 1;
}
fclose(ctx.out);
ctx.out = NULL;
if (ctx.modified) {
/* Backup the original file and replace it with the temporary */
if (unlink(back) == -1 && errno != ENOENT) {
oerrno = errno;
error_f("unlink %.100s: %s", back, strerror(errno));
r = SSH_ERR_SYSTEM_ERROR;
goto fail;
}
if (link(filename, back) == -1) {
oerrno = errno;
error_f("link %.100s to %.100s: %s", filename,
back, strerror(errno));
r = SSH_ERR_SYSTEM_ERROR;
goto fail;
}
if (rename(temp, filename) == -1) {
oerrno = errno;
error_f("rename \"%s\" to \"%s\": %s", temp,
filename, strerror(errno));
r = SSH_ERR_SYSTEM_ERROR;
goto fail;
}
} else {
/* No changes made; just delete the temporary file */
if (unlink(temp) != 0)
error_f("unlink \"%s\": %s", temp, strerror(errno));
}
/* success */
r = 0;
fail:
if (temp != NULL && r != 0)
unlink(temp);
free(temp);
free(back);
if (ctx.out != NULL)
fclose(ctx.out);
free(ctx.match_keys);
2015-01-26 13:36:53 +00:00
umask(omask);
if (r == SSH_ERR_SYSTEM_ERROR)
errno = oerrno;
return r;
1999-10-27 03:42:43 +00:00
}
static int
match_maybe_hashed(const char *host, const char *names, int *was_hashed)
{
int hashed = *names == HASH_DELIM;
const char *hashed_host;
size_t nlen = strlen(names);
if (was_hashed != NULL)
*was_hashed = hashed;
if (hashed) {
if ((hashed_host = host_hash(host, names, nlen)) == NULL)
return -1;
return nlen == strlen(hashed_host) &&
strncmp(hashed_host, names, nlen) == 0;
}
return match_hostname(host, names) == 1;
}
int
hostkeys_foreach_file(const char *path, FILE *f, hostkeys_foreach_fn *callback,
void *ctx, const char *host, const char *ip, u_int options, u_int note)
{
char *line = NULL, ktype[128];
u_long linenum = 0;
char *cp, *cp2;
u_int kbits;
int hashed;
int s, r = 0;
struct hostkey_foreach_line lineinfo;
size_t linesize = 0, l;
memset(&lineinfo, 0, sizeof(lineinfo));
if (host == NULL && (options & HKF_WANT_MATCH) != 0)
return SSH_ERR_INVALID_ARGUMENT;
while (getline(&line, &linesize, f) != -1) {
linenum++;
line[strcspn(line, "\n")] = '\0';
free(lineinfo.line);
sshkey_free(lineinfo.key);
memset(&lineinfo, 0, sizeof(lineinfo));
lineinfo.path = path;
lineinfo.linenum = linenum;
lineinfo.line = xstrdup(line);
lineinfo.marker = MRK_NONE;
lineinfo.status = HKF_STATUS_OK;
lineinfo.keytype = KEY_UNSPEC;
lineinfo.note = note;
/* Skip any leading whitespace, comments and empty lines. */
for (cp = line; *cp == ' ' || *cp == '\t'; cp++)
;
if (!*cp || *cp == '#' || *cp == '\n') {
if ((options & HKF_WANT_MATCH) == 0) {
lineinfo.status = HKF_STATUS_COMMENT;
if ((r = callback(&lineinfo, ctx)) != 0)
break;
}
continue;
}
if ((lineinfo.marker = check_markers(&cp)) == MRK_ERROR) {
verbose_f("invalid marker at %s:%lu", path, linenum);
if ((options & HKF_WANT_MATCH) == 0)
goto bad;
continue;
}
/* Find the end of the host name portion. */
for (cp2 = cp; *cp2 && *cp2 != ' ' && *cp2 != '\t'; cp2++)
;
lineinfo.hosts = cp;
*cp2++ = '\0';
/* Check if the host name matches. */
if (host != NULL) {
if ((s = match_maybe_hashed(host, lineinfo.hosts,
&hashed)) == -1) {
debug2_f("%s:%ld: bad host hash \"%.32s\"",
path, linenum, lineinfo.hosts);
goto bad;
}
if (s == 1) {
lineinfo.status = HKF_STATUS_MATCHED;
lineinfo.match |= HKF_MATCH_HOST |
(hashed ? HKF_MATCH_HOST_HASHED : 0);
}
/* Try matching IP address if supplied */
if (ip != NULL) {
if ((s = match_maybe_hashed(ip, lineinfo.hosts,
&hashed)) == -1) {
debug2_f("%s:%ld: bad ip hash "
"\"%.32s\"", path, linenum,
lineinfo.hosts);
goto bad;
}
if (s == 1) {
lineinfo.status = HKF_STATUS_MATCHED;
lineinfo.match |= HKF_MATCH_IP |
(hashed ? HKF_MATCH_IP_HASHED : 0);
}
}
/*
* Skip this line if host matching requested and
* neither host nor address matched.
*/
if ((options & HKF_WANT_MATCH) != 0 &&
lineinfo.status != HKF_STATUS_MATCHED)
continue;
}
/* Got a match. Skip host name and any following whitespace */
for (; *cp2 == ' ' || *cp2 == '\t'; cp2++)
;
if (*cp2 == '\0' || *cp2 == '#') {
debug2("%s:%ld: truncated before key type",
path, linenum);
goto bad;
}
lineinfo.rawkey = cp = cp2;
if ((options & HKF_WANT_PARSE_KEY) != 0) {
/*
* Extract the key from the line. This will skip
* any leading whitespace. Ignore badly formatted
* lines.
*/
if ((lineinfo.key = sshkey_new(KEY_UNSPEC)) == NULL) {
error_f("sshkey_new failed");
r = SSH_ERR_ALLOC_FAIL;
break;
}
if (!hostfile_read_key(&cp, &kbits, lineinfo.key)) {
goto bad;
}
lineinfo.keytype = lineinfo.key->type;
lineinfo.comment = cp;
} else {
/* Extract and parse key type */
l = strcspn(lineinfo.rawkey, " \t");
if (l <= 1 || l >= sizeof(ktype) ||
lineinfo.rawkey[l] == '\0')
goto bad;
memcpy(ktype, lineinfo.rawkey, l);
ktype[l] = '\0';
lineinfo.keytype = sshkey_type_from_name(ktype);
/*
* Assume legacy RSA1 if the first component is a short
* decimal number.
*/
if (lineinfo.keytype == KEY_UNSPEC && l < 8 &&
strspn(ktype, "0123456789") == l)
goto bad;
/*
* Check that something other than whitespace follows
* the key type. This won't catch all corruption, but
* it does catch trivial truncation.
*/
cp2 += l; /* Skip past key type */
for (; *cp2 == ' ' || *cp2 == '\t'; cp2++)
;
if (*cp2 == '\0' || *cp2 == '#') {
debug2("%s:%ld: truncated after key type",
path, linenum);
lineinfo.keytype = KEY_UNSPEC;
}
if (lineinfo.keytype == KEY_UNSPEC) {
bad:
sshkey_free(lineinfo.key);
lineinfo.key = NULL;
lineinfo.status = HKF_STATUS_INVALID;
if ((r = callback(&lineinfo, ctx)) != 0)
break;
continue;
}
}
if ((r = callback(&lineinfo, ctx)) != 0)
break;
}
sshkey_free(lineinfo.key);
free(lineinfo.line);
free(line);
return r;
}
int
hostkeys_foreach(const char *path, hostkeys_foreach_fn *callback, void *ctx,
const char *host, const char *ip, u_int options, u_int note)
{
FILE *f;
int r, oerrno;
if ((f = fopen(path, "r")) == NULL)
return SSH_ERR_SYSTEM_ERROR;
debug3_f("reading file \"%s\"", path);
r = hostkeys_foreach_file(path, f, callback, ctx, host, ip,
options, note);
oerrno = errno;
fclose(f);
errno = oerrno;
return r;
}