From 083414da945ff6e4be429a43b3e5cfefe10e7c05 Mon Sep 17 00:00:00 2001 From: Iru Cai Date: Sun, 20 May 2018 23:39:07 +0800 Subject: ssh_known_hosts.c: .ssh/known_hosts parser --- src/protocol/internal/ssh_known_hosts.c | 177 ++++++++++++++++++++++++++++++++ src/protocol/internal/ssh_known_hosts.h | 30 ++++++ 2 files changed, 207 insertions(+) create mode 100644 src/protocol/internal/ssh_known_hosts.c create mode 100644 src/protocol/internal/ssh_known_hosts.h diff --git a/src/protocol/internal/ssh_known_hosts.c b/src/protocol/internal/ssh_known_hosts.c new file mode 100644 index 0000000..95b7b85 --- /dev/null +++ b/src/protocol/internal/ssh_known_hosts.c @@ -0,0 +1,177 @@ +/* + * ssh_known_hosts.c: parser for ~/.ssh/known_hosts + * Copyright (C) 2018 Iru Cai + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#include +#include +#include +#include "ssh_endian.h" +#include "ccan_base64.h" +#include "ssh_known_hosts.h" + +static char *ssh_hosts_file = NULL; + +static char *ssh_get_string(const char *buf, char *s, int size, char delim) +{ + char *end = strchr(buf, delim); + if (end == NULL) + return NULL; + + int l = end - buf; + if (l >= size) + return NULL; + + memcpy(s, buf, l); + s[l] = 0; + return end; +} + +struct ssh_host *parse_hosts_file(const char *fn, int *list_sz) +{ + FILE *fp; + char linebuf[1536]; + int nHosts; + struct ssh_host *hosts; + + fp = fopen(fn, "r"); + if (fp == NULL) { + *list_sz = 0; + return NULL; + } + + fseek(fp, 0, SEEK_END); // seek to end of file + long size = ftell(fp); // get current file pointer + fseek(fp, 0, SEEK_SET); // seek back to beginning of file + + /* use file size/64 to estimate # of hosts */ + hosts = (struct ssh_host *)malloc(sizeof(struct ssh_host) * + (size / 64 + 1)); + nHosts = 0; + while (fgets(linebuf, sizeof(linebuf), fp)) { + char *host_end = ssh_get_string(linebuf, hosts[nHosts].hostname, + 256, ' '); + if (host_end == NULL) + continue; + char *type_end = ssh_get_string(host_end + 1, + hosts[nHosts].keytype, 32, ' '); + if (type_end == NULL) + continue; + char *key_end = ssh_get_string( + type_end + 1, hosts[nHosts].pubkey, 1024, '\n'); + if (key_end == NULL) + continue; + if (key_end - linebuf < 64) /* too short */ + continue; + nHosts++; + } + fclose(fp); + *list_sz = nHosts; + return hosts; +} + +int find_ssh_host(struct ssh_host *h, int n, const char *hostname, int port) +{ + int L = strlen(hostname); + for (int i = 0; i < n; i++) { + const char *hn = h[i].hostname; + const char *hn_end; + int hlen; + + if (port == 22 && hn[0] == '[') + continue; + if (port != 22 && hn[0] != '[') + continue; + + if (port == 22) { + hn_end = strchr(hn, ','); + if (hn_end == NULL) + hlen = strlen(hn); + else + hlen = hn_end - hn; + } else { + hn_end = strchr(hn, ':'); + if (hn_end == NULL || atoi(hn_end + 1) != port) + continue; + hn++; + hlen = hn_end - hn - 1; + } + + /* TODO: match IP address */ + + if (hlen != L || memcmp(hostname, hn, L) != 0) + continue; + else + return i; + } + return -1; +} + +int key_matches(struct ssh_host *h, const unsigned char *K_S, int K_S_len) +{ + int algo_len = ntohu32(K_S); + int h_keylen = strlen(h->pubkey); + char buf[1536]; + + if (strlen(h->keytype) == algo_len && + memcmp(h->keytype, K_S + 4, algo_len) == 0) { + if (base64_encoded_length(K_S_len) != h_keylen) + return 0; + base64_encode(buf, sizeof(buf), K_S, K_S_len); + if (memcmp(buf, h->pubkey, h_keylen) == 0) + return 1; + else + return 0; + } else { + return 0; + } +} + +void append_hostkey(const char *fn, const char *hostname, const unsigned char *K_S, int K_S_len) +{ + FILE *fp = fopen(fn, "a"); + if (fp == NULL) + return; + + char algo[32]; + char buf[1536]; + + int algo_len = ntohu32(K_S); + if (algo_len >= sizeof(algo)) + goto end; + + memcpy(algo, K_S + 4, algo_len); + algo[algo_len] = 0; + + int b64len = base64_encoded_length(K_S_len); + if (b64len >= sizeof(buf)) + goto end; + + base64_encode(buf, sizeof(buf), K_S, K_S_len); + buf[b64len] = 0; + + fprintf(fp, "%s %s %s\n", hostname, algo, buf); +end: + fclose(fp); + return; +} + +const char *ssh_hosts_filename(void) +{ + if (ssh_hosts_file != NULL) + return ssh_hosts_file; + + char *myhome = getenv("HOME"); + ssh_hosts_file = malloc(strlen(myhome) + 20); + strcpy(ssh_hosts_file, myhome); + strcat(ssh_hosts_file, "/.ssh/known_hosts"); + return ssh_hosts_file; +} + +struct ssh_host *parse_unix_hosts_file(int *nhosts) +{ + const char *hosts_file = ssh_hosts_filename(); + return parse_hosts_file(hosts_file, nhosts); +} diff --git a/src/protocol/internal/ssh_known_hosts.h b/src/protocol/internal/ssh_known_hosts.h new file mode 100644 index 0000000..94fa215 --- /dev/null +++ b/src/protocol/internal/ssh_known_hosts.h @@ -0,0 +1,30 @@ +/* + * ssh_known_hosts.h: parser for ~/.ssh/known_hosts + * Copyright (C) 2018 Iru Cai + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#ifndef SSH_KNOWN_HOSTS_H +#define SSH_KNOWN_HOSTS_H + +#ifdef __cplusplus +extern "C" { +#endif /* } */ + +struct ssh_host { + char hostname[256], keytype[32], pubkey[1024]; +}; + +struct ssh_host *parse_hosts_file(const char *fn, int *list_sz); +int find_ssh_host(struct ssh_host *h, int n, const char *hostname, int port); +int key_matches(struct ssh_host *h, const unsigned char *K_S, int K_S_len); +const char *ssh_hosts_filename(void); +struct ssh_host *parse_unix_hosts_file(int *nhosts); +void append_hostkey(const char *, const char *, const unsigned char *, int); + +#ifdef __cplusplus +} +#endif + +#endif -- cgit v1.2.3