From d3542e6f7d0a13f3b6ff3ad5047a2f9dddcf8c0f Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Thu, 7 May 2009 17:21:56 +0000 Subject: [PATCH] split out the knownhost code from hostkey.c into its own separate source file now: knownhost.c --- src/Makefile.am | 4 +- src/hostkey.c | 519 -------------------------------------------- src/knownhost.c | 566 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 568 insertions(+), 521 deletions(-) create mode 100644 src/knownhost.c diff --git a/src/Makefile.am b/src/Makefile.am index 9f89347..5b355a4 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,10 +1,10 @@ -# $Id: Makefile.am,v 1.20 2009/05/07 13:09:49 bagder Exp $ +# $Id: Makefile.am,v 1.21 2009/05/07 17:21:56 bagder Exp $ AUTOMAKE_OPTIONS = foreign nostdinc libssh2_la_SOURCES = channel.c comp.c crypt.c hostkey.c kex.c mac.c misc.c \ packet.c publickey.c scp.c session.c sftp.c userauth.c libssh2_priv.h \ openssl.h libgcrypt.h transport.c version.c transport.h channel.h comp.h \ - mac.h misc.h + mac.h misc.h knownhost.c if LIBGCRYPT libssh2_la_SOURCES += libgcrypt.c pem.c diff --git a/src/hostkey.c b/src/hostkey.c index 954b4c6..11aff49 100644 --- a/src/hostkey.c +++ b/src/hostkey.c @@ -457,19 +457,6 @@ libssh2_hostkey_hash(LIBSSH2_SESSION * session, int hash_type) } } -static void free_host(LIBSSH2_SESSION *session, struct known_host *entry) -{ - if(entry) { - if(entry->key) - LIBSSH2_FREE(session, entry->key); - if(entry->salt) - LIBSSH2_FREE(session, entry->salt); - if(entry->name) - LIBSSH2_FREE(session, entry->name); - LIBSSH2_FREE(session, entry); - } -} - /* * libssh2_session_hostkey() * @@ -489,509 +476,3 @@ libssh2_session_hostkey(LIBSSH2_SESSION *session, size_t *len) return NULL; } -/* - * libssh2_knownhost_init - * - * Init a collection of known hosts. Returns the pointer to a collection. - * - */ -LIBSSH2_API LIBSSH2_KNOWNHOSTS * -libssh2_knownhost_init(LIBSSH2_SESSION *session) -{ - LIBSSH2_KNOWNHOSTS *knh = - LIBSSH2_ALLOC(session, sizeof(struct _LIBSSH2_KNOWNHOSTS)); - - if(!knh) - return NULL; - - knh->session = session; - - _libssh2_list_init(&knh->head); - - return knh; -} - -/* - * libssh2_knownhost_add - * - * Add a host and its associated key to the collection of known hosts. - * - * The 'type' argument specifies on what format the given host and keys are: - * - * plain - ascii "hostname.domain.tld" - * sha1 - SHA1( ) base64-encoded! - * custom - another hash - * - * If 'sha1' is selected as type, the salt must be provided to the salt - * argument. This too base64 encoded. - * - * The SHA-1 hash is what OpenSSH can be told to use in known_hosts files. If - * a custom type is used, salt is ignored and you must provide the host - * pre-hashed when checking for it in the libssh2_knownhost_check() function. - * - */ - -LIBSSH2_API int -libssh2_knownhost_add(LIBSSH2_KNOWNHOSTS *hosts, - char *host, char *salt, - char *key, size_t keylen, - int typemask) -{ - struct known_host *entry = - LIBSSH2_ALLOC(hosts->session, sizeof(struct known_host)); - size_t hostlen = strlen(host); - int rc = LIBSSH2_ERROR_MEMORY; - char *ptr; - unsigned int ptrlen; - - if(!entry) - return rc; - - memset(entry, 0, sizeof(struct known_host)); - - entry->typemask = typemask; - - switch(entry->typemask & LIBSSH2_KNOWNHOST_TYPE_MASK) { - case LIBSSH2_KNOWNHOST_TYPE_PLAIN: - case LIBSSH2_KNOWNHOST_TYPE_CUSTOM: - entry->name = LIBSSH2_ALLOC(hosts->session, hostlen+1); - if(!entry) - goto error; - memcpy(entry->name, host, hostlen+1); - break; - case LIBSSH2_KNOWNHOST_TYPE_SHA1: - rc = libssh2_base64_decode(hosts->session, &ptr, &ptrlen, - host, hostlen); - if(rc) - goto error; - entry->name = ptr; - entry->name_len = ptrlen; - - rc = libssh2_base64_decode(hosts->session, &ptr, &ptrlen, - salt, strlen(salt)); - if(rc) - goto error; - entry->salt = ptr; - entry->salt_len = ptrlen; - break; - default: - rc = LIBSSH2_ERROR_METHOD_NOT_SUPPORTED; - goto error; - } - - if(typemask & LIBSSH2_KNOWNHOST_KEY_BASE64) { - /* the provided key is base64 encoded already */ - if(!keylen) - keylen = strlen(key); - entry->key = LIBSSH2_ALLOC(hosts->session, keylen+1); - if(!entry) - goto error; - memcpy(entry->key, key, keylen+1); - } - else { - /* key is raw, we base64 encode it and store it as such */ - size_t nlen = _libssh2_base64_encode(hosts->session, key, keylen, - &ptr); - if(!nlen) - goto error; - - entry->key = ptr; - } - - /* add this new host to the big list of known hosts */ - _libssh2_list_add(&hosts->head, &entry->node); - - return LIBSSH2_ERROR_NONE; - error: - free_host(hosts->session, entry); - return rc; -} - -#define KNOWNHOST_MAGIC 0xdeadcafe -/* - * knownhost_to_external() - * - * Copies data from the internal to the external representation struct. - * - */ -static void knownhost_to_external(struct known_host *node, - struct libssh2_knownhost *ext) -{ - if(ext) { - ext->magic = KNOWNHOST_MAGIC; - ext->node = node; - ext->name = ((node->typemask & LIBSSH2_KNOWNHOST_TYPE_MASK) == - LIBSSH2_KNOWNHOST_TYPE_PLAIN)? node->name:NULL; - ext->key = node->key; - ext->typemask = node->typemask; - } -} - -/* - * libssh2_knownhost_check - * - * Check a host and its associated key against the collection of known hosts. - * - * The typemask is the type/format of the given host name and key - * - * plain - ascii "hostname.domain.tld" - * sha1 - NOT SUPPORTED AS INPUT - * custom - prehashed base64 encoded. Note that this cannot use any salts. - * - * Returns: - * - * LIBSSH2_KNOWNHOST_CHECK_FAILURE - * LIBSSH2_KNOWNHOST_CHECK_NOTFOUND - * LIBSSH2_KNOWNHOST_CHECK_MATCH - * LIBSSH2_KNOWNHOST_CHECK_MISMATCH - */ -LIBSSH2_API int -libssh2_knownhost_check(LIBSSH2_KNOWNHOSTS *hosts, - char *host, char *key, size_t keylen, - int typemask, - struct libssh2_knownhost *knownhost) -{ - struct known_host *node = _libssh2_list_first(&hosts->head); - struct known_host *badkey = NULL; - int type = typemask & LIBSSH2_KNOWNHOST_TYPE_MASK; - char *keyalloc = NULL; - int rc = LIBSSH2_KNOWNHOST_CHECK_NOTFOUND; - - if(type == LIBSSH2_KNOWNHOST_TYPE_SHA1) - /* we can't work with a sha1 as given input */ - return LIBSSH2_KNOWNHOST_CHECK_MISMATCH; - - if(!(typemask & LIBSSH2_KNOWNHOST_KEY_BASE64)) { - /* we got a raw key input, convert it to base64 for the checks below */ - size_t nlen = _libssh2_base64_encode(hosts->session, key, keylen, - &keyalloc); - if(!nlen) - return LIBSSH2_KNOWNHOST_CHECK_FAILURE; - - /* make the key point to this */ - key = keyalloc; - keylen = nlen; - } - - while (node) { - int match = 0; - switch(node->typemask & LIBSSH2_KNOWNHOST_TYPE_MASK) { - case LIBSSH2_KNOWNHOST_TYPE_PLAIN: - if(type == LIBSSH2_KNOWNHOST_TYPE_PLAIN) - match = !strcmp(host, node->name); - break; - case LIBSSH2_KNOWNHOST_TYPE_CUSTOM: - if(type == LIBSSH2_KNOWNHOST_TYPE_CUSTOM) - match = !strcmp(host, node->name); - break; - case LIBSSH2_KNOWNHOST_TYPE_SHA1: - if(type == LIBSSH2_KNOWNHOST_TYPE_PLAIN) { - /* when we have the sha1 version stored, we can use a plain - input to produce a hash to compare with the stored hash. - - HMAC_Init(&mac_ctx, salt, len, md); - HMAC_Update(&mac_ctx, host, strlen(host)); - HMAC_Final(&mac_ctx, result, NULL); - HMAC_cleanup(&mac_ctx); - - */ - libssh2_hmac_ctx ctx; - unsigned char hash[SHA_DIGEST_LENGTH]; - - if(SHA_DIGEST_LENGTH != node->name_len) { - /* the name hash length must be the sha1 size or - we can't match it */ - break; - } - libssh2_hmac_sha1_init(&ctx, node->salt, node->salt_len); - libssh2_hmac_update(ctx, (unsigned char *)host, strlen(host)); - libssh2_hmac_final(ctx, hash); - libssh2_hmac_cleanup(&ctx); - - if(!memcmp(hash, node->name, SHA_DIGEST_LENGTH)) - /* this is a node we're interested in */ - match = 1; - } - break; - default: /* unsupported type */ - break; - } - if(match) { - /* host name match, now compare the keys */ - if(!strcmp(key, node->key)) { - /* they match! */ - knownhost_to_external(node, knownhost); - badkey = NULL; - rc = LIBSSH2_KNOWNHOST_CHECK_MATCH; - break; - } - else { - /* remember the first node that had a host match but a failed - key match since we continue our search from here */ - if(!badkey) - badkey = node; - } - } - node= _libssh2_list_next(&node->node); - } - - if(badkey) { - /* key mismatch */ - knownhost_to_external(badkey, knownhost); - rc = LIBSSH2_KNOWNHOST_CHECK_MISMATCH; - } - - if(keyalloc) - LIBSSH2_FREE(hosts->session, keyalloc); - - return rc; -} - -/* - * libssh2_knownhost_del - * - * Remove a host from the collection of known hosts. - * - */ -LIBSSH2_API int -libssh2_knownhost_del(LIBSSH2_KNOWNHOSTS *hosts, - struct libssh2_knownhost *entry) -{ - struct known_host *node; - if(!entry || (entry->magic != KNOWNHOST_MAGIC)) - /* check that this was retrieved the right way or get out */ - return -1; - - /* get the internal node pointer */ - node = entry->node; - - /* unlink from the list of all hosts */ - _libssh2_list_remove(&node->node); - - /* free all resources */ - free_host(hosts->session, node); - - return 0; -} - -/* - * libssh2_knownhost_free - * - * Free an entire collection of known hosts. - * - */ -LIBSSH2_API void -libssh2_knownhost_free(LIBSSH2_KNOWNHOSTS *hosts) -{ - struct known_host *node; - struct known_host *next; - - for(node = _libssh2_list_first(&hosts->head); node; node = next) { - next = _libssh2_list_next(&node->node); - free_host(hosts->session, node); - } - LIBSSH2_FREE(hosts->session, hosts); -} - -/* - * hostline() - * - * Parse a single known_host line pre-split into host and key. - * - * Note: this function assumes that the 'host' pointer points into a temporary - * buffer as it will write to it. - */ -static int hostline(LIBSSH2_KNOWNHOSTS *hosts, - char *host, size_t hostlen, - char *key, size_t keylen) -{ - char *p; - char *salt = NULL; - int rc; - int type = LIBSSH2_KNOWNHOST_TYPE_PLAIN; - char *sep = NULL; - - /* Figure out host format */ - if(strncmp(host, "|1|", 3)) { - /* old style plain text: [name][,][ip-address] - - for the sake of simplicity, we add them as two hosts with the same - key - */ - sep = strchr(host, ','); - } - else { - /* |1|[salt]|[hash] */ - type = LIBSSH2_KNOWNHOST_TYPE_SHA1; - - salt = &host[3]; /* skip the magic marker */ - - /* this is where the salt starts, find the end of it */ - for(p = salt; *p && (*p != '|'); p++) - ; - - if(*p=='|') { - char *hash = NULL; - *p=0; /* terminate the salt string */ - hash = p+1; /* the hash is after the separator */ - - /* now make the host point to the hash */ - hostlen = strlen(hash); - host = hash; - } - else - return 0; - } - - if(keylen < 20) - return -1; /* TODO: better return code */ - - switch(key[0]) { - case '0': case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - type |= LIBSSH2_KNOWNHOST_KEY_RSA1; - - /* Note that the old-style keys (RSA1) aren't truly base64, but we - * claim it is for now since we can get away with strcmp()ing the - * entire anything anyway! We need to check and fix these to make them - * work properly. - */ - break; - - case 's': /* ssh-dss or ssh-rsa */ - if(!strncmp(key, "ssh-dss", 7)) - type |= LIBSSH2_KNOWNHOST_KEY_SSHDSS; - else if(!strncmp(key, "ssh-rsa", 7)) - type |= LIBSSH2_KNOWNHOST_KEY_SSHRSA; - else - return -1; /* unknown */ - - key += 7; - keylen -= 7; - - /* skip whitespaces */ - while((*key ==' ') || (*key == '\t')) { - key++; - keylen--; - } - break; - - default: /* unknown key format */ - return -1; - } - - if(sep) { - /* this is the second host, add this first */ - char *ipaddr; - *sep++ = 0; /* zero terminate the first host name here */ - ipaddr = sep; - rc = libssh2_knownhost_add(hosts, ipaddr, salt, key, keylen, - type | LIBSSH2_KNOWNHOST_KEY_BASE64); - if(rc) - return rc; - } - - rc = libssh2_knownhost_add(hosts, host, salt, key, keylen, - type | LIBSSH2_KNOWNHOST_KEY_BASE64); - - return rc; -} - -/* - * libssh2_knownhost_parsefile - * - * Add hosts+key pairs from a given file. - * - * Returns a negative value for error or number of successfully added hosts. - * - * Line format: - * - * - * - * Where the two parts can be created like: - * - * can be either - * or - * - * consists of - * [name,address] or just [name] or just [address] - * - * consists of - * |1||hash - * - * can be one of: - * [RSA bits] [e] [n as a decimal number] - * 'ssh-dss' [base64-encoded-key] - * 'ssh-rsa' [base64-encoded-key] - * - */ - -#define LIBSSH2_KNOWNHOST_FILE_OPENSSH 1 - -LIBSSH2_API int -libssh2_knownhost_parsefile(LIBSSH2_KNOWNHOSTS *hosts, - const char *filename, int type) -{ - FILE *file; - int num = 0; - char buf[2048]; - - if(type != LIBSSH2_KNOWNHOST_FILE_OPENSSH) - return -1; - - file = fopen(filename, "r"); - if(file) { - char *cp; - char *hostp; - char *key; - size_t hostlen; - - while(fgets(buf, sizeof(buf), file)) { - cp = buf; - - /* skip leading whitespaces */ - while((*cp==' ') || (*cp == '\t')) - cp++; - - if(!*cp || (*cp == '#') || (*cp == '\n')) - /* comment or empty line */ - continue; - - /* the host part starts here */ - hostp = cp; - - /* move over the host to the separator */ - while(*cp && (*cp!=' ') && (*cp != '\t')) - cp++; - - hostlen = cp - hostp; - - *cp++ = 0; /* terminate the host string here */ - - /* the key starts after the whitespaces */ - while(*cp && ((*cp==' ') || (*cp == '\t'))) - cp++; - - if(!*cp) - /* illegal line */ - continue; - - key = cp; /* the key starts here */ - - while(*cp && (*cp != '\n')) - cp++; - - /* zero terminate where the newline is */ - if(*cp == '\n') - *cp = 0; - - /* deal with this one host+key line */ - if(!hostline(hosts, hostp, hostlen, key, strlen(key))) - num++; - } - fclose(file); - } - else - return -1; - return num; -} diff --git a/src/knownhost.c b/src/knownhost.c new file mode 100644 index 0000000..bb30272 --- /dev/null +++ b/src/knownhost.c @@ -0,0 +1,566 @@ +/* + * Copyright (c) 2009 by Daniel Stenberg + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#include "libssh2.h" +#include "libssh2_priv.h" +#include "misc.h" + +/* Needed for struct iovec on some platforms */ +#ifdef HAVE_SYS_UIO_H +#include +#endif + +static void free_host(LIBSSH2_SESSION *session, struct known_host *entry) +{ + if(entry) { + if(entry->key) + LIBSSH2_FREE(session, entry->key); + if(entry->salt) + LIBSSH2_FREE(session, entry->salt); + if(entry->name) + LIBSSH2_FREE(session, entry->name); + LIBSSH2_FREE(session, entry); + } +} + +/* + * libssh2_knownhost_init + * + * Init a collection of known hosts. Returns the pointer to a collection. + * + */ +LIBSSH2_API LIBSSH2_KNOWNHOSTS * +libssh2_knownhost_init(LIBSSH2_SESSION *session) +{ + LIBSSH2_KNOWNHOSTS *knh = + LIBSSH2_ALLOC(session, sizeof(struct _LIBSSH2_KNOWNHOSTS)); + + if(!knh) + return NULL; + + knh->session = session; + + _libssh2_list_init(&knh->head); + + return knh; +} + +/* + * libssh2_knownhost_add + * + * Add a host and its associated key to the collection of known hosts. + * + * The 'type' argument specifies on what format the given host and keys are: + * + * plain - ascii "hostname.domain.tld" + * sha1 - SHA1( ) base64-encoded! + * custom - another hash + * + * If 'sha1' is selected as type, the salt must be provided to the salt + * argument. This too base64 encoded. + * + * The SHA-1 hash is what OpenSSH can be told to use in known_hosts files. If + * a custom type is used, salt is ignored and you must provide the host + * pre-hashed when checking for it in the libssh2_knownhost_check() function. + * + */ + +LIBSSH2_API int +libssh2_knownhost_add(LIBSSH2_KNOWNHOSTS *hosts, + char *host, char *salt, + char *key, size_t keylen, + int typemask) +{ + struct known_host *entry = + LIBSSH2_ALLOC(hosts->session, sizeof(struct known_host)); + size_t hostlen = strlen(host); + int rc = LIBSSH2_ERROR_MEMORY; + char *ptr; + unsigned int ptrlen; + + if(!entry) + return rc; + + memset(entry, 0, sizeof(struct known_host)); + + entry->typemask = typemask; + + switch(entry->typemask & LIBSSH2_KNOWNHOST_TYPE_MASK) { + case LIBSSH2_KNOWNHOST_TYPE_PLAIN: + case LIBSSH2_KNOWNHOST_TYPE_CUSTOM: + entry->name = LIBSSH2_ALLOC(hosts->session, hostlen+1); + if(!entry) + goto error; + memcpy(entry->name, host, hostlen+1); + break; + case LIBSSH2_KNOWNHOST_TYPE_SHA1: + rc = libssh2_base64_decode(hosts->session, &ptr, &ptrlen, + host, hostlen); + if(rc) + goto error; + entry->name = ptr; + entry->name_len = ptrlen; + + rc = libssh2_base64_decode(hosts->session, &ptr, &ptrlen, + salt, strlen(salt)); + if(rc) + goto error; + entry->salt = ptr; + entry->salt_len = ptrlen; + break; + default: + rc = LIBSSH2_ERROR_METHOD_NOT_SUPPORTED; + goto error; + } + + if(typemask & LIBSSH2_KNOWNHOST_KEY_BASE64) { + /* the provided key is base64 encoded already */ + if(!keylen) + keylen = strlen(key); + entry->key = LIBSSH2_ALLOC(hosts->session, keylen+1); + if(!entry) + goto error; + memcpy(entry->key, key, keylen+1); + } + else { + /* key is raw, we base64 encode it and store it as such */ + size_t nlen = _libssh2_base64_encode(hosts->session, key, keylen, + &ptr); + if(!nlen) + goto error; + + entry->key = ptr; + } + + /* add this new host to the big list of known hosts */ + _libssh2_list_add(&hosts->head, &entry->node); + + return LIBSSH2_ERROR_NONE; + error: + free_host(hosts->session, entry); + return rc; +} + +#define KNOWNHOST_MAGIC 0xdeadcafe +/* + * knownhost_to_external() + * + * Copies data from the internal to the external representation struct. + * + */ +static void knownhost_to_external(struct known_host *node, + struct libssh2_knownhost *ext) +{ + if(ext) { + ext->magic = KNOWNHOST_MAGIC; + ext->node = node; + ext->name = ((node->typemask & LIBSSH2_KNOWNHOST_TYPE_MASK) == + LIBSSH2_KNOWNHOST_TYPE_PLAIN)? node->name:NULL; + ext->key = node->key; + ext->typemask = node->typemask; + } +} + +/* + * libssh2_knownhost_check + * + * Check a host and its associated key against the collection of known hosts. + * + * The typemask is the type/format of the given host name and key + * + * plain - ascii "hostname.domain.tld" + * sha1 - NOT SUPPORTED AS INPUT + * custom - prehashed base64 encoded. Note that this cannot use any salts. + * + * Returns: + * + * LIBSSH2_KNOWNHOST_CHECK_FAILURE + * LIBSSH2_KNOWNHOST_CHECK_NOTFOUND + * LIBSSH2_KNOWNHOST_CHECK_MATCH + * LIBSSH2_KNOWNHOST_CHECK_MISMATCH + */ +LIBSSH2_API int +libssh2_knownhost_check(LIBSSH2_KNOWNHOSTS *hosts, + char *host, char *key, size_t keylen, + int typemask, + struct libssh2_knownhost *knownhost) +{ + struct known_host *node = _libssh2_list_first(&hosts->head); + struct known_host *badkey = NULL; + int type = typemask & LIBSSH2_KNOWNHOST_TYPE_MASK; + char *keyalloc = NULL; + int rc = LIBSSH2_KNOWNHOST_CHECK_NOTFOUND; + + if(type == LIBSSH2_KNOWNHOST_TYPE_SHA1) + /* we can't work with a sha1 as given input */ + return LIBSSH2_KNOWNHOST_CHECK_MISMATCH; + + if(!(typemask & LIBSSH2_KNOWNHOST_KEY_BASE64)) { + /* we got a raw key input, convert it to base64 for the checks below */ + size_t nlen = _libssh2_base64_encode(hosts->session, key, keylen, + &keyalloc); + if(!nlen) + return LIBSSH2_KNOWNHOST_CHECK_FAILURE; + + /* make the key point to this */ + key = keyalloc; + keylen = nlen; + } + + while (node) { + int match = 0; + switch(node->typemask & LIBSSH2_KNOWNHOST_TYPE_MASK) { + case LIBSSH2_KNOWNHOST_TYPE_PLAIN: + if(type == LIBSSH2_KNOWNHOST_TYPE_PLAIN) + match = !strcmp(host, node->name); + break; + case LIBSSH2_KNOWNHOST_TYPE_CUSTOM: + if(type == LIBSSH2_KNOWNHOST_TYPE_CUSTOM) + match = !strcmp(host, node->name); + break; + case LIBSSH2_KNOWNHOST_TYPE_SHA1: + if(type == LIBSSH2_KNOWNHOST_TYPE_PLAIN) { + /* when we have the sha1 version stored, we can use a plain + input to produce a hash to compare with the stored hash. + + HMAC_Init(&mac_ctx, salt, len, md); + HMAC_Update(&mac_ctx, host, strlen(host)); + HMAC_Final(&mac_ctx, result, NULL); + HMAC_cleanup(&mac_ctx); + + */ + libssh2_hmac_ctx ctx; + unsigned char hash[SHA_DIGEST_LENGTH]; + + if(SHA_DIGEST_LENGTH != node->name_len) { + /* the name hash length must be the sha1 size or + we can't match it */ + break; + } + libssh2_hmac_sha1_init(&ctx, node->salt, node->salt_len); + libssh2_hmac_update(ctx, (unsigned char *)host, strlen(host)); + libssh2_hmac_final(ctx, hash); + libssh2_hmac_cleanup(&ctx); + + if(!memcmp(hash, node->name, SHA_DIGEST_LENGTH)) + /* this is a node we're interested in */ + match = 1; + } + break; + default: /* unsupported type */ + break; + } + if(match) { + /* host name match, now compare the keys */ + if(!strcmp(key, node->key)) { + /* they match! */ + knownhost_to_external(node, knownhost); + badkey = NULL; + rc = LIBSSH2_KNOWNHOST_CHECK_MATCH; + break; + } + else { + /* remember the first node that had a host match but a failed + key match since we continue our search from here */ + if(!badkey) + badkey = node; + } + } + node= _libssh2_list_next(&node->node); + } + + if(badkey) { + /* key mismatch */ + knownhost_to_external(badkey, knownhost); + rc = LIBSSH2_KNOWNHOST_CHECK_MISMATCH; + } + + if(keyalloc) + LIBSSH2_FREE(hosts->session, keyalloc); + + return rc; +} + +/* + * libssh2_knownhost_del + * + * Remove a host from the collection of known hosts. + * + */ +LIBSSH2_API int +libssh2_knownhost_del(LIBSSH2_KNOWNHOSTS *hosts, + struct libssh2_knownhost *entry) +{ + struct known_host *node; + if(!entry || (entry->magic != KNOWNHOST_MAGIC)) + /* check that this was retrieved the right way or get out */ + return -1; + + /* get the internal node pointer */ + node = entry->node; + + /* unlink from the list of all hosts */ + _libssh2_list_remove(&node->node); + + /* free all resources */ + free_host(hosts->session, node); + + return 0; +} + +/* + * libssh2_knownhost_free + * + * Free an entire collection of known hosts. + * + */ +LIBSSH2_API void +libssh2_knownhost_free(LIBSSH2_KNOWNHOSTS *hosts) +{ + struct known_host *node; + struct known_host *next; + + for(node = _libssh2_list_first(&hosts->head); node; node = next) { + next = _libssh2_list_next(&node->node); + free_host(hosts->session, node); + } + LIBSSH2_FREE(hosts->session, hosts); +} + +/* + * hostline() + * + * Parse a single known_host line pre-split into host and key. + * + * Note: this function assumes that the 'host' pointer points into a temporary + * buffer as it will write to it. + */ +static int hostline(LIBSSH2_KNOWNHOSTS *hosts, + char *host, size_t hostlen, + char *key, size_t keylen) +{ + char *p; + char *salt = NULL; + int rc; + int type = LIBSSH2_KNOWNHOST_TYPE_PLAIN; + char *sep = NULL; + + /* Figure out host format */ + if(strncmp(host, "|1|", 3)) { + /* old style plain text: [name][,][ip-address] + + for the sake of simplicity, we add them as two hosts with the same + key + */ + sep = strchr(host, ','); + } + else { + /* |1|[salt]|[hash] */ + type = LIBSSH2_KNOWNHOST_TYPE_SHA1; + + salt = &host[3]; /* skip the magic marker */ + + /* this is where the salt starts, find the end of it */ + for(p = salt; *p && (*p != '|'); p++) + ; + + if(*p=='|') { + char *hash = NULL; + *p=0; /* terminate the salt string */ + hash = p+1; /* the hash is after the separator */ + + /* now make the host point to the hash */ + hostlen = strlen(hash); + host = hash; + } + else + return 0; + } + + if(keylen < 20) + return -1; /* TODO: better return code */ + + switch(key[0]) { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + type |= LIBSSH2_KNOWNHOST_KEY_RSA1; + + /* Note that the old-style keys (RSA1) aren't truly base64, but we + * claim it is for now since we can get away with strcmp()ing the + * entire anything anyway! We need to check and fix these to make them + * work properly. + */ + break; + + case 's': /* ssh-dss or ssh-rsa */ + if(!strncmp(key, "ssh-dss", 7)) + type |= LIBSSH2_KNOWNHOST_KEY_SSHDSS; + else if(!strncmp(key, "ssh-rsa", 7)) + type |= LIBSSH2_KNOWNHOST_KEY_SSHRSA; + else + return -1; /* unknown */ + + key += 7; + keylen -= 7; + + /* skip whitespaces */ + while((*key ==' ') || (*key == '\t')) { + key++; + keylen--; + } + break; + + default: /* unknown key format */ + return -1; + } + + if(sep) { + /* this is the second host, add this first */ + char *ipaddr; + *sep++ = 0; /* zero terminate the first host name here */ + ipaddr = sep; + rc = libssh2_knownhost_add(hosts, ipaddr, salt, key, keylen, + type | LIBSSH2_KNOWNHOST_KEY_BASE64); + if(rc) + return rc; + } + + rc = libssh2_knownhost_add(hosts, host, salt, key, keylen, + type | LIBSSH2_KNOWNHOST_KEY_BASE64); + + return rc; +} + +/* + * libssh2_knownhost_parsefile + * + * Add hosts+key pairs from a given file. + * + * Returns a negative value for error or number of successfully added hosts. + * + * Line format: + * + * + * + * Where the two parts can be created like: + * + * can be either + * or + * + * consists of + * [name,address] or just [name] or just [address] + * + * consists of + * |1||hash + * + * can be one of: + * [RSA bits] [e] [n as a decimal number] + * 'ssh-dss' [base64-encoded-key] + * 'ssh-rsa' [base64-encoded-key] + * + */ + +#define LIBSSH2_KNOWNHOST_FILE_OPENSSH 1 + +LIBSSH2_API int +libssh2_knownhost_parsefile(LIBSSH2_KNOWNHOSTS *hosts, + const char *filename, int type) +{ + FILE *file; + int num = 0; + char buf[2048]; + + if(type != LIBSSH2_KNOWNHOST_FILE_OPENSSH) + return -1; + + file = fopen(filename, "r"); + if(file) { + char *cp; + char *hostp; + char *key; + size_t hostlen; + + while(fgets(buf, sizeof(buf), file)) { + cp = buf; + + /* skip leading whitespaces */ + while((*cp==' ') || (*cp == '\t')) + cp++; + + if(!*cp || (*cp == '#') || (*cp == '\n')) + /* comment or empty line */ + continue; + + /* the host part starts here */ + hostp = cp; + + /* move over the host to the separator */ + while(*cp && (*cp!=' ') && (*cp != '\t')) + cp++; + + hostlen = cp - hostp; + + *cp++ = 0; /* terminate the host string here */ + + /* the key starts after the whitespaces */ + while(*cp && ((*cp==' ') || (*cp == '\t'))) + cp++; + + if(!*cp) + /* illegal line */ + continue; + + key = cp; /* the key starts here */ + + while(*cp && (*cp != '\n')) + cp++; + + /* zero terminate where the newline is */ + if(*cp == '\n') + *cp = 0; + + /* deal with this one host+key line */ + if(!hostline(hosts, hostp, hostlen, key, strlen(key))) + num++; + } + fclose(file); + } + else + return -1; + return num; +}