From 4b991b232dc7df53a339c530055ca818467d6fb8 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Thu, 7 May 2009 13:09:48 +0000 Subject: [PATCH] My knownhost work as of right now. It works at least partly. More tests and tweaks will come. --- NEWS | 28 ++ TODO | 5 + example/simple/ssh2_exec.c | 47 ++-- include/libssh2.h | 130 +++++++++ src/Makefile.am | 9 +- src/hostkey.c | 542 +++++++++++++++++++++++++++++++++++++ src/kex.c | 17 +- src/libssh2_priv.h | 39 ++- src/misc.c | 145 +++++++++- src/misc.h | 63 +++++ src/session.c | 4 + 11 files changed, 994 insertions(+), 35 deletions(-) create mode 100644 src/misc.h diff --git a/NEWS b/NEWS index 464f87b..b188271 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,31 @@ +* (May 7 2009) Daniel Stenberg: + + - linked list code. I got a bit tired of the fact that we don't have any + generic linked-list functions within libssh2 so I wrote up the first embryo + for one that I use for this new functionality. The plan would then be to + move all existing code that uses linked lists to use this new set. + + - base64 encode. I had to add a base64 encoding function which was missing + in the code base so it helps to "bloat" my patch. + + - The knownhost API is currently: + + _init() - init a bundle of known hosts + _add() - add a known host + _del() - delete a known host + _free() - free an entire bundle of known hosts + _check() - check if a host+key is present in the bundle + + The convenience function: + + _parsefile() - reads a ~/.ssh/known_hosts file and add all entries to the + given bundle + + - there's no docs other than some comments in the code/headers yet + + - the patch includes changes to example/simple/ssh2_exec.c that makes use of + a few of these functions. Using that I've verified that the functions in + fact can verify my localhost's key agains my ~/.ssh/known_hosts file * (Apr 30 2009) Daniel Stenberg: diff --git a/TODO b/TODO index 27ec928..0bead67 100644 --- a/TODO +++ b/TODO @@ -28,3 +28,8 @@ At next SONAME bump libssh2_channel_receive_window_adjust() libssh2_poll() libssh2_poll_channel_read() + +* Rename a few function: + + libssh2_hostkey_hash => libssh2_session_hostkey_hash + libssh2_banner_set => libssh2_session_banner_set \ No newline at end of file diff --git a/example/simple/ssh2_exec.c b/example/simple/ssh2_exec.c index b84cd4d..4379e4d 100644 --- a/example/simple/ssh2_exec.c +++ b/example/simple/ssh2_exec.c @@ -1,5 +1,5 @@ /* - * $Id: ssh2_exec.c,v 1.2 2009/05/05 12:30:19 bagder Exp $ + * $Id: ssh2_exec.c,v 1.3 2009/05/07 13:09:49 bagder Exp $ * * Sample showing how to use libssh2 to execute a command remotely. * @@ -26,7 +26,6 @@ #include - static int waitsocket(int socket_fd, LIBSSH2_SESSION *session) { struct timeval timeout; @@ -59,11 +58,12 @@ static int waitsocket(int socket_fd, LIBSSH2_SESSION *session) int main(int argc, char *argv[]) { + const char *hostname = "127.0.0.1"; const char *commandline = "uptime"; const char *username = "user"; const char *password = "password"; unsigned long hostaddr; - int sock, i; + int sock; struct sockaddr_in sin; const char *fingerprint; LIBSSH2_SESSION *session; @@ -71,16 +71,17 @@ int main(int argc, char *argv[]) int rc; int exitcode; int bytecount = 0; + size_t len; + LIBSSH2_KNOWNHOSTS *nh; #ifdef WIN32 WSADATA wsadata; WSAStartup(MAKEWORD(2,0), &wsadata); #endif - if (argc > 1) { - hostaddr = inet_addr(argv[1]); - } else { - hostaddr = htonl(0x7F000001); - } + if (argc > 1) + /* must be ip address only */ + hostname = argv[1]; + if (argc > 2) { username = argv[2]; } @@ -91,6 +92,8 @@ int main(int argc, char *argv[]) commandline = argv[4]; } + hostaddr = inet_addr(hostname); + /* Ultra basic "connect to port 22 on localhost" * Your code is responsible for creating the socket establishing the * connection @@ -141,17 +144,25 @@ int main(int argc, char *argv[]) return -1; } - /* At this point we havn't yet authenticated. The first thing to do - * is check the hostkey's fingerprint against our known hosts Your app - * may have it hard coded, may go to a file, may present it to the - * user, that's your call - */ - fingerprint = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_MD5); - fprintf(stderr, "Fingerprint: "); - for(i = 0; i < 16; i++) { - fprintf(stderr, "%02X ", (unsigned char)fingerprint[i]); + nh = libssh2_knownhost_init(session); + + libssh2_knownhost_parsefile(nh, "/home/daniel/.ssh/known_hosts", + LIBSSH2_KNOWNHOST_FILE_OPENSSH); + + fingerprint = libssh2_session_hostkey(session, &len); + if(fingerprint) { + struct libssh2_knownhost host; + int check; + + check = libssh2_knownhost_check(nh, (char *)hostname, + (char *)fingerprint, len, + LIBSSH2_KNOWNHOST_TYPE_DEFAULT, &host); + + fprintf(stderr, "Host check: %d, key: %s\n", check, + (check <= LIBSSH2_KNOWNHOST_CHECK_MISMATCH)? + host.key:""); } - fprintf(stderr, "\n"); + libssh2_knownhost_free(nh); if ( strlen(password) != 0 ) { /* We could authenticate via password */ diff --git a/include/libssh2.h b/include/libssh2.h index 73f99af..75aa507 100644 --- a/include/libssh2.h +++ b/include/libssh2.h @@ -240,6 +240,7 @@ typedef struct _LIBSSH2_USERAUTH_KBDINT_RESPONSE typedef struct _LIBSSH2_SESSION LIBSSH2_SESSION; typedef struct _LIBSSH2_CHANNEL LIBSSH2_CHANNEL; typedef struct _LIBSSH2_LISTENER LIBSSH2_LISTENER; +typedef struct _LIBSSH2_KNOWNHOSTS LIBSSH2_KNOWNHOSTS; typedef struct _LIBSSH2_POLLFD { unsigned char type; /* LIBSSH2_POLLFD_* below */ @@ -348,6 +349,7 @@ typedef struct _LIBSSH2_POLLFD { #define LIBSSH2_ERROR_INVALID_POLL_TYPE -35 #define LIBSSH2_ERROR_PUBLICKEY_PROTOCOL -36 #define LIBSSH2_ERROR_EAGAIN -37 +#define LIBSSH2_ERROR_MEMORY -38 /* Session API */ LIBSSH2_API LIBSSH2_SESSION * @@ -377,6 +379,9 @@ LIBSSH2_API int libssh2_session_free(LIBSSH2_SESSION *session); LIBSSH2_API const char *libssh2_hostkey_hash(LIBSSH2_SESSION *session, int hash_type); +LIBSSH2_API const char *libssh2_session_hostkey(LIBSSH2_SESSION *session, + size_t *len); + LIBSSH2_API int libssh2_session_method_pref(LIBSSH2_SESSION *session, int method_type, const char *prefs); @@ -664,6 +669,131 @@ LIBSSH2_API const char *libssh2_version(int req_version_num); +/* + * 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_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 is: + * + * 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. + * + */ + +#define LIBSSH2_KNOWNHOST_TYPE_DEFAULT (LIBSSH2_KNOWNHOST_TYPE_PLAIN | \ + LIBSSH2_KNOWNHOST_KEY_RAW) + +/* host format (2 bits) */ +#define LIBSSH2_KNOWNHOST_TYPE_MASK 0xffff +#define LIBSSH2_KNOWNHOST_TYPE_PLAIN 1 +#define LIBSSH2_KNOWNHOST_TYPE_SHA1 2 /* always base64 encoded */ +#define LIBSSH2_KNOWNHOST_TYPE_CUSTOM 3 + +/* key format (2 bits) */ +#define LIBSSH2_KNOWNHOST_KEY_RAW (1<<16) +#define LIBSSH2_KNOWNHOST_KEY_BASE64 (2<<16) + +/* type of key (2 bits) */ +#define LIBSSH2_KNOWNHOST_KEY_RSA1 (1<<18) +#define LIBSSH2_KNOWNHOST_KEY_SSHRSA (2<<18) +#define LIBSSH2_KNOWNHOST_KEY_SSHDSS (3<<18) + +LIBSSH2_API int +libssh2_knownhost_add(LIBSSH2_KNOWNHOSTS *hosts, char *host, char *salt, + char *key, size_t keylen, int typemask); + +/* + * libssh2_knownhost_check + * + * Check a host and its associated key against the collection of known hosts. + * + * The type is the type/format of the given host name. + * + * plain - ascii "hostname.domain.tld" + * custom - prehashed base64 encoded. Note that this cannot use any salts. + * + * + * 'knownhost' may be set to NULL if you don't care about that info. + * + * Returns: + * + * LIBSSH2_KNOWNHOST_CHECK_* values, see below + * + */ +struct libssh2_knownhost { + unsigned int magic; /* magic stored by the library */ + void *node; /* handle to the internal representation of this host */ + char *name; /* this is NULL if no plain text host name exists */ + char *key; /* key in base64/printable format */ + int typemask; +}; + +#define LIBSSH2_KNOWNHOST_CHECK_MATCH 0 +#define LIBSSH2_KNOWNHOST_CHECK_MISMATCH 1 +#define LIBSSH2_KNOWNHOST_CHECK_NOTFOUND 2 +#define LIBSSH2_KNOWNHOST_CHECK_FAILURE 3 + +LIBSSH2_API int +libssh2_knownhost_check(LIBSSH2_KNOWNHOSTS *hosts, + char *host, char *key, size_t keylen, + int typemask, + struct libssh2_knownhost *knownhost); + +/* + * libssh2_knownhost_del + * + * Remove a host from the collection of known hosts. The 'entry' struct is + * retrieved by a call to libssh2_knownhost_check(). + * + */ +LIBSSH2_API int +libssh2_knownhost_del(LIBSSH2_KNOWNHOSTS *hosts, + struct libssh2_knownhost *entry); + +/* + * libssh2_knownhost_free + * + * Free an entire collection of known hosts. + * + */ +LIBSSH2_API void +libssh2_knownhost_free(LIBSSH2_KNOWNHOSTS *hosts); + +/* + * libssh2_knownhost_parsefile + * + * Add hosts+key pairs from a given file. + * + * Returns a negative value for error or number of successfully added hosts. + * + * This implementation currently only knows one type, all others are reserved + * for future use. + */ + +#define LIBSSH2_KNOWNHOST_FILE_OPENSSH 1 + +LIBSSH2_API int +libssh2_knownhost_parsefile(LIBSSH2_KNOWNHOSTS *hosts, + const char *filename, int type); + /* NOTE NOTE NOTE libssh2_trace() has no function in builds that aren't built with debug enabled diff --git a/src/Makefile.am b/src/Makefile.am index d961d23..9f89347 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,9 +1,10 @@ -# $Id: Makefile.am,v 1.19 2009/03/26 22:25:23 bagder Exp $ +# $Id: Makefile.am,v 1.20 2009/05/07 13:09:49 bagder Exp $ AUTOMAKE_OPTIONS = foreign nostdinc -libssh2_la_SOURCES = channel.c comp.c crypt.c hostkey.c kex.c mac.c misc.c \ -packet.c publickey.c scp.c session.c sftp.c userauth.c libssh2_priv.h \ -openssl.h libgcrypt.h transport.c version.c transport.h channel.h comp.h mac.h +libssh2_la_SOURCES = channel.c comp.c crypt.c hostkey.c kex.c mac.c misc.c \ + packet.c publickey.c scp.c session.c sftp.c userauth.c libssh2_priv.h \ + openssl.h libgcrypt.h transport.c version.c transport.h channel.h comp.h \ + mac.h misc.h if LIBGCRYPT libssh2_la_SOURCES += libgcrypt.c pem.c diff --git a/src/hostkey.c b/src/hostkey.c index 55a20d6..954b4c6 100644 --- a/src/hostkey.c +++ b/src/hostkey.c @@ -1,4 +1,5 @@ /* Copyright (c) 2004-2006, Sara Golemon + * Copyright (c) 2009 by Daniel Stenberg * All rights reserved. * * Redistribution and use in source and binary forms, @@ -35,7 +36,9 @@ * OF SUCH DAMAGE. */ +#include "libssh2.h" #include "libssh2_priv.h" +#include "misc.h" /* Needed for struct iovec on some platforms */ #ifdef HAVE_SYS_UIO_H @@ -453,3 +456,542 @@ libssh2_hostkey_hash(LIBSSH2_SESSION * session, int hash_type) return NULL; } } + +static void free_host(LIBSSH2_SESSION *session, struct known_host *entry) +{ + if(entry) { + if(entry->key) + LIBSSH2_FREE(session, entry->key); + if(entry->salt) + LIBSSH2_FREE(session, entry->salt); + if(entry->name) + LIBSSH2_FREE(session, entry->name); + LIBSSH2_FREE(session, entry); + } +} + +/* + * libssh2_session_hostkey() + * + * Returns the server key and length. + * + */ +LIBSSH2_API const char * +libssh2_session_hostkey(LIBSSH2_SESSION *session, size_t *len) +{ + if(session->server_hostkey_len) { + if(len) + *len = session->server_hostkey_len; + return (char *) session->server_hostkey; + } + if(len) + *len = 0; + return NULL; +} + +/* + * libssh2_knownhost_init + * + * Init a collection of known hosts. Returns the pointer to a collection. + * + */ +LIBSSH2_API LIBSSH2_KNOWNHOSTS * +libssh2_knownhost_init(LIBSSH2_SESSION *session) +{ + LIBSSH2_KNOWNHOSTS *knh = + LIBSSH2_ALLOC(session, sizeof(struct _LIBSSH2_KNOWNHOSTS)); + + if(!knh) + return NULL; + + knh->session = session; + + _libssh2_list_init(&knh->head); + + return knh; +} + +/* + * libssh2_knownhost_add + * + * Add a host and its associated key to the collection of known hosts. + * + * The 'type' argument specifies on what format the given host and keys are: + * + * plain - ascii "hostname.domain.tld" + * sha1 - SHA1( ) base64-encoded! + * custom - another hash + * + * If 'sha1' is selected as type, the salt must be provided to the salt + * argument. This too base64 encoded. + * + * The SHA-1 hash is what OpenSSH can be told to use in known_hosts files. If + * a custom type is used, salt is ignored and you must provide the host + * pre-hashed when checking for it in the libssh2_knownhost_check() function. + * + */ + +LIBSSH2_API int +libssh2_knownhost_add(LIBSSH2_KNOWNHOSTS *hosts, + char *host, char *salt, + char *key, size_t keylen, + int typemask) +{ + struct known_host *entry = + LIBSSH2_ALLOC(hosts->session, sizeof(struct known_host)); + size_t hostlen = strlen(host); + int rc = LIBSSH2_ERROR_MEMORY; + char *ptr; + unsigned int ptrlen; + + if(!entry) + return rc; + + memset(entry, 0, sizeof(struct known_host)); + + entry->typemask = typemask; + + switch(entry->typemask & LIBSSH2_KNOWNHOST_TYPE_MASK) { + case LIBSSH2_KNOWNHOST_TYPE_PLAIN: + case LIBSSH2_KNOWNHOST_TYPE_CUSTOM: + entry->name = LIBSSH2_ALLOC(hosts->session, hostlen+1); + if(!entry) + goto error; + memcpy(entry->name, host, hostlen+1); + break; + case LIBSSH2_KNOWNHOST_TYPE_SHA1: + rc = libssh2_base64_decode(hosts->session, &ptr, &ptrlen, + host, hostlen); + if(rc) + goto error; + entry->name = ptr; + entry->name_len = ptrlen; + + rc = libssh2_base64_decode(hosts->session, &ptr, &ptrlen, + salt, strlen(salt)); + if(rc) + goto error; + entry->salt = ptr; + entry->salt_len = ptrlen; + break; + default: + rc = LIBSSH2_ERROR_METHOD_NOT_SUPPORTED; + goto error; + } + + if(typemask & LIBSSH2_KNOWNHOST_KEY_BASE64) { + /* the provided key is base64 encoded already */ + if(!keylen) + keylen = strlen(key); + entry->key = LIBSSH2_ALLOC(hosts->session, keylen+1); + if(!entry) + goto error; + memcpy(entry->key, key, keylen+1); + } + else { + /* key is raw, we base64 encode it and store it as such */ + size_t nlen = _libssh2_base64_encode(hosts->session, key, keylen, + &ptr); + if(!nlen) + goto error; + + entry->key = ptr; + } + + /* add this new host to the big list of known hosts */ + _libssh2_list_add(&hosts->head, &entry->node); + + return LIBSSH2_ERROR_NONE; + error: + free_host(hosts->session, entry); + return rc; +} + +#define KNOWNHOST_MAGIC 0xdeadcafe +/* + * knownhost_to_external() + * + * Copies data from the internal to the external representation struct. + * + */ +static void knownhost_to_external(struct known_host *node, + struct libssh2_knownhost *ext) +{ + if(ext) { + ext->magic = KNOWNHOST_MAGIC; + ext->node = node; + ext->name = ((node->typemask & LIBSSH2_KNOWNHOST_TYPE_MASK) == + LIBSSH2_KNOWNHOST_TYPE_PLAIN)? node->name:NULL; + ext->key = node->key; + ext->typemask = node->typemask; + } +} + +/* + * libssh2_knownhost_check + * + * Check a host and its associated key against the collection of known hosts. + * + * The typemask is the type/format of the given host name and key + * + * plain - ascii "hostname.domain.tld" + * sha1 - NOT SUPPORTED AS INPUT + * custom - prehashed base64 encoded. Note that this cannot use any salts. + * + * Returns: + * + * LIBSSH2_KNOWNHOST_CHECK_FAILURE + * LIBSSH2_KNOWNHOST_CHECK_NOTFOUND + * LIBSSH2_KNOWNHOST_CHECK_MATCH + * LIBSSH2_KNOWNHOST_CHECK_MISMATCH + */ +LIBSSH2_API int +libssh2_knownhost_check(LIBSSH2_KNOWNHOSTS *hosts, + char *host, char *key, size_t keylen, + int typemask, + struct libssh2_knownhost *knownhost) +{ + struct known_host *node = _libssh2_list_first(&hosts->head); + struct known_host *badkey = NULL; + int type = typemask & LIBSSH2_KNOWNHOST_TYPE_MASK; + char *keyalloc = NULL; + int rc = LIBSSH2_KNOWNHOST_CHECK_NOTFOUND; + + if(type == LIBSSH2_KNOWNHOST_TYPE_SHA1) + /* we can't work with a sha1 as given input */ + return LIBSSH2_KNOWNHOST_CHECK_MISMATCH; + + if(!(typemask & LIBSSH2_KNOWNHOST_KEY_BASE64)) { + /* we got a raw key input, convert it to base64 for the checks below */ + size_t nlen = _libssh2_base64_encode(hosts->session, key, keylen, + &keyalloc); + if(!nlen) + return LIBSSH2_KNOWNHOST_CHECK_FAILURE; + + /* make the key point to this */ + key = keyalloc; + keylen = nlen; + } + + while (node) { + int match = 0; + switch(node->typemask & LIBSSH2_KNOWNHOST_TYPE_MASK) { + case LIBSSH2_KNOWNHOST_TYPE_PLAIN: + if(type == LIBSSH2_KNOWNHOST_TYPE_PLAIN) + match = !strcmp(host, node->name); + break; + case LIBSSH2_KNOWNHOST_TYPE_CUSTOM: + if(type == LIBSSH2_KNOWNHOST_TYPE_CUSTOM) + match = !strcmp(host, node->name); + break; + case LIBSSH2_KNOWNHOST_TYPE_SHA1: + if(type == LIBSSH2_KNOWNHOST_TYPE_PLAIN) { + /* when we have the sha1 version stored, we can use a plain + input to produce a hash to compare with the stored hash. + + HMAC_Init(&mac_ctx, salt, len, md); + HMAC_Update(&mac_ctx, host, strlen(host)); + HMAC_Final(&mac_ctx, result, NULL); + HMAC_cleanup(&mac_ctx); + + */ + libssh2_hmac_ctx ctx; + unsigned char hash[SHA_DIGEST_LENGTH]; + + if(SHA_DIGEST_LENGTH != node->name_len) { + /* the name hash length must be the sha1 size or + we can't match it */ + break; + } + libssh2_hmac_sha1_init(&ctx, node->salt, node->salt_len); + libssh2_hmac_update(ctx, (unsigned char *)host, strlen(host)); + libssh2_hmac_final(ctx, hash); + libssh2_hmac_cleanup(&ctx); + + if(!memcmp(hash, node->name, SHA_DIGEST_LENGTH)) + /* this is a node we're interested in */ + match = 1; + } + break; + default: /* unsupported type */ + break; + } + if(match) { + /* host name match, now compare the keys */ + if(!strcmp(key, node->key)) { + /* they match! */ + knownhost_to_external(node, knownhost); + badkey = NULL; + rc = LIBSSH2_KNOWNHOST_CHECK_MATCH; + break; + } + else { + /* remember the first node that had a host match but a failed + key match since we continue our search from here */ + if(!badkey) + badkey = node; + } + } + node= _libssh2_list_next(&node->node); + } + + if(badkey) { + /* key mismatch */ + knownhost_to_external(badkey, knownhost); + rc = LIBSSH2_KNOWNHOST_CHECK_MISMATCH; + } + + if(keyalloc) + LIBSSH2_FREE(hosts->session, keyalloc); + + return rc; +} + +/* + * libssh2_knownhost_del + * + * Remove a host from the collection of known hosts. + * + */ +LIBSSH2_API int +libssh2_knownhost_del(LIBSSH2_KNOWNHOSTS *hosts, + struct libssh2_knownhost *entry) +{ + struct known_host *node; + if(!entry || (entry->magic != KNOWNHOST_MAGIC)) + /* check that this was retrieved the right way or get out */ + return -1; + + /* get the internal node pointer */ + node = entry->node; + + /* unlink from the list of all hosts */ + _libssh2_list_remove(&node->node); + + /* free all resources */ + free_host(hosts->session, node); + + return 0; +} + +/* + * libssh2_knownhost_free + * + * Free an entire collection of known hosts. + * + */ +LIBSSH2_API void +libssh2_knownhost_free(LIBSSH2_KNOWNHOSTS *hosts) +{ + struct known_host *node; + struct known_host *next; + + for(node = _libssh2_list_first(&hosts->head); node; node = next) { + next = _libssh2_list_next(&node->node); + free_host(hosts->session, node); + } + LIBSSH2_FREE(hosts->session, hosts); +} + +/* + * hostline() + * + * Parse a single known_host line pre-split into host and key. + * + * Note: this function assumes that the 'host' pointer points into a temporary + * buffer as it will write to it. + */ +static int hostline(LIBSSH2_KNOWNHOSTS *hosts, + char *host, size_t hostlen, + char *key, size_t keylen) +{ + char *p; + char *salt = NULL; + int rc; + int type = LIBSSH2_KNOWNHOST_TYPE_PLAIN; + char *sep = NULL; + + /* Figure out host format */ + if(strncmp(host, "|1|", 3)) { + /* old style plain text: [name][,][ip-address] + + for the sake of simplicity, we add them as two hosts with the same + key + */ + sep = strchr(host, ','); + } + else { + /* |1|[salt]|[hash] */ + type = LIBSSH2_KNOWNHOST_TYPE_SHA1; + + salt = &host[3]; /* skip the magic marker */ + + /* this is where the salt starts, find the end of it */ + for(p = salt; *p && (*p != '|'); p++) + ; + + if(*p=='|') { + char *hash = NULL; + *p=0; /* terminate the salt string */ + hash = p+1; /* the hash is after the separator */ + + /* now make the host point to the hash */ + hostlen = strlen(hash); + host = hash; + } + else + return 0; + } + + if(keylen < 20) + return -1; /* TODO: better return code */ + + switch(key[0]) { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + type |= LIBSSH2_KNOWNHOST_KEY_RSA1; + + /* Note that the old-style keys (RSA1) aren't truly base64, but we + * claim it is for now since we can get away with strcmp()ing the + * entire anything anyway! We need to check and fix these to make them + * work properly. + */ + break; + + case 's': /* ssh-dss or ssh-rsa */ + if(!strncmp(key, "ssh-dss", 7)) + type |= LIBSSH2_KNOWNHOST_KEY_SSHDSS; + else if(!strncmp(key, "ssh-rsa", 7)) + type |= LIBSSH2_KNOWNHOST_KEY_SSHRSA; + else + return -1; /* unknown */ + + key += 7; + keylen -= 7; + + /* skip whitespaces */ + while((*key ==' ') || (*key == '\t')) { + key++; + keylen--; + } + break; + + default: /* unknown key format */ + return -1; + } + + if(sep) { + /* this is the second host, add this first */ + char *ipaddr; + *sep++ = 0; /* zero terminate the first host name here */ + ipaddr = sep; + rc = libssh2_knownhost_add(hosts, ipaddr, salt, key, keylen, + type | LIBSSH2_KNOWNHOST_KEY_BASE64); + if(rc) + return rc; + } + + rc = libssh2_knownhost_add(hosts, host, salt, key, keylen, + type | LIBSSH2_KNOWNHOST_KEY_BASE64); + + return rc; +} + +/* + * libssh2_knownhost_parsefile + * + * Add hosts+key pairs from a given file. + * + * Returns a negative value for error or number of successfully added hosts. + * + * Line format: + * + * + * + * Where the two parts can be created like: + * + * can be either + * or + * + * consists of + * [name,address] or just [name] or just [address] + * + * consists of + * |1||hash + * + * can be one of: + * [RSA bits] [e] [n as a decimal number] + * 'ssh-dss' [base64-encoded-key] + * 'ssh-rsa' [base64-encoded-key] + * + */ + +#define LIBSSH2_KNOWNHOST_FILE_OPENSSH 1 + +LIBSSH2_API int +libssh2_knownhost_parsefile(LIBSSH2_KNOWNHOSTS *hosts, + const char *filename, int type) +{ + FILE *file; + int num = 0; + char buf[2048]; + + if(type != LIBSSH2_KNOWNHOST_FILE_OPENSSH) + return -1; + + file = fopen(filename, "r"); + if(file) { + char *cp; + char *hostp; + char *key; + size_t hostlen; + + while(fgets(buf, sizeof(buf), file)) { + cp = buf; + + /* skip leading whitespaces */ + while((*cp==' ') || (*cp == '\t')) + cp++; + + if(!*cp || (*cp == '#') || (*cp == '\n')) + /* comment or empty line */ + continue; + + /* the host part starts here */ + hostp = cp; + + /* move over the host to the separator */ + while(*cp && (*cp!=' ') && (*cp != '\t')) + cp++; + + hostlen = cp - hostp; + + *cp++ = 0; /* terminate the host string here */ + + /* the key starts after the whitespaces */ + while(*cp && ((*cp==' ') || (*cp == '\t'))) + cp++; + + if(!*cp) + /* illegal line */ + continue; + + key = cp; /* the key starts here */ + + while(*cp && (*cp != '\n')) + cp++; + + /* zero terminate where the newline is */ + if(*cp == '\n') + *cp = 0; + + /* deal with this one host+key line */ + if(!hostline(hosts, hostp, hostlen, key, strlen(key))) + num++; + } + fclose(file); + } + else + return -1; + return num; +} diff --git a/src/kex.c b/src/kex.c index caf01a5..c6591ff 100644 --- a/src/kex.c +++ b/src/kex.c @@ -158,7 +158,7 @@ kex_method_diffie_hellman_groupGP_sha1_key_exchange(LIBSSH2_SESSION *session, if (exchange_state->state == libssh2_NB_state_sent) { if (session->burn_optimistic_kexinit) { - /* The first KEX packet to come along will be the guess initially + /* The first KEX packet to come along will be the guess initially * sent by the server. That guess turned out to be wrong so we * need to silently ignore it */ int burn_type; @@ -445,12 +445,12 @@ kex_method_diffie_hellman_groupGP_sha1_key_exchange(LIBSSH2_SESSION *session, ret = -1; goto clean_exit; } - /* The first key exchange has been performed, + /* The first key exchange has been performed, switch to active crypt/comp/mac mode */ session->state |= LIBSSH2_STATE_NEWKEYS; _libssh2_debug(session, LIBSSH2_DBG_KEX, "Received NEWKEYS message"); - /* This will actually end up being just packet_type(1) + /* This will actually end up being just packet_type(1) for this packet type anyway */ LIBSSH2_FREE(session, exchange_state->tmp); @@ -641,11 +641,6 @@ kex_method_diffie_hellman_groupGP_sha1_key_exchange(LIBSSH2_SESSION *session, exchange_state->k_value = NULL; } - if (session->server_hostkey) { - LIBSSH2_FREE(session, session->server_hostkey); - session->server_hostkey = NULL; - } - exchange_state->state = libssh2_NB_state_idle; return ret; @@ -1013,7 +1008,7 @@ kex_method_list(unsigned char *buf, size_t list_strlen, */ static int kexinit(LIBSSH2_SESSION * session) { - /* 62 = packet_type(1) + cookie(16) + first_packet_follows(1) + + /* 62 = packet_type(1) + cookie(16) + first_packet_follows(1) + reserved(4) + length longs(40) */ size_t data_len = 62; size_t kex_len, hostkey_len = 0; @@ -1069,8 +1064,8 @@ static int kexinit(LIBSSH2_SESSION * session) libssh2_random(s, 16); s += 16; - /* Ennumerating through these lists twice is probably (certainly?) - inefficient from a CPU standpoint, but it saves multiple + /* Ennumerating through these lists twice is probably (certainly?) + inefficient from a CPU standpoint, but it saves multiple malloc/realloc calls */ LIBSSH2_METHOD_PREFS_STR(s, kex_len, session->kex_prefs, libssh2_kex_methods); diff --git a/src/libssh2_priv.h b/src/libssh2_priv.h index f3fc5d3..5920a47 100644 --- a/src/libssh2_priv.h +++ b/src/libssh2_priv.h @@ -922,7 +922,44 @@ struct _LIBSSH2_SESSION #define LIBSSH2_SOCKET_RECV_FLAGS(session) 0 #endif -/* libssh2 extensible ssh api, ultimately I'd like to allow loading additional methods via .so/.dll */ +/* -------- */ + +/* First take towards a generic linked list handling code for libssh2 + internals */ + +struct list_head { + struct list_node *last; + struct list_node *first; +}; + +struct list_node { + struct list_node *next; + struct list_node *prev; + struct list_head *head; +}; + +/* --------- */ + +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 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. */ +}; + +struct _LIBSSH2_KNOWNHOSTS +{ + LIBSSH2_SESSION *session; /* the session this "belongs to" */ + struct list_head head; +}; + + +/* libssh2 extensible ssh api, ultimately I'd like to allow loading additional + methods via .so/.dll */ struct _LIBSSH2_KEX_METHOD { diff --git a/src/misc.c b/src/misc.c index 588ad16..c8e58c0 100644 --- a/src/misc.c +++ b/src/misc.c @@ -37,6 +37,8 @@ */ #include "libssh2_priv.h" +#include "misc.h" + #ifdef HAVE_UNISTD_H #include #endif @@ -176,7 +178,7 @@ static const short base64_reverse_table[256] = { * Decode a base64 chunk and store it into a newly alloc'd buffer */ LIBSSH2_API int -libssh2_base64_decode(LIBSSH2_SESSION * session, char **data, +libssh2_base64_decode(LIBSSH2_SESSION *session, char **data, unsigned int *datalen, const char *src, unsigned int src_len) { @@ -222,6 +224,86 @@ libssh2_base64_decode(LIBSSH2_SESSION * session, char **data, return 0; } +/* ---- Base64 Encoding/Decoding Table --- */ +static const char table64[]= + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +/* + * _libssh2_base64_encode() + * + * Returns the length of the newly created base64 string. The third argument + * is a pointer to an allocated area holding the base64 data. If something + * went wrong, 0 is returned. + * + */ +size_t _libssh2_base64_encode(LIBSSH2_SESSION *session, + const char *inp, size_t insize, char **outptr) +{ + unsigned char ibuf[3]; + unsigned char obuf[4]; + int i; + int inputparts; + char *output; + char *base64data; + const char *indata = inp; + + *outptr = NULL; /* set to NULL in case of failure before we reach the end */ + + if(0 == insize) + insize = strlen(indata); + + base64data = output = LIBSSH2_ALLOC(session, insize*4/3+4); + if(NULL == output) + return 0; + + while(insize > 0) { + for (i = inputparts = 0; i < 3; i++) { + if(insize > 0) { + inputparts++; + ibuf[i] = *indata; + indata++; + insize--; + } + else + ibuf[i] = 0; + } + + obuf[0] = (unsigned char) ((ibuf[0] & 0xFC) >> 2); + obuf[1] = (unsigned char) (((ibuf[0] & 0x03) << 4) | \ + ((ibuf[1] & 0xF0) >> 4)); + obuf[2] = (unsigned char) (((ibuf[1] & 0x0F) << 2) | \ + ((ibuf[2] & 0xC0) >> 6)); + obuf[3] = (unsigned char) (ibuf[2] & 0x3F); + + switch(inputparts) { + case 1: /* only one byte read */ + snprintf(output, 5, "%c%c==", + table64[obuf[0]], + table64[obuf[1]]); + break; + case 2: /* two bytes read */ + snprintf(output, 5, "%c%c%c=", + table64[obuf[0]], + table64[obuf[1]], + table64[obuf[2]]); + break; + default: + snprintf(output, 5, "%c%c%c%c", + table64[obuf[0]], + table64[obuf[1]], + table64[obuf[2]], + table64[obuf[3]] ); + break; + } + output += 4; + } + *output=0; + *outptr = base64data; /* make it return the actual data memory */ + + return strlen(base64data); /* return the length of the new data */ +} +/* ---- End of Base64 Encoding ---- */ + #ifdef LIBSSH2DEBUG LIBSSH2_API int libssh2_trace(LIBSSH2_SESSION * session, int bitmask) @@ -283,3 +365,64 @@ libssh2_trace(LIBSSH2_SESSION * session, int bitmask) return 0; } #endif + +/* init the list head */ +void _libssh2_list_init(struct list_head *head) +{ + head->first = head->last = NULL; +} + +/* add a node to the list */ +void _libssh2_list_add(struct list_head *head, + struct list_node *entry) +{ + /* store a pointer to the head */ + entry->head = head; + + /* we add this entry at the "top" so it has no next */ + entry->next = NULL; + + /* make our prev point to what the head thinks is last */ + entry->prev = head->last; + + /* and make head's last be us now */ + head->last = entry; + + /* make sure our 'prev' node points to us next */ + if(entry->prev) + entry->prev->next = entry; + else + head->first = entry; +} + +/* return the "first" node in the list this head points to */ +void *_libssh2_list_first(struct list_head *head) +{ + return head->first; +} + +/* return the next node in the list */ +void *_libssh2_list_next(struct list_node *node) +{ + return node->next; +} + +/* return the prev node in the list */ +void *_libssh2_list_prev(struct list_node *node) +{ + return node->prev; +} + +/* remove this node from the list */ +void _libssh2_list_remove(struct list_node *entry) +{ + if(entry->prev) + entry->prev->next = entry->next; + else + entry->head->first = entry->next; + + if(entry->next) + entry->next->prev = entry->prev; + else + entry->head->last = entry->prev; +} diff --git a/src/misc.h b/src/misc.h new file mode 100644 index 0000000..13b6784 --- /dev/null +++ b/src/misc.h @@ -0,0 +1,63 @@ +#ifndef __LIBSSH2_MISC_H +#define __LIBSSH2_MISC_H +/* Copyright (c) 2009 by Daniel Stenberg + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, + * with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the copyright holder nor the names + * of any other contributors may be used to endorse or + * promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#include "libssh2_priv.h" + +void _libssh2_list_init(struct list_head *head); + +/* add a node first in the list */ +void _libssh2_list_add(struct list_head *head, + struct list_node *entry); + +/* return the "first" node in the list this head points to */ +void *_libssh2_list_first(struct list_head *head); + +/* return the next node in the list */ +void *_libssh2_list_next(struct list_node *node); + +/* return the prev node in the list */ +void *_libssh2_list_prev(struct list_node *node); + +/* remove this node from the list */ +void _libssh2_list_remove(struct list_node *entry); + +size_t _libssh2_base64_encode(LIBSSH2_SESSION *session, + const char *inp, size_t insize, char **outptr); +#endif /* _LIBSSH2_MISC_H */ diff --git a/src/session.c b/src/session.c index e157024..adc98b5 100644 --- a/src/session.c +++ b/src/session.c @@ -924,6 +924,10 @@ session_free(LIBSSH2_SESSION *session) /* if the socket was previously blocking, put it back so */ session_nonblock(session->socket_fd, 0); + if (session->server_hostkey) { + LIBSSH2_FREE(session, session->server_hostkey); + } + LIBSSH2_FREE(session, session); return 0;