/* * 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); }