summaryrefslogtreecommitdiff
path: root/libs/libssh2/src/knownhost.c
diff options
context:
space:
mode:
Diffstat (limited to 'libs/libssh2/src/knownhost.c')
-rw-r--r--libs/libssh2/src/knownhost.c1245
1 files changed, 1245 insertions, 0 deletions
diff --git a/libs/libssh2/src/knownhost.c b/libs/libssh2/src/knownhost.c
new file mode 100644
index 0000000000..a32dcf8764
--- /dev/null
+++ b/libs/libssh2/src/knownhost.c
@@ -0,0 +1,1245 @@
+/*
+ * Copyright (c) 2009-2014 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_priv.h"
+#include "misc.h"
+
+struct known_host {
+ struct list_node node;
+ char *name; /* points to the name or the hash (allocated) */
+ size_t name_len; /* needed for hashed data */
+ int port; /* if non-zero, a specific port this key is for on this
+ host */
+ int typemask; /* plain, sha1, custom, ... */
+ char *salt; /* points to binary salt (allocated) */
+ size_t salt_len; /* size of salt */
+ char *key; /* the (allocated) associated key. This is kept base64
+ encoded in memory. */
+ char *key_type_name; /* the (allocated) key type name */
+ size_t key_type_len; /* size of key_type_name */
+ char *comment; /* the (allocated) optional comment text, may be
+ NULL */
+ size_t comment_len; /* the size of comment */
+
+ /* this is the struct we expose externally */
+ struct libssh2_knownhost external;
+};
+
+struct _LIBSSH2_KNOWNHOSTS
+{
+ LIBSSH2_SESSION *session; /* the session this "belongs to" */
+ struct list_head head;
+};
+
+static void free_host(LIBSSH2_SESSION *session, struct known_host *entry)
+{
+ if(entry) {
+ if(entry->comment)
+ LIBSSH2_FREE(session, entry->comment);
+ if (entry->key_type_name)
+ LIBSSH2_FREE(session, entry->key_type_name);
+ 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) {
+ _libssh2_error(session, LIBSSH2_ERROR_ALLOC,
+ "Unable to allocate memory for known-hosts "
+ "collection");
+ return NULL;
+ }
+
+ knh->session = session;
+
+ _libssh2_list_init(&knh->head);
+
+ return knh;
+}
+
+#define KNOWNHOST_MAGIC 0xdeadcafe
+/*
+ * knownhost_to_external()
+ *
+ * Copies data from the internal to the external representation struct.
+ *
+ */
+static struct libssh2_knownhost *knownhost_to_external(struct known_host *node)
+{
+ struct libssh2_knownhost *ext = &node->external;
+
+ 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;
+
+ return ext;
+}
+
+static int
+knownhost_add(LIBSSH2_KNOWNHOSTS *hosts,
+ const char *host, const char *salt,
+ const char *key_type_name, size_t key_type_len,
+ const char *key, size_t keylen,
+ const char *comment, size_t commentlen,
+ int typemask, struct libssh2_knownhost **store)
+{
+ struct known_host *entry;
+ size_t hostlen = strlen(host);
+ int rc;
+ char *ptr;
+ unsigned int ptrlen;
+
+ /* make sure we have a key type set */
+ if(!(typemask & LIBSSH2_KNOWNHOST_KEY_MASK))
+ return _libssh2_error(hosts->session, LIBSSH2_ERROR_INVAL,
+ "No key type set");
+
+ if(!(entry = LIBSSH2_CALLOC(hosts->session, sizeof(struct known_host))))
+ return _libssh2_error(hosts->session, LIBSSH2_ERROR_ALLOC,
+ "Unable to allocate memory for known host "
+ "entry");
+
+ 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->name) {
+ rc = _libssh2_error(hosts->session, LIBSSH2_ERROR_ALLOC,
+ "Unable to allocate memory for host name");
+ goto error;
+ }
+ memcpy(entry->name, host, hostlen+1);
+ entry->name_len = hostlen;
+ 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(hosts->session, LIBSSH2_ERROR_METHOD_NOT_SUPPORTED,
+ "Unknown host name type");
+ goto error;
+ }
+
+ if(typemask & LIBSSH2_KNOWNHOST_KEYENC_BASE64) {
+ /* the provided key is base64 encoded already */
+ if(!keylen)
+ keylen = strlen(key);
+ entry->key = LIBSSH2_ALLOC(hosts->session, keylen+1);
+ if(!entry->key) {
+ rc = _libssh2_error(hosts->session, LIBSSH2_ERROR_ALLOC,
+ "Unable to allocate memory for key");
+ goto error;
+ }
+ memcpy(entry->key, key, keylen+1);
+ entry->key[keylen]=0; /* force a terminating zero trailer */
+ }
+ 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) {
+ rc = _libssh2_error(hosts->session, LIBSSH2_ERROR_ALLOC,
+ "Unable to allocate memory for "
+ "base64-encoded key");
+ goto error;
+ }
+
+ entry->key = ptr;
+ }
+
+ if (key_type_name && ((typemask & LIBSSH2_KNOWNHOST_KEY_MASK) ==
+ LIBSSH2_KNOWNHOST_KEY_UNKNOWN)) {
+ entry->key_type_name = LIBSSH2_ALLOC(hosts->session, key_type_len+1);
+ if (!entry->key_type_name) {
+ rc = _libssh2_error(hosts->session, LIBSSH2_ERROR_ALLOC,
+ "Unable to allocate memory for key type");
+ goto error;
+ }
+ memcpy(entry->key_type_name, key_type_name, key_type_len);
+ entry->key_type_name[key_type_len]=0;
+ entry->key_type_len = key_type_len;
+ }
+
+ if (comment) {
+ entry->comment = LIBSSH2_ALLOC(hosts->session, commentlen+1);
+ if(!entry->comment) {
+ rc = _libssh2_error(hosts->session, LIBSSH2_ERROR_ALLOC,
+ "Unable to allocate memory for comment");
+ goto error;
+ }
+ memcpy(entry->comment, comment, commentlen+1);
+ entry->comment[commentlen]=0; /* force a terminating zero trailer */
+ entry->comment_len = commentlen;
+ }
+ else {
+ entry->comment = NULL;
+ }
+
+ /* add this new host to the big list of known hosts */
+ _libssh2_list_add(&hosts->head, &entry->node);
+
+ if(store)
+ *store = knownhost_to_external(entry);
+
+ return LIBSSH2_ERROR_NONE;
+ error:
+ free_host(hosts->session, entry);
+ return rc;
+}
+
+/*
+ * 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(<salt> <host>) 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.
+ *
+ * The keylen parameter may be omitted (zero) if the key is provided as a
+ * NULL-terminated base64-encoded string.
+ */
+
+LIBSSH2_API int
+libssh2_knownhost_add(LIBSSH2_KNOWNHOSTS *hosts,
+ const char *host, const char *salt,
+ const char *key, size_t keylen,
+ int typemask, struct libssh2_knownhost **store)
+{
+ return knownhost_add(hosts, host, salt, NULL, 0, key, keylen, NULL,
+ 0, typemask, store);
+}
+
+
+/*
+ * libssh2_knownhost_addc
+ *
+ * Add a host and its associated key to the collection of known hosts.
+ *
+ * Takes a comment argument that may be NULL. A NULL comment indicates
+ * there is no comment and the entry will end directly after the key
+ * when written out to a file. An empty string "" comment will indicate an
+ * empty comment which will cause a single space to be written after the key.
+ *
+ * The 'type' argument specifies on what format the given host and keys are:
+ *
+ * plain - ascii "hostname.domain.tld"
+ * sha1 - SHA1(<salt> <host>) 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.
+ *
+ * The keylen parameter may be omitted (zero) if the key is provided as a
+ * NULL-terminated base64-encoded string.
+ */
+
+LIBSSH2_API int
+libssh2_knownhost_addc(LIBSSH2_KNOWNHOSTS *hosts,
+ const char *host, const char *salt,
+ const char *key, size_t keylen,
+ const char *comment, size_t commentlen,
+ int typemask, struct libssh2_knownhost **store)
+{
+ return knownhost_add(hosts, host, salt, NULL, 0, key, keylen,
+ comment, commentlen, typemask, store);
+}
+
+/*
+ * 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
+ */
+static int
+knownhost_check(LIBSSH2_KNOWNHOSTS *hosts,
+ const char *hostp, int port,
+ const char *key, size_t keylen,
+ int typemask,
+ struct libssh2_knownhost **ext)
+{
+ struct known_host *node;
+ struct known_host *badkey = NULL;
+ int type = typemask & LIBSSH2_KNOWNHOST_TYPE_MASK;
+ char *keyalloc = NULL;
+ int rc = LIBSSH2_KNOWNHOST_CHECK_NOTFOUND;
+ char hostbuff[270]; /* most host names can't be longer than like 256 */
+ const char *host;
+ int numcheck; /* number of host combos to check */
+ int match = 0;
+
+ if(type == LIBSSH2_KNOWNHOST_TYPE_SHA1)
+ /* we can't work with a sha1 as given input */
+ return LIBSSH2_KNOWNHOST_CHECK_MISMATCH;
+
+ /* if a port number is given, check for a '[host]:port' first before the
+ plain 'host' */
+ if(port >= 0) {
+ int len = snprintf(hostbuff, sizeof(hostbuff), "[%s]:%d", hostp, port);
+ if (len < 0 || len >= (int)sizeof(hostbuff)) {
+ _libssh2_error(hosts->session,
+ LIBSSH2_ERROR_BUFFER_TOO_SMALL,
+ "Known-host write buffer too small");
+ return LIBSSH2_KNOWNHOST_CHECK_FAILURE;
+ }
+ host = hostbuff;
+ numcheck = 2; /* check both combos, start with this */
+ }
+ else {
+ host = hostp;
+ numcheck = 1; /* only check this host version */
+ }
+
+ if(!(typemask & LIBSSH2_KNOWNHOST_KEYENC_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) {
+ _libssh2_error(hosts->session, LIBSSH2_ERROR_ALLOC,
+ "Unable to allocate memory for base64-encoded "
+ "key");
+ return LIBSSH2_KNOWNHOST_CHECK_FAILURE;
+ }
+
+ /* make the key point to this */
+ key = keyalloc;
+ }
+
+ do {
+ node = _libssh2_list_first(&hosts->head);
+ while (node) {
+ 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.
+ */
+ unsigned char hash[SHA_DIGEST_LENGTH];
+ libssh2_hmac_ctx ctx;
+ libssh2_hmac_ctx_init(ctx);
+
+ 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, (unsigned char *)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) {
+ int host_key_type = typemask & LIBSSH2_KNOWNHOST_KEY_MASK;
+ int known_key_type =
+ node->typemask & LIBSSH2_KNOWNHOST_KEY_MASK;
+ /* match on key type as follows:
+ - never match on an unknown key type
+ - if key_type is set to zero, ignore it an match always
+ - otherwise match when both key types are equal
+ */
+ if ( (host_key_type != LIBSSH2_KNOWNHOST_KEY_UNKNOWN ) &&
+ ( (host_key_type == 0) ||
+ (host_key_type == known_key_type) ) ) {
+ /* host name and key type match, now compare the keys */
+ if(!strcmp(key, node->key)) {
+ /* they match! */
+ if (ext)
+ *ext = knownhost_to_external(node);
+ 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;
+ }
+ }
+ match = 0; /* don't count this as a match anymore */
+ }
+ node= _libssh2_list_next(&node->node);
+ }
+ host = hostp;
+ } while(!match && --numcheck);
+
+ if(badkey) {
+ /* key mismatch */
+ if (ext)
+ *ext = knownhost_to_external(badkey);
+ rc = LIBSSH2_KNOWNHOST_CHECK_MISMATCH;
+ }
+
+ if(keyalloc)
+ LIBSSH2_FREE(hosts->session, keyalloc);
+
+ return rc;
+}
+
+/*
+ * 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,
+ const char *hostp, const char *key, size_t keylen,
+ int typemask,
+ struct libssh2_knownhost **ext)
+{
+ return knownhost_check(hosts, hostp, -1, key, keylen,
+ typemask, ext);
+}
+
+/*
+ * libssh2_knownhost_checkp
+ *
+ * Check a host+port and its associated key against the collection of known
+ * hosts.
+ *
+ * Note that if 'port' is specified as greater than zero, the check function
+ * will be able to check for a dedicated key for this particular host+port
+ * combo, and if 'port' is negative it only checks for the generic host key.
+ *
+ * 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_checkp(LIBSSH2_KNOWNHOSTS *hosts,
+ const char *hostp, int port,
+ const char *key, size_t keylen,
+ int typemask,
+ struct libssh2_knownhost **ext)
+{
+ return knownhost_check(hosts, hostp, port, key, keylen,
+ typemask, ext);
+}
+
+
+/*
+ * 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;
+
+ /* check that this was retrieved the right way or get out */
+ if(!entry || (entry->magic != KNOWNHOST_MAGIC))
+ return _libssh2_error(hosts->session, LIBSSH2_ERROR_INVAL,
+ "Invalid host information");
+
+ /* get the internal node pointer */
+ node = entry->node;
+
+ /* unlink from the list of all hosts */
+ _libssh2_list_remove(&node->node);
+
+ /* clear the struct now since the memory in which it is allocated is
+ about to be freed! */
+ memset(entry, 0, sizeof(struct libssh2_knownhost));
+
+ /* 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);
+}
+
+
+/* old style plain text: [name]([,][name])*
+
+ for the sake of simplicity, we add them as separate hosts with the same
+ key
+*/
+static int oldstyle_hostline(LIBSSH2_KNOWNHOSTS *hosts,
+ const char *host, size_t hostlen,
+ const char *key_type_name, size_t key_type_len,
+ const char *key, size_t keylen, int key_type,
+ const char *comment, size_t commentlen)
+{
+ int rc = 0;
+ size_t namelen = 0;
+ const char *name = host + hostlen;
+
+ if(hostlen < 1)
+ return _libssh2_error(hosts->session,
+ LIBSSH2_ERROR_METHOD_NOT_SUPPORTED,
+ "Failed to parse known_hosts line "
+ "(no host names)");
+
+ while(name > host) {
+ --name;
+ ++namelen;
+
+ /* when we get the the start or see a comma coming up, add the host
+ name to the collection */
+ if((name == host) || (*(name-1) == ',')) {
+
+ char hostbuf[256];
+
+ /* make sure we don't overflow the buffer */
+ if(namelen >= sizeof(hostbuf)-1)
+ return _libssh2_error(hosts->session,
+ LIBSSH2_ERROR_METHOD_NOT_SUPPORTED,
+ "Failed to parse known_hosts line "
+ "(unexpected length)");
+
+ /* copy host name to the temp buffer and zero terminate */
+ memcpy(hostbuf, name, namelen);
+ hostbuf[namelen]=0;
+
+ rc = knownhost_add(hosts, hostbuf, NULL,
+ key_type_name, key_type_len,
+ key, keylen,
+ comment, commentlen,
+ key_type | LIBSSH2_KNOWNHOST_TYPE_PLAIN |
+ LIBSSH2_KNOWNHOST_KEYENC_BASE64, NULL);
+ if(rc)
+ return rc;
+
+ if(name > host) {
+ namelen = 0;
+ --name; /* skip comma */
+ }
+ }
+ }
+
+ return rc;
+}
+
+/* |1|[salt]|[hash] */
+static int hashed_hostline(LIBSSH2_KNOWNHOSTS *hosts,
+ const char *host, size_t hostlen,
+ const char *key_type_name, size_t key_type_len,
+ const char *key, size_t keylen, int key_type,
+ const char *comment, size_t commentlen)
+{
+ const char *p;
+ char saltbuf[32];
+ char hostbuf[256];
+
+ const char *salt = &host[3]; /* skip the magic marker */
+ hostlen -= 3; /* deduct the marker */
+
+ /* this is where the salt starts, find the end of it */
+ for(p = salt; *p && (*p != '|'); p++)
+ ;
+
+ if(*p=='|') {
+ const char *hash = NULL;
+ size_t saltlen = p - salt;
+ if(saltlen >= (sizeof(saltbuf)-1)) /* weird length */
+ return _libssh2_error(hosts->session,
+ LIBSSH2_ERROR_METHOD_NOT_SUPPORTED,
+ "Failed to parse known_hosts line "
+ "(unexpectedly long salt)");
+
+ memcpy(saltbuf, salt, saltlen);
+ saltbuf[saltlen] = 0; /* zero terminate */
+ salt = saltbuf; /* point to the stack based buffer */
+
+ hash = p+1; /* the host hash is after the separator */
+
+ /* now make the host point to the hash */
+ host = hash;
+ hostlen -= saltlen+1; /* deduct the salt and separator */
+
+ /* check that the lengths seem sensible */
+ if(hostlen >= sizeof(hostbuf)-1)
+ return _libssh2_error(hosts->session,
+ LIBSSH2_ERROR_METHOD_NOT_SUPPORTED,
+ "Failed to parse known_hosts line "
+ "(unexpected length)");
+
+ memcpy(hostbuf, host, hostlen);
+ hostbuf[hostlen]=0;
+
+ return knownhost_add(hosts, hostbuf, salt,
+ key_type_name, key_type_len,
+ key, keylen,
+ comment, commentlen,
+ key_type | LIBSSH2_KNOWNHOST_TYPE_SHA1 |
+ LIBSSH2_KNOWNHOST_KEYENC_BASE64, NULL);
+ }
+ else
+ return 0; /* XXX: This should be an error, shouldn't it? */
+}
+
+/*
+ * hostline()
+ *
+ * Parse a single known_host line pre-split into host and key.
+ *
+ * The key part may include an optional comment which will be parsed here
+ * for ssh-rsa and ssh-dsa keys. Comments in other key types aren't handled.
+ *
+ * The function assumes new-lines have already been removed from the arguments.
+ */
+static int hostline(LIBSSH2_KNOWNHOSTS *hosts,
+ const char *host, size_t hostlen,
+ const char *key, size_t keylen)
+{
+ const char *comment = NULL;
+ const char *key_type_name = NULL;
+ size_t commentlen = 0;
+ size_t key_type_len = 0;
+ int key_type;
+
+ /* make some checks that the lengths seem sensible */
+ if(keylen < 20)
+ return _libssh2_error(hosts->session,
+ LIBSSH2_ERROR_METHOD_NOT_SUPPORTED,
+ "Failed to parse known_hosts line "
+ "(key too short)");
+
+ switch(key[0]) {
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ key_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;
+
+ default:
+ key_type_name = key;
+ while (keylen && *key &&
+ (*key != ' ') && (*key != '\t')) {
+ key++;
+ keylen--;
+ }
+ key_type_len = key - key_type_name;
+
+ if (!strncmp(key_type_name, "ssh-dss", key_type_len))
+ key_type = LIBSSH2_KNOWNHOST_KEY_SSHDSS;
+ else if (!strncmp(key_type_name, "ssh-rsa", key_type_len))
+ key_type = LIBSSH2_KNOWNHOST_KEY_SSHRSA;
+ else
+ key_type = LIBSSH2_KNOWNHOST_KEY_UNKNOWN;
+
+ /* skip whitespaces */
+ while((*key ==' ') || (*key == '\t')) {
+ key++;
+ keylen--;
+ }
+
+ comment = key;
+ commentlen = keylen;
+
+ /* move over key */
+ while(commentlen && *comment &&
+ (*comment != ' ') && (*comment != '\t')) {
+ comment++;
+ commentlen--;
+ }
+
+ /* reduce key by comment length */
+ keylen -= commentlen;
+
+ /* Distinguish empty comment (a space) from no comment (no space) */
+ if (commentlen == 0)
+ comment = NULL;
+
+ /* skip whitespaces */
+ while(commentlen && *comment &&
+ ((*comment ==' ') || (*comment == '\t'))) {
+ comment++;
+ commentlen--;
+ }
+ break;
+ }
+
+ /* Figure out host format */
+ if((hostlen >2) && memcmp(host, "|1|", 3)) {
+ /* old style plain text: [name]([,][name])*
+
+ for the sake of simplicity, we add them as separate hosts with the
+ same key
+ */
+ return oldstyle_hostline(hosts, host, hostlen, key_type_name,
+ key_type_len, key, keylen, key_type,
+ comment, commentlen);
+ }
+ else {
+ /* |1|[salt]|[hash] */
+ return hashed_hostline(hosts, host, hostlen, key_type_name,
+ key_type_len, key, keylen, key_type,
+ comment, commentlen);
+ }
+}
+
+/*
+ * libssh2_knownhost_readline()
+ *
+ * Pass in a line of a file of 'type'.
+ *
+ * LIBSSH2_KNOWNHOST_FILE_OPENSSH is the only supported type.
+ *
+ * OpenSSH line format:
+ *
+ * <host> <key>
+ *
+ * Where the two parts can be created like:
+ *
+ * <host> can be either
+ * <name> or <hash>
+ *
+ * <name> consists of
+ * [name] optionally followed by [,name] one or more times
+ *
+ * <hash> consists of
+ * |1|<salt>|hash
+ *
+ * <key> can be one of:
+ * [RSA bits] [e] [n as a decimal number]
+ * 'ssh-dss' [base64-encoded-key]
+ * 'ssh-rsa' [base64-encoded-key]
+ *
+ */
+LIBSSH2_API int
+libssh2_knownhost_readline(LIBSSH2_KNOWNHOSTS *hosts,
+ const char *line, size_t len, int type)
+{
+ const char *cp;
+ const char *hostp;
+ const char *keyp;
+ size_t hostlen;
+ size_t keylen;
+ int rc;
+
+ if(type != LIBSSH2_KNOWNHOST_FILE_OPENSSH)
+ return _libssh2_error(hosts->session,
+ LIBSSH2_ERROR_METHOD_NOT_SUPPORTED,
+ "Unsupported type of known-host information "
+ "store");
+
+ cp = line;
+
+ /* skip leading whitespaces */
+ while(len && ((*cp==' ') || (*cp == '\t'))) {
+ cp++;
+ len--;
+ }
+
+ if(!len || !*cp || (*cp == '#') || (*cp == '\n'))
+ /* comment or empty line */
+ return LIBSSH2_ERROR_NONE;
+
+ /* the host part starts here */
+ hostp = cp;
+
+ /* move over the host to the separator */
+ while(len && *cp && (*cp!=' ') && (*cp != '\t')) {
+ cp++;
+ len--;
+ }
+
+ hostlen = cp - hostp;
+
+ /* the key starts after the whitespaces */
+ while(len && *cp && ((*cp==' ') || (*cp == '\t'))) {
+ cp++;
+ len--;
+ }
+
+ if(!*cp || !len) /* illegal line */
+ return _libssh2_error(hosts->session,
+ LIBSSH2_ERROR_METHOD_NOT_SUPPORTED,
+ "Failed to parse known_hosts line");
+
+ keyp = cp; /* the key starts here */
+ keylen = len;
+
+ /* check if the line (key) ends with a newline and if so kill it */
+ while(len && *cp && (*cp != '\n')) {
+ cp++;
+ len--;
+ }
+
+ /* zero terminate where the newline is */
+ if(*cp == '\n')
+ keylen--; /* don't include this in the count */
+
+ /* deal with this one host+key line */
+ rc = hostline(hosts, hostp, hostlen, keyp, keylen);
+ if(rc)
+ return rc; /* failed */
+
+ return LIBSSH2_ERROR_NONE; /* success */
+}
+
+/*
+ * libssh2_knownhost_readfile
+ *
+ * Read hosts+key pairs from a given file.
+ *
+ * Returns a negative value for error or number of successfully added hosts.
+ *
+ */
+
+LIBSSH2_API int
+libssh2_knownhost_readfile(LIBSSH2_KNOWNHOSTS *hosts,
+ const char *filename, int type)
+{
+ FILE *file;
+ int num = 0;
+ char buf[2048];
+
+ if(type != LIBSSH2_KNOWNHOST_FILE_OPENSSH)
+ return _libssh2_error(hosts->session,
+ LIBSSH2_ERROR_METHOD_NOT_SUPPORTED,
+ "Unsupported type of known-host information "
+ "store");
+
+ file = fopen(filename, "r");
+ if(file) {
+ while(fgets(buf, sizeof(buf), file)) {
+ if(libssh2_knownhost_readline(hosts, buf, strlen(buf), type)) {
+ num = _libssh2_error(hosts->session, LIBSSH2_ERROR_KNOWN_HOSTS,
+ "Failed to parse known hosts file");
+ break;
+ }
+ num++;
+ }
+ fclose(file);
+ }
+ else
+ return _libssh2_error(hosts->session, LIBSSH2_ERROR_FILE,
+ "Failed to open file");
+
+ return num;
+}
+
+/*
+ * knownhost_writeline()
+ *
+ * Ask libssh2 to convert a known host to an output line for storage.
+ *
+ * Note that this function returns LIBSSH2_ERROR_BUFFER_TOO_SMALL if the given
+ * output buffer is too small to hold the desired output. The 'outlen' field
+ * will then contain the size libssh2 wanted to store, which then is the
+ * smallest sufficient buffer it would require.
+ *
+ */
+static int
+knownhost_writeline(LIBSSH2_KNOWNHOSTS *hosts,
+ struct known_host *node,
+ char *buf, size_t buflen,
+ size_t *outlen, int type)
+{
+ size_t required_size;
+
+ const char *key_type_name;
+ size_t key_type_len;
+
+ /* we only support this single file type for now, bail out on all other
+ attempts */
+ if(type != LIBSSH2_KNOWNHOST_FILE_OPENSSH)
+ return _libssh2_error(hosts->session,
+ LIBSSH2_ERROR_METHOD_NOT_SUPPORTED,
+ "Unsupported type of known-host information "
+ "store");
+
+ switch(node->typemask & LIBSSH2_KNOWNHOST_KEY_MASK) {
+ case LIBSSH2_KNOWNHOST_KEY_RSA1:
+ key_type_name = NULL;
+ key_type_len = 0;
+ break;
+ case LIBSSH2_KNOWNHOST_KEY_SSHRSA:
+ key_type_name = "ssh-rsa";
+ key_type_len = 7;
+ break;
+ case LIBSSH2_KNOWNHOST_KEY_SSHDSS:
+ key_type_name = "ssh-dss";
+ key_type_len = 7;
+ break;
+ case LIBSSH2_KNOWNHOST_KEY_UNKNOWN:
+ key_type_name = node->key_type_name;
+ if (key_type_name) {
+ key_type_len = node->key_type_len;
+ break;
+ }
+ /* otherwise fallback to default and error */
+ default:
+ return _libssh2_error(hosts->session,
+ LIBSSH2_ERROR_METHOD_NOT_SUPPORTED,
+ "Unsupported type of known-host entry");
+ }
+
+ /* When putting together the host line there are three aspects to consider:
+ - Hashed (SHA1) or unhashed hostname
+ - key name or no key name (RSA1)
+ - comment or no comment
+
+ This means there are 2^3 different formats:
+ ("|1|%s|%s %s %s %s\n", salt, hashed_host, key_name, key, comment)
+ ("|1|%s|%s %s %s\n", salt, hashed_host, key_name, key)
+ ("|1|%s|%s %s %s\n", salt, hashed_host, key, comment)
+ ("|1|%s|%s %s\n", salt, hashed_host, key)
+ ("%s %s %s %s\n", host, key_name, key, comment)
+ ("%s %s %s\n", host, key_name, key)
+ ("%s %s %s\n", host, key, comment)
+ ("%s %s\n", host, key)
+
+ Even if the buffer is too small, we have to set outlen to the number of
+ characters the complete line would have taken. We also don't write
+ anything to the buffer unless we are sure we can write everything to the
+ buffer. */
+
+ required_size = strlen(node->key);
+
+ if(key_type_len)
+ required_size += key_type_len + 1; /* ' ' = 1 */
+ if(node->comment)
+ required_size += node->comment_len + 1; /* ' ' = 1 */
+
+ if((node->typemask & LIBSSH2_KNOWNHOST_TYPE_MASK) ==
+ LIBSSH2_KNOWNHOST_TYPE_SHA1) {
+ char *namealloc;
+ size_t name_base64_len;
+ char *saltalloc;
+ size_t salt_base64_len;
+
+ name_base64_len = _libssh2_base64_encode(hosts->session, node->name,
+ node->name_len, &namealloc);
+ if(!name_base64_len)
+ return _libssh2_error(hosts->session, LIBSSH2_ERROR_ALLOC,
+ "Unable to allocate memory for "
+ "base64-encoded host name");
+
+ salt_base64_len = _libssh2_base64_encode(hosts->session,
+ node->salt, node->salt_len,
+ &saltalloc);
+ if(!salt_base64_len) {
+ LIBSSH2_FREE(hosts->session, namealloc);
+ return _libssh2_error(hosts->session, LIBSSH2_ERROR_ALLOC,
+ "Unable to allocate memory for "
+ "base64-encoded salt");
+ }
+
+ required_size += salt_base64_len + name_base64_len + 7;
+ /* |1| + | + ' ' + \n + \0 = 7 */
+
+ if(required_size <= buflen) {
+ if(node->comment && key_type_len)
+ snprintf(buf, buflen, "|1|%s|%s %s %s %s\n", saltalloc,
+ namealloc, key_type_name, node->key, node->comment);
+ else if (node->comment)
+ snprintf(buf, buflen, "|1|%s|%s %s %s\n", saltalloc, namealloc,
+ node->key, node->comment);
+ else if (key_type_len)
+ snprintf(buf, buflen, "|1|%s|%s %s %s\n", saltalloc, namealloc,
+ key_type_name, node->key);
+ else
+ snprintf(buf, buflen, "|1|%s|%s %s\n", saltalloc, namealloc,
+ node->key);
+ }
+
+ LIBSSH2_FREE(hosts->session, namealloc);
+ LIBSSH2_FREE(hosts->session, saltalloc);
+ }
+ else {
+ required_size += node->name_len + 3;
+ /* ' ' + '\n' + \0 = 3 */
+
+ if(required_size <= buflen) {
+ if(node->comment && key_type_len)
+ snprintf(buf, buflen, "%s %s %s %s\n", node->name,
+ key_type_name, node->key, node->comment);
+ else if (node->comment)
+ snprintf(buf, buflen, "%s %s %s\n", node->name, node->key,
+ node->comment);
+ else if (key_type_len)
+ snprintf(buf, buflen, "%s %s %s\n", node->name, key_type_name,
+ node->key);
+ else
+ snprintf(buf, buflen, "%s %s\n", node->name, node->key);
+ }
+ }
+
+ /* we report the full length of the data with the trailing zero excluded */
+ *outlen = required_size-1;
+
+ if(required_size <= buflen)
+ return LIBSSH2_ERROR_NONE;
+ else
+ return _libssh2_error(hosts->session, LIBSSH2_ERROR_BUFFER_TOO_SMALL,
+ "Known-host write buffer too small");
+}
+
+/*
+ * libssh2_knownhost_writeline()
+ *
+ * Ask libssh2 to convert a known host to an output line for storage.
+ *
+ * Note that this function returns LIBSSH2_ERROR_BUFFER_TOO_SMALL if the given
+ * output buffer is too small to hold the desired output.
+ */
+LIBSSH2_API int
+libssh2_knownhost_writeline(LIBSSH2_KNOWNHOSTS *hosts,
+ struct libssh2_knownhost *known,
+ char *buffer, size_t buflen,
+ size_t *outlen, /* the amount of written data */
+ int type)
+{
+ struct known_host *node;
+
+ if(known->magic != KNOWNHOST_MAGIC)
+ return _libssh2_error(hosts->session, LIBSSH2_ERROR_INVAL,
+ "Invalid host information");
+
+ node = known->node;
+
+ return knownhost_writeline(hosts, node, buffer, buflen, outlen, type);
+}
+
+/*
+ * libssh2_knownhost_writefile()
+ *
+ * Write hosts+key pairs to the given file.
+ */
+LIBSSH2_API int
+libssh2_knownhost_writefile(LIBSSH2_KNOWNHOSTS *hosts,
+ const char *filename, int type)
+{
+ struct known_host *node;
+ FILE *file;
+ int rc = LIBSSH2_ERROR_NONE;
+ char buffer[2048];
+
+ /* we only support this single file type for now, bail out on all other
+ attempts */
+ if(type != LIBSSH2_KNOWNHOST_FILE_OPENSSH)
+ return _libssh2_error(hosts->session,
+ LIBSSH2_ERROR_METHOD_NOT_SUPPORTED,
+ "Unsupported type of known-host information "
+ "store");
+
+ file = fopen(filename, "w");
+ if(!file)
+ return _libssh2_error(hosts->session, LIBSSH2_ERROR_FILE,
+ "Failed to open file");
+
+ for(node = _libssh2_list_first(&hosts->head);
+ node;
+ node = _libssh2_list_next(&node->node)) {
+ size_t wrote = 0;
+ size_t nwrote;
+ rc = knownhost_writeline(hosts, node, buffer, sizeof(buffer), &wrote,
+ type);
+ if(rc)
+ break;
+
+ nwrote = fwrite(buffer, 1, wrote, file);
+ if(nwrote != wrote) {
+ /* failed to write the whole thing, bail out */
+ rc = _libssh2_error(hosts->session, LIBSSH2_ERROR_FILE,
+ "Write failed");
+ break;
+ }
+ }
+ fclose(file);
+
+ return rc;
+}
+
+
+/*
+ * libssh2_knownhost_get()
+ *
+ * Traverse the internal list of known hosts. Pass NULL to 'prev' to get
+ * the first one.
+ *
+ * Returns:
+ * 0 if a fine host was stored in 'store'
+ * 1 if end of hosts
+ * [negative] on errors
+ */
+LIBSSH2_API int
+libssh2_knownhost_get(LIBSSH2_KNOWNHOSTS *hosts,
+ struct libssh2_knownhost **ext,
+ struct libssh2_knownhost *oprev)
+{
+ struct known_host *node;
+ if(oprev && oprev->node) {
+ /* we have a starting point */
+ struct known_host *prev = oprev->node;
+
+ /* get the next node in the list */
+ node = _libssh2_list_next(&prev->node);
+
+ }
+ else
+ node = _libssh2_list_first(&hosts->head);
+
+ if(!node)
+ /* no (more) node */
+ return 1;
+
+ *ext = knownhost_to_external(node);
+
+ return 0;
+}