#include "config.h"
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include "libssh/priv.h"
#include "libssh/session.h"
#include "libssh/buffer.h"
#include "libssh/misc.h"
#include "libssh/dh.h"
#include "libssh/pki.h"
#include "libssh/options.h"
#include "libssh/knownhosts.h"
#include "libssh/string.h"
#include "libssh/token.h"
#ifndef _WIN32
# include <netinet/in.h>
# include <arpa/inet.h>
#endif
#ifndef MAX_LINE_SIZE
#define MAX_LINE_SIZE 4096
#endif
static struct ssh_tokens_st *ssh_get_knownhost_line(FILE **file,
const char *filename,
const char **found_type)
{
char buffer[MAX_LINE_SIZE] = {0};
char *ptr;
struct ssh_tokens_st *tokens;
if (*file == NULL) {
*file = fopen(filename,"r");
if (*file == NULL) {
return NULL;
}
}
while (fgets(buffer, sizeof(buffer), *file)) {
ptr = strchr(buffer, '\n');
if (ptr) {
*ptr = '\0';
}
ptr = strchr(buffer,'\r');
if (ptr) {
*ptr = '\0';
}
if (buffer[0] == '\0' || buffer[0] == '#') {
continue;
}
tokens = ssh_tokenize(buffer, ' ');
if (tokens == NULL) {
fclose(*file);
*file = NULL;
return NULL;
}
if (tokens->tokens[0] == NULL ||
tokens->tokens[1] == NULL ||
tokens->tokens[2] == NULL)
{
ssh_tokens_free(tokens);
continue;
}
*found_type = tokens->tokens[1];
return tokens;
}
fclose(*file);
*file = NULL;
return NULL;
}
static int check_public_key(ssh_session session, char **tokens) {
ssh_string pubkey_blob = NULL;
ssh_buffer pubkey_buffer;
char *pubkey_64;
int rc;
pubkey_64 = tokens[2];
pubkey_buffer = base64_to_bin(pubkey_64);
if (pubkey_buffer == NULL) {
ssh_set_error(session, SSH_FATAL,
"Verifying that server is a known host: base64 error");
return -1;
}
rc = ssh_dh_get_current_server_publickey_blob(session, &pubkey_blob);
if (rc != 0) {
ssh_buffer_free(pubkey_buffer);
return -1;
}
if (ssh_buffer_get_len(pubkey_buffer) != ssh_string_len(pubkey_blob)) {
ssh_string_free(pubkey_blob);
ssh_buffer_free(pubkey_buffer);
return 0;
}
if (memcmp(ssh_buffer_get(pubkey_buffer), ssh_string_data(pubkey_blob),
ssh_buffer_get_len(pubkey_buffer)) != 0) {
ssh_string_free(pubkey_blob);
ssh_buffer_free(pubkey_buffer);
return 0;
}
ssh_string_free(pubkey_blob);
ssh_buffer_free(pubkey_buffer);
return 1;
}
static int match_hashed_host(const char *host, const char *sourcehash)
{
unsigned char buffer[256] = {0};
ssh_buffer salt;
ssh_buffer hash;
HMACCTX mac;
char *source;
char *b64hash;
int match, rc;
size_t size;
if (strncmp(sourcehash, "|1|", 3) != 0) {
return 0;
}
source = strdup(sourcehash + 3);
if (source == NULL) {
return 0;
}
b64hash = strchr(source, '|');
if (b64hash == NULL) {
SAFE_FREE(source);
return 0;
}
*b64hash = '\0';
b64hash++;
salt = base64_to_bin(source);
if (salt == NULL) {
SAFE_FREE(source);
return 0;
}
hash = base64_to_bin(b64hash);
SAFE_FREE(source);
if (hash == NULL) {
ssh_buffer_free(salt);
return 0;
}
mac = hmac_init(ssh_buffer_get(salt), ssh_buffer_get_len(salt), SSH_HMAC_SHA1);
if (mac == NULL) {
ssh_buffer_free(salt);
ssh_buffer_free(hash);
return 0;
}
size = sizeof(buffer);
rc = hmac_update(mac, host, strlen(host));
if (rc != 1) {
ssh_buffer_free(salt);
ssh_buffer_free(hash);
return 0;
}
rc = hmac_final(mac, buffer, &size);
if (rc != 1) {
ssh_buffer_free(salt);
ssh_buffer_free(hash);
return 0;
}
if (size == ssh_buffer_get_len(hash) &&
memcmp(buffer, ssh_buffer_get(hash), size) == 0) {
match = 1;
} else {
match = 0;
}
ssh_buffer_free(salt);
ssh_buffer_free(hash);
SSH_LOG(SSH_LOG_PACKET,
"Matching a hashed host: %s match=%d", host, match);
return match;
}
int ssh_is_server_known(ssh_session session)
{
FILE *file = NULL;
char *host;
char *hostport;
const char *type;
int match;
int i = 0;
char *files[3];
struct ssh_tokens_st *tokens;
int ret = SSH_SERVER_NOT_KNOWN;
if (session->opts.knownhosts == NULL) {
if (ssh_options_apply(session) < 0) {
ssh_set_error(session, SSH_REQUEST_DENIED,
"Can't find a known_hosts file");
return SSH_SERVER_FILE_NOT_FOUND;
}
}
if (session->opts.host == NULL) {
ssh_set_error(session, SSH_FATAL,
"Can't verify host in known hosts if the hostname isn't known");
return SSH_SERVER_ERROR;
}
if (session->current_crypto == NULL){
ssh_set_error(session, SSH_FATAL,
"ssh_is_host_known called without cryptographic context");
return SSH_SERVER_ERROR;
}
host = ssh_lowercase(session->opts.host);
hostport = ssh_hostport(host, session->opts.port > 0 ? session->opts.port : 22);
if (host == NULL || hostport == NULL) {
ssh_set_error_oom(session);
SAFE_FREE(host);
SAFE_FREE(hostport);
return SSH_SERVER_ERROR;
}
i = 0;
if (session->opts.global_knownhosts != NULL){
files[i++] = session->opts.global_knownhosts;
}
files[i++] = session->opts.knownhosts;
files[i] = NULL;
i = 0;
do {
tokens = ssh_get_knownhost_line(&file,
files[i],
&type);
if (tokens == NULL) {
++i;
if(files[i] == NULL)
break;
else
continue;
}
match = match_hashed_host(host, tokens->tokens[0]);
if (match == 0){
match = match_hostname(hostport, tokens->tokens[0],
strlen(tokens->tokens[0]));
}
if (match == 0) {
match = match_hostname(host, tokens->tokens[0],
strlen(tokens->tokens[0]));
}
if (match == 0) {
match = match_hashed_host(hostport, tokens->tokens[0]);
}
if (match) {
ssh_key pubkey = ssh_dh_get_current_server_publickey(session);
const char *pubkey_type = ssh_key_type_to_char(ssh_key_type(pubkey));
if (strcmp(pubkey_type, type) != 0) {
SSH_LOG(SSH_LOG_PACKET,
"ssh_is_server_known: server type [%s] doesn't match the "
"type [%s] in known_hosts file",
pubkey_type,
type);
if (ret != SSH_SERVER_KNOWN_CHANGED)
ret = SSH_SERVER_FOUND_OTHER;
ssh_tokens_free(tokens);
continue;
}
match = check_public_key(session, tokens->tokens);
ssh_tokens_free(tokens);
if (match < 0) {
ret = SSH_SERVER_ERROR;
break;
} else if (match == 1) {
ret = SSH_SERVER_KNOWN_OK;
break;
} else if(match == 0) {
ret = SSH_SERVER_KNOWN_CHANGED;
}
} else {
ssh_tokens_free(tokens);
}
} while (1);
if ((ret == SSH_SERVER_NOT_KNOWN) &&
(session->opts.StrictHostKeyChecking == 0)) {
int rv = ssh_session_update_known_hosts(session);
if (rv != SSH_OK) {
ret = SSH_SERVER_ERROR;
} else {
ret = SSH_SERVER_KNOWN_OK;
}
}
SAFE_FREE(host);
SAFE_FREE(hostport);
if (file != NULL) {
fclose(file);
}
return ret;
}
char * ssh_dump_knownhost(ssh_session session) {
ssh_key server_pubkey = NULL;
char *host;
char *hostport;
char *buffer;
char *b64_key;
int rc;
if (session->opts.host == NULL) {
ssh_set_error(session, SSH_FATAL,
"Can't write host in known hosts if the hostname isn't known");
return NULL;
}
host = ssh_lowercase(session->opts.host);
if (session->opts.port > 0 && session->opts.port != 22) {
hostport = ssh_hostport(host, session->opts.port);
SAFE_FREE(host);
if (hostport == NULL) {
return NULL;
}
host = hostport;
hostport = NULL;
}
if (session->current_crypto==NULL) {
ssh_set_error(session, SSH_FATAL, "No current crypto context");
SAFE_FREE(host);
return NULL;
}
server_pubkey = ssh_dh_get_current_server_publickey(session);
if (server_pubkey == NULL){
ssh_set_error(session, SSH_FATAL, "No public key present");
SAFE_FREE(host);
return NULL;
}
buffer = calloc (1, MAX_LINE_SIZE);
if (!buffer) {
SAFE_FREE(host);
return NULL;
}
rc = ssh_pki_export_pubkey_base64(server_pubkey, &b64_key);
if (rc < 0) {
SAFE_FREE(buffer);
SAFE_FREE(host);
return NULL;
}
snprintf(buffer, MAX_LINE_SIZE,
"%s %s %s\n",
host,
server_pubkey->type_c,
b64_key);
SAFE_FREE(host);
SAFE_FREE(b64_key);
return buffer;
}
int ssh_write_knownhost(ssh_session session)
{
FILE *file;
char *buffer = NULL;
char *dir;
int rc;
if (session->opts.knownhosts == NULL) {
if (ssh_options_apply(session) < 0) {
ssh_set_error(session, SSH_FATAL, "Can't find a known_hosts file");
return SSH_ERROR;
}
}
errno = 0;
file = fopen(session->opts.knownhosts, "a");
if (file == NULL) {
char err_msg[SSH_ERRNO_MSG_MAX] = {0};
if (errno == ENOENT) {
dir = ssh_dirname(session->opts.knownhosts);
if (dir == NULL) {
ssh_set_error(session, SSH_FATAL,
"%s", ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX));
return SSH_ERROR;
}
rc = ssh_mkdirs(dir, 0700);
if (rc < 0) {
ssh_set_error(session, SSH_FATAL,
"Cannot create %s directory: %s",
dir, ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX));
SAFE_FREE(dir);
return SSH_ERROR;
}
SAFE_FREE(dir);
errno = 0;
file = fopen(session->opts.knownhosts, "a");
if (file == NULL) {
ssh_set_error(session, SSH_FATAL,
"Couldn't open known_hosts file %s"
" for appending: %s",
session->opts.knownhosts,
ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX));
return SSH_ERROR;
}
} else {
ssh_set_error(session, SSH_FATAL,
"Couldn't open known_hosts file %s for appending: %s",
session->opts.knownhosts,
ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX));
return SSH_ERROR;
}
}
rc = ssh_session_export_known_hosts_entry(session, &buffer);
if (rc != SSH_OK) {
fclose(file);
return SSH_ERROR;
}
if (fwrite(buffer, strlen(buffer), 1, file) != 1 || ferror(file)) {
SAFE_FREE(buffer);
fclose(file);
return -1;
}
SAFE_FREE(buffer);
fclose(file);
return 0;
}
#define KNOWNHOSTS_MAXTYPES 10