#include "includes.h"
#include <sys/types.h>
#include <netinet/in.h>
#include <openssl/hmac.h>
#include <openssl/sha.h>
#include <resolv.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "xmalloc.h"
#include "match.h"
#include "key.h"
#include "hostfile.h"
#include "log.h"
static int
extract_salt(const char *s, u_int l, 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;
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);
xfree(b64salt);
if (ret == -1) {
debug2("extract_salt: salt decode error");
return (-1);
}
if (ret != SHA_DIGEST_LENGTH) {
debug2("extract_salt: expected salt len %d, got %d",
SHA_DIGEST_LENGTH, ret);
return (-1);
}
return (0);
}
char *
host_hash(const char *host, const char *name_from_hostfile, u_int src_len)
{
const EVP_MD *md = EVP_sha1();
HMAC_CTX mac_ctx;
char salt[256], result[256], uu_salt[512], uu_result[512];
static char encoded[1024];
u_int i, len;
len = EVP_MD_size(md);
if (name_from_hostfile == NULL) {
for (i = 0; i < len; i++)
salt[i] = arc4random();
} else {
if (extract_salt(name_from_hostfile, src_len, salt,
sizeof(salt)) == -1)
return (NULL);
}
HMAC_Init(&mac_ctx, salt, len, md);
HMAC_Update(&mac_ctx, host, strlen(host));
HMAC_Final(&mac_ctx, result, NULL);
HMAC_cleanup(&mac_ctx);
if (__b64_ntop(salt, len, uu_salt, sizeof(uu_salt)) == -1 ||
__b64_ntop(result, len, uu_result, sizeof(uu_result)) == -1)
fatal("host_hash: __b64_ntop failed");
snprintf(encoded, sizeof(encoded), "%s%s%c%s", HASH_MAGIC, uu_salt,
HASH_DELIM, uu_result);
return (encoded);
}
int
hostfile_read_key(char **cpp, u_int *bitsp, Key *ret)
{
char *cp;
for (cp = *cpp; *cp == ' ' || *cp == '\t'; cp++)
;
if (key_read(ret, &cp) != 1)
return 0;
for (; *cp == ' ' || *cp == '\t'; cp++)
;
*cpp = cp;
*bitsp = key_size(ret);
return 1;
}
static int
hostfile_check_key(int bits, const Key *key, const char *host, const char *filename, int linenum)
{
if (key == NULL || key->type != KEY_RSA1 || key->rsa == NULL)
return 1;
if (bits != BN_num_bits(key->rsa->n)) {
logit("Warning: %s, line %d: keysize mismatch for host %s: "
"actual %d vs. announced %d.",
filename, linenum, host, BN_num_bits(key->rsa->n), bits);
logit("Warning: replace %d with %d in %s, line %d.",
bits, BN_num_bits(key->rsa->n), filename, linenum);
}
return 1;
}
static HostStatus
check_host_in_hostfile_by_key_or_type(const char *filename,
const char *host, const Key *key, int keytype, Key *found, int *numret)
{
FILE *f;
char line[8192];
int linenum = 0;
u_int kbits;
char *cp, *cp2, *hashed_host;
HostStatus end_return;
debug3("check_host_in_hostfile: filename %s", filename);
f = fopen(filename, "r");
if (!f)
return HOST_NEW;
end_return = HOST_NEW;
while (fgets(line, sizeof(line), f)) {
cp = line;
linenum++;
for (; *cp == ' ' || *cp == '\t'; cp++)
;
if (!*cp || *cp == '#' || *cp == '\n')
continue;
for (cp2 = cp; *cp2 && *cp2 != ' ' && *cp2 != '\t'; cp2++)
;
if (match_hostname(host, cp, (u_int) (cp2 - cp)) != 1) {
if (*cp != HASH_DELIM)
continue;
hashed_host = host_hash(host, cp, (u_int) (cp2 - cp));
if (hashed_host == NULL) {
debug("Invalid hashed host line %d of %s",
linenum, filename);
continue;
}
if (strncmp(hashed_host, cp, (u_int) (cp2 - cp)) != 0)
continue;
}
cp = cp2;
if (!hostfile_read_key(&cp, &kbits, found))
continue;
if (numret != NULL)
*numret = linenum;
if (key == NULL) {
if (found->type == keytype) {
fclose(f);
return HOST_FOUND;
}
continue;
}
if (!hostfile_check_key(kbits, found, host, filename, linenum))
continue;
if (key_equal(key, found)) {
debug3("check_host_in_hostfile: match line %d", linenum);
fclose(f);
return HOST_OK;
}
end_return = HOST_CHANGED;
}
fclose(f);
return end_return;
}
HostStatus
check_host_in_hostfile(const char *filename, const char *host, const Key *key,
Key *found, int *numret)
{
if (key == NULL)
fatal("no key to look up");
return (check_host_in_hostfile_by_key_or_type(filename, host, key, 0,
found, numret));
}
int
lookup_key_in_hostfile_by_type(const char *filename, const char *host,
int keytype, Key *found, int *numret)
{
return (check_host_in_hostfile_by_key_or_type(filename, host, NULL,
keytype, found, numret) == HOST_FOUND);
}
int
add_host_to_hostfile(const char *filename, const char *host, const Key *key,
int store_hash)
{
FILE *f;
int success = 0;
char *hashed_host = NULL;
if (key == NULL)
return 1;
f = fopen(filename, "a");
if (!f)
return 0;
if (store_hash) {
if ((hashed_host = host_hash(host, NULL, 0)) == NULL) {
error("add_host_to_hostfile: host_hash failed");
fclose(f);
return 0;
}
}
fprintf(f, "%s ", store_hash ? hashed_host : host);
if (key_write(key, f)) {
success = 1;
} else {
error("add_host_to_hostfile: saving key in %s failed", filename);
}
fprintf(f, "\n");
fclose(f);
return success;
}