diff --git a/include/libssh2.h b/include/libssh2.h index 1b0d690..172e9b3 100644 --- a/include/libssh2.h +++ b/include/libssh2.h @@ -865,11 +865,12 @@ libssh2_knownhost_init(LIBSSH2_SESSION *session); #define LIBSSH2_KNOWNHOST_KEYENC_BASE64 (2<<16) /* type of key (2 bits) */ -#define LIBSSH2_KNOWNHOST_KEY_MASK (3<<18) +#define LIBSSH2_KNOWNHOST_KEY_MASK (7<<18) #define LIBSSH2_KNOWNHOST_KEY_SHIFT 18 #define LIBSSH2_KNOWNHOST_KEY_RSA1 (1<<18) #define LIBSSH2_KNOWNHOST_KEY_SSHRSA (2<<18) #define LIBSSH2_KNOWNHOST_KEY_SSHDSS (3<<18) +#define LIBSSH2_KNOWNHOST_KEY_UNKNOWN (7<<18) LIBSSH2_API int libssh2_knownhost_add(LIBSSH2_KNOWNHOSTS *hosts, diff --git a/src/knownhost.c b/src/knownhost.c index 8e1889c..fee6fb8 100644 --- a/src/knownhost.c +++ b/src/knownhost.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2011 by Daniel Stenberg + * Copyright (c) 2009-2013 by Daniel Stenberg * All rights reserved. * * Redistribution and use in source and binary forms, @@ -50,7 +50,11 @@ struct known_host { 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 */ + 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; @@ -67,6 +71,8 @@ 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) @@ -127,6 +133,7 @@ static struct libssh2_knownhost *knownhost_to_external(struct known_host *node) 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) @@ -161,6 +168,7 @@ knownhost_add(LIBSSH2_KNOWNHOSTS *hosts, 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, @@ -210,6 +218,19 @@ knownhost_add(LIBSSH2_KNOWNHOSTS *hosts, 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) { @@ -219,6 +240,7 @@ knownhost_add(LIBSSH2_KNOWNHOSTS *hosts, } memcpy(entry->comment, comment, commentlen+1); entry->comment[commentlen]=0; /* force a terminating zero trailer */ + entry->comment_len = commentlen; } else { entry->comment = NULL; @@ -264,8 +286,8 @@ libssh2_knownhost_add(LIBSSH2_KNOWNHOSTS *hosts, const char *key, size_t keylen, int typemask, struct libssh2_knownhost **store) { - return knownhost_add(hosts, host, salt, key, keylen, NULL, 0, typemask, - store); + return knownhost_add(hosts, host, salt, NULL, 0, key, keylen, NULL, + 0, typemask, store); } @@ -303,8 +325,8 @@ libssh2_knownhost_addc(LIBSSH2_KNOWNHOSTS *hosts, const char *comment, size_t commentlen, int typemask, struct libssh2_knownhost **store) { - return knownhost_add(hosts, host, salt, key, keylen, comment, commentlen, - typemask, store); + return knownhost_add(hosts, host, salt, NULL, 0, key, keylen, + comment, commentlen, typemask, store); } /* @@ -414,23 +436,35 @@ knownhost_check(LIBSSH2_KNOWNHOSTS *hosts, 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 */ + 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); } @@ -573,6 +607,7 @@ libssh2_knownhost_free(LIBSSH2_KNOWNHOSTS *hosts) */ 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) { @@ -607,7 +642,9 @@ static int oldstyle_hostline(LIBSSH2_KNOWNHOSTS *hosts, memcpy(hostbuf, name, namelen); hostbuf[namelen]=0; - rc = knownhost_add(hosts, hostbuf, NULL, key, keylen, + 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); @@ -627,6 +664,7 @@ static int oldstyle_hostline(LIBSSH2_KNOWNHOSTS *hosts, /* |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) { @@ -670,9 +708,11 @@ static int hashed_hostline(LIBSSH2_KNOWNHOSTS *hosts, memcpy(hostbuf, host, hostlen); hostbuf[hostlen]=0; - return knownhost_add(hosts, hostbuf, salt, key, keylen, comment, - commentlen, - key_type | LIBSSH2_KNOWNHOST_TYPE_SHA1 | + 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 @@ -694,7 +734,9 @@ static int hostline(LIBSSH2_KNOWNHOSTS *hosts, 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; int key_type; /* make some checks that the lengths seem sensible */ @@ -703,7 +745,7 @@ static int hostline(LIBSSH2_KNOWNHOSTS *hosts, 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': @@ -716,19 +758,21 @@ static int hostline(LIBSSH2_KNOWNHOSTS *hosts, */ break; - case 's': /* ssh-dss or ssh-rsa */ - if(!strncmp(key, "ssh-dss", 7)) + 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, "ssh-rsa", 7)) + if (!strncmp(key_type_name, "ssh-rsa", key_type_len)) 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; + key_type = LIBSSH2_KNOWNHOST_KEY_UNKNOWN; /* skip whitespaces */ while((*key ==' ') || (*key == '\t')) { @@ -760,11 +804,6 @@ static int hostline(LIBSSH2_KNOWNHOSTS *hosts, commentlen--; } break; - - default: /* unknown key format */ - return _libssh2_error(hosts->session, - LIBSSH2_ERROR_METHOD_NOT_SUPPORTED, - "Unknown key format"); } /* Figure out host format */ @@ -774,12 +813,14 @@ static int hostline(LIBSSH2_KNOWNHOSTS *hosts, 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, + 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, keylen, key_type, + return hashed_hostline(hosts, host, hostlen, key_type_name, + key_type_len, key, keylen, key_type, comment, commentlen); } } @@ -943,17 +984,9 @@ knownhost_writeline(LIBSSH2_KNOWNHOSTS *hosts, 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; + const char *key_type_name; + size_t key_type_len; + size_t offset = 0; /* we only support this single file type for now, bail out on all other attempts */ @@ -963,75 +996,103 @@ knownhost_writeline(LIBSSH2_KNOWNHOSTS *hosts, "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; + switch(node->typemask & LIBSSH2_KNOWNHOST_KEY_MASK) { + case LIBSSH2_KNOWNHOST_KEY_RSA1: + key_type_name = NULL; + key_type_len = 0; + break; + case LIBSSH2_KNOWNHOST_KEY_SSHRSA: + key_type_name = "ssh-rsa"; + key_type_len = 7; + break; + case LIBSSH2_KNOWNHOST_KEY_SSHDSS: + key_type_name = "ssh-dss"; + key_type_len = 7; + break; + case LIBSSH2_KNOWNHOST_KEY_UNKNOWN: + key_type_name = node->key_type_name; + if (key_type_name) { + key_type_len = node->key_type_len; + break; + } + /* otherwise fallback to default and error */ + default: + return _libssh2_error(hosts->session, + LIBSSH2_ERROR_METHOD_NOT_SUPPORTED, + "Unsupported type of known-host entry"); + } 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) { - LIBSSH2_FREE(hosts->session, 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); + int rc = LIBSSH2_ERROR_NONE; + char *namealloc = NULL; + char *saltalloc = NULL; + if (_libssh2_base64_encode(hosts->session, node->name, + node->name_len, &namealloc) && + _libssh2_base64_encode(hosts->session, + node->salt, node->salt_len, + &saltalloc)) + offset = snprintf(buf, buflen, "|1|%s|%s", saltalloc, namealloc); else - rc = _libssh2_error(hosts->session, LIBSSH2_ERROR_BUFFER_TOO_SMALL, - "Known-host write buffer too small"); + rc = _libssh2_error(hosts->session, LIBSSH2_ERROR_ALLOC, + "Unable allocate memory for known-host line"); - LIBSSH2_FREE(hosts->session, namealloc); - LIBSSH2_FREE(hosts->session, saltalloc); + if (namealloc) + LIBSSH2_FREE(hosts->session, namealloc); + if (saltalloc) + LIBSSH2_FREE(hosts->session, saltalloc); + + if (rc != LIBSSH2_ERROR_NONE) + return rc; + + if (buflen <= offset) + return _libssh2_error(hosts->session, + LIBSSH2_ERROR_BUFFER_TOO_SMALL, + "Known-host write buffer too small"); } 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"); + if (buflen <= node->name_len) + return _libssh2_error(hosts->session, + LIBSSH2_ERROR_BUFFER_TOO_SMALL, + "Known-host write buffer too small"); + memcpy(buf, node->name, node->name_len); + offset = node->name_len; } - /* we report the full length of the data with the trailing zero excluded */ - *outlen = nlen-1; + if (key_type_name) { + buf[offset++] = ' '; + if (buflen - offset <= key_type_len) + return _libssh2_error(hosts->session, + LIBSSH2_ERROR_BUFFER_TOO_SMALL, + "Known-host write buffer too small"); + memcpy(buf + offset, key_type_name, key_type_len); + offset += key_type_len; + } - return rc; + offset += snprintf(buf + offset, buflen - offset, + " %s", node->key); + if (buflen <= offset) + return _libssh2_error(hosts->session, LIBSSH2_ERROR_BUFFER_TOO_SMALL, + "Known-host write buffer too small"); + + if (node->comment) { + buf[offset++] = ' '; + if (buflen - offset <= node->comment_len) + return _libssh2_error(hosts->session, + LIBSSH2_ERROR_BUFFER_TOO_SMALL, + "Known-host write buffer too small"); + memcpy(buf + offset, node->comment, node->comment_len); + offset += node->comment_len; + } + + if (buflen - offset <= 1) + return _libssh2_error(hosts->session, LIBSSH2_ERROR_BUFFER_TOO_SMALL, + "Known-host write buffer too small"); + buf[offset++] = '\n'; + + buf[offset] = '\0'; + *outlen = offset; + return LIBSSH2_ERROR_NONE; } /*