summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIru Cai <mytbk920423@gmail.com>2018-05-20 23:39:07 +0800
committerIru Cai <mytbk920423@gmail.com>2018-06-08 15:25:11 +0800
commit083414da945ff6e4be429a43b3e5cfefe10e7c05 (patch)
tree9c630bb3d7b29a7a5c9fbed400a152fcc3ca4bd7
parentd029e2bbdffeeb7b35144802362926bc008620c7 (diff)
downloadfqterm-083414da945ff6e4be429a43b3e5cfefe10e7c05.tar.xz
ssh_known_hosts.c: .ssh/known_hosts parser
-rw-r--r--src/protocol/internal/ssh_known_hosts.c177
-rw-r--r--src/protocol/internal/ssh_known_hosts.h30
2 files changed, 207 insertions, 0 deletions
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 <mytbk920423@gmail.com>
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#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 <mytbk920423@gmail.com>
+ *
+ * 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