/* * Copyright (c) 2009-2011 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 *comment; /* the (allocated) optional comment text, may be NULL */ /* 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) 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, 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_ALLOC(hosts->session, sizeof(struct known_host)))) return _libssh2_error(hosts->session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for known host " "entry"); 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->name) { rc = _libssh2_error(hosts->session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for host name"); 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(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 (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 */ } 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( ) 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, 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( ) 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, 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(!(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; } /* if a port number is given, check for a '[host]:port' first before the plain 'host' */ if(port >= 0) { snprintf(hostbuff, sizeof(hostbuff), "[%s]:%d", hostp, port); host = hostbuff; numcheck = 2; /* check both combos, start with this */ } else { host = hostp; numcheck = 1; /* only check this host version */ } 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. */ 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! */ 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, 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, 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, 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, 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; size_t commentlen = 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; case 's': /* ssh-dss or ssh-rsa */ if(!strncmp(key, "ssh-dss", 7)) key_type = LIBSSH2_KNOWNHOST_KEY_SSHDSS; else if(!strncmp(key, "ssh-rsa", 7)) key_type = LIBSSH2_KNOWNHOST_KEY_SSHRSA; else /* unknown key type */ return _libssh2_error(hosts->session, LIBSSH2_ERROR_METHOD_NOT_SUPPORTED, "Unknown key type"); key += 7; keylen -= 7; /* 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; default: /* unknown key format */ return _libssh2_error(hosts->session, LIBSSH2_ERROR_METHOD_NOT_SUPPORTED, "Unknown key format"); } /* 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, keylen, key_type, comment, commentlen); } else { /* |1|[salt]|[hash] */ return hashed_hostline(hosts, host, hostlen, 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: * * * * Where the two parts can be created like: * * can be either * or * * consists of * [name] optionally followed by [,name] one or more times * * 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] * */ 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)) 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) { int rc = LIBSSH2_ERROR_NONE; int tindex; const char *keytypes[4]={ "", /* not used */ "", /* this type has no name in the file */ " ssh-rsa", " ssh-dss" }; const char *keytype; size_t nlen; size_t commentlen = 0; /* 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"); tindex = (node->typemask & LIBSSH2_KNOWNHOST_KEY_MASK) >> LIBSSH2_KNOWNHOST_KEY_SHIFT; /* set the string used in the file */ keytype = keytypes[tindex]; /* calculate extra space needed for comment */ if(node->comment) commentlen = strlen(node->comment) + 1; if((node->typemask & LIBSSH2_KNOWNHOST_TYPE_MASK) == LIBSSH2_KNOWNHOST_TYPE_SHA1) { char *namealloc; char *saltalloc; nlen = _libssh2_base64_encode(hosts->session, node->name, node->name_len, &namealloc); if(!nlen) return _libssh2_error(hosts->session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for " "base64-encoded host name"); nlen = _libssh2_base64_encode(hosts->session, node->salt, node->salt_len, &saltalloc); if(!nlen) { free(namealloc); return _libssh2_error(hosts->session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for " "base64-encoded salt"); } nlen = strlen(saltalloc) + strlen(namealloc) + strlen(keytype) + strlen(node->key) + commentlen + 7; /* |1| + | + ' ' + \n + \0 = 7 */ if(nlen <= buflen) if(node->comment) snprintf(buf, buflen, "|1|%s|%s%s %s %s\n", saltalloc, namealloc, keytype, node->key, node->comment); else snprintf(buf, buflen, "|1|%s|%s%s %s\n", saltalloc, namealloc, keytype, node->key); else rc = _libssh2_error(hosts->session, LIBSSH2_ERROR_BUFFER_TOO_SMALL, "Known-host write buffer too small"); free(namealloc); free(saltalloc); } else { nlen = strlen(node->name) + strlen(keytype) + strlen(node->key) + commentlen + 3; /* ' ' + '\n' + \0 = 3 */ if(nlen <= buflen) /* these types have the plain name */ if(node->comment) snprintf(buf, buflen, "%s%s %s %s\n", node->name, keytype, node->key, node->comment); else snprintf(buf, buflen, "%s%s %s\n", node->name, keytype, node->key); else rc = _libssh2_error(hosts->session, LIBSSH2_ERROR_BUFFER_TOO_SMALL, "Known-host write buffer too small"); } /* we report the full length of the data with the trailing zero excluded */ *outlen = nlen-1; return rc; } /* * 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; 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; }