| /* |
| * Copyright (c) 2009-2019 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"); |
| |
| entry = LIBSSH2_CALLOC(hosts->session, sizeof(struct known_host)); |
| if(!entry) |
| 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 if(!strncmp(key_type_name, "ecdsa-sha2-nistp256", key_type_len)) |
| key_type = LIBSSH2_KNOWNHOST_KEY_ECDSA_256; |
| else if(!strncmp(key_type_name, "ecdsa-sha2-nistp384", key_type_len)) |
| key_type = LIBSSH2_KNOWNHOST_KEY_ECDSA_384; |
| else if(!strncmp(key_type_name, "ecdsa-sha2-nistp521", key_type_len)) |
| key_type = LIBSSH2_KNOWNHOST_KEY_ECDSA_521; |
| else if(!strncmp(key_type_name, "ssh-ed25519", key_type_len)) |
| key_type = LIBSSH2_KNOWNHOST_KEY_ED25519; |
| 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[4092]; |
| |
| 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, FOPEN_READTEXT); |
| 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_ECDSA_256: |
| key_type_name = "ecdsa-sha2-nistp256"; |
| key_type_len = 19; |
| break; |
| case LIBSSH2_KNOWNHOST_KEY_ECDSA_384: |
| key_type_name = "ecdsa-sha2-nistp384"; |
| key_type_len = 19; |
| break; |
| case LIBSSH2_KNOWNHOST_KEY_ECDSA_521: |
| key_type_name = "ecdsa-sha2-nistp521"; |
| key_type_len = 19; |
| break; |
| case LIBSSH2_KNOWNHOST_KEY_ED25519: |
| key_type_name = "ssh-ed25519"; |
| key_type_len = 11; |
| 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 */ |
| /* FALL-THROUGH */ |
| 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[4092]; |
| |
| /* 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, FOPEN_WRITETEXT); |
| 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; |
| } |