1 /*
2  * Copyright (c) 2009-2014 by Daniel Stenberg
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms,
6  * with or without modification, are permitted provided
7  * that the following conditions are met:
8  *
9  *   Redistributions of source code must retain the above
10  *   copyright notice, this list of conditions and the
11  *   following disclaimer.
12  *
13  *   Redistributions in binary form must reproduce the above
14  *   copyright notice, this list of conditions and the following
15  *   disclaimer in the documentation and/or other materials
16  *   provided with the distribution.
17  *
18  *   Neither the name of the copyright holder nor the names
19  *   of any other contributors may be used to endorse or
20  *   promote products derived from this software without
21  *   specific prior written permission.
22  *
23  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
24  * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
25  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
26  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
28  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
29  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
30  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
31  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
32  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
33  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
34  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
35  * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
36  * OF SUCH DAMAGE.
37  */
38 
39 #include "libssh2_priv.h"
40 #include "misc.h"
41 
42 struct known_host {
43     struct list_node node;
44     char *name;      /* points to the name or the hash (allocated) */
45     size_t name_len; /* needed for hashed data */
46     int port;        /* if non-zero, a specific port this key is for on this
47                         host */
48     int typemask;    /* plain, sha1, custom, ... */
49     char *salt;      /* points to binary salt (allocated) */
50     size_t salt_len; /* size of salt */
51     char *key;       /* the (allocated) associated key. This is kept base64
52                         encoded in memory. */
53     char *key_type_name; /* the (allocated) key type name */
54     size_t key_type_len; /* size of key_type_name */
55     char *comment;       /* the (allocated) optional comment text, may be
56                             NULL */
57     size_t comment_len;  /* the size of comment */
58 
59     /* this is the struct we expose externally */
60     struct libssh2_knownhost external;
61 };
62 
63 struct _LIBSSH2_KNOWNHOSTS
64 {
65     LIBSSH2_SESSION *session;  /* the session this "belongs to" */
66     struct list_head head;
67 };
68 
free_host(LIBSSH2_SESSION * session,struct known_host * entry)69 static void free_host(LIBSSH2_SESSION *session, struct known_host *entry)
70 {
71     if(entry) {
72         if(entry->comment)
73             LIBSSH2_FREE(session, entry->comment);
74         if (entry->key_type_name)
75             LIBSSH2_FREE(session, entry->key_type_name);
76         if(entry->key)
77             LIBSSH2_FREE(session, entry->key);
78         if(entry->salt)
79             LIBSSH2_FREE(session, entry->salt);
80         if(entry->name)
81             LIBSSH2_FREE(session, entry->name);
82         LIBSSH2_FREE(session, entry);
83     }
84 }
85 
86 /*
87  * libssh2_knownhost_init
88  *
89  * Init a collection of known hosts. Returns the pointer to a collection.
90  *
91  */
92 LIBSSH2_API LIBSSH2_KNOWNHOSTS *
libssh2_knownhost_init(LIBSSH2_SESSION * session)93 libssh2_knownhost_init(LIBSSH2_SESSION *session)
94 {
95     LIBSSH2_KNOWNHOSTS *knh =
96         LIBSSH2_ALLOC(session, sizeof(struct _LIBSSH2_KNOWNHOSTS));
97 
98     if(!knh) {
99         _libssh2_error(session, LIBSSH2_ERROR_ALLOC,
100                        "Unable to allocate memory for known-hosts "
101                        "collection");
102         return NULL;
103     }
104 
105     knh->session = session;
106 
107     _libssh2_list_init(&knh->head);
108 
109     return knh;
110 }
111 
112 #define KNOWNHOST_MAGIC 0xdeadcafe
113 /*
114  * knownhost_to_external()
115  *
116  * Copies data from the internal to the external representation struct.
117  *
118  */
knownhost_to_external(struct known_host * node)119 static struct libssh2_knownhost *knownhost_to_external(struct known_host *node)
120 {
121     struct libssh2_knownhost *ext = &node->external;
122 
123     ext->magic = KNOWNHOST_MAGIC;
124     ext->node = node;
125     ext->name = ((node->typemask & LIBSSH2_KNOWNHOST_TYPE_MASK) ==
126                  LIBSSH2_KNOWNHOST_TYPE_PLAIN)? node->name:NULL;
127     ext->key = node->key;
128     ext->typemask = node->typemask;
129 
130     return ext;
131 }
132 
133 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)134 knownhost_add(LIBSSH2_KNOWNHOSTS *hosts,
135               const char *host, const char *salt,
136               const char *key_type_name, size_t key_type_len,
137               const char *key, size_t keylen,
138               const char *comment, size_t commentlen,
139               int typemask, struct libssh2_knownhost **store)
140 {
141     struct known_host *entry;
142     size_t hostlen = strlen(host);
143     int rc;
144     char *ptr;
145     unsigned int ptrlen;
146 
147     /* make sure we have a key type set */
148     if(!(typemask & LIBSSH2_KNOWNHOST_KEY_MASK))
149         return _libssh2_error(hosts->session, LIBSSH2_ERROR_INVAL,
150                               "No key type set");
151 
152     if(!(entry = LIBSSH2_CALLOC(hosts->session, sizeof(struct known_host))))
153         return _libssh2_error(hosts->session, LIBSSH2_ERROR_ALLOC,
154                               "Unable to allocate memory for known host "
155                               "entry");
156 
157     entry->typemask = typemask;
158 
159     switch(entry->typemask  & LIBSSH2_KNOWNHOST_TYPE_MASK) {
160     case LIBSSH2_KNOWNHOST_TYPE_PLAIN:
161     case LIBSSH2_KNOWNHOST_TYPE_CUSTOM:
162         entry->name = LIBSSH2_ALLOC(hosts->session, hostlen+1);
163         if(!entry->name) {
164             rc = _libssh2_error(hosts->session, LIBSSH2_ERROR_ALLOC,
165                                 "Unable to allocate memory for host name");
166             goto error;
167         }
168         memcpy(entry->name, host, hostlen+1);
169         entry->name_len = hostlen;
170         break;
171     case LIBSSH2_KNOWNHOST_TYPE_SHA1:
172         rc = libssh2_base64_decode(hosts->session, &ptr, &ptrlen,
173                                    host, hostlen);
174         if(rc)
175             goto error;
176         entry->name = ptr;
177         entry->name_len = ptrlen;
178 
179         rc = libssh2_base64_decode(hosts->session, &ptr, &ptrlen,
180                                    salt, strlen(salt));
181         if(rc)
182             goto error;
183         entry->salt = ptr;
184         entry->salt_len = ptrlen;
185         break;
186     default:
187         rc = _libssh2_error(hosts->session, LIBSSH2_ERROR_METHOD_NOT_SUPPORTED,
188                             "Unknown host name type");
189         goto error;
190     }
191 
192     if(typemask & LIBSSH2_KNOWNHOST_KEYENC_BASE64) {
193         /* the provided key is base64 encoded already */
194         if(!keylen)
195             keylen = strlen(key);
196         entry->key = LIBSSH2_ALLOC(hosts->session, keylen+1);
197         if(!entry->key) {
198             rc = _libssh2_error(hosts->session, LIBSSH2_ERROR_ALLOC,
199                                 "Unable to allocate memory for key");
200             goto error;
201         }
202         memcpy(entry->key, key, keylen+1);
203         entry->key[keylen]=0; /* force a terminating zero trailer */
204     }
205     else {
206         /* key is raw, we base64 encode it and store it as such */
207         size_t nlen = _libssh2_base64_encode(hosts->session, key, keylen,
208                                              &ptr);
209         if(!nlen) {
210             rc = _libssh2_error(hosts->session, LIBSSH2_ERROR_ALLOC,
211                                 "Unable to allocate memory for "
212                                 "base64-encoded key");
213             goto error;
214         }
215 
216         entry->key = ptr;
217     }
218 
219     if (key_type_name && ((typemask & LIBSSH2_KNOWNHOST_KEY_MASK) ==
220                           LIBSSH2_KNOWNHOST_KEY_UNKNOWN)) {
221         entry->key_type_name = LIBSSH2_ALLOC(hosts->session, key_type_len+1);
222         if (!entry->key_type_name) {
223             rc = _libssh2_error(hosts->session, LIBSSH2_ERROR_ALLOC,
224                                 "Unable to allocate memory for key type");
225             goto error;
226         }
227         memcpy(entry->key_type_name, key_type_name, key_type_len);
228         entry->key_type_name[key_type_len]=0;
229         entry->key_type_len = key_type_len;
230     }
231 
232     if (comment) {
233         entry->comment = LIBSSH2_ALLOC(hosts->session, commentlen+1);
234         if(!entry->comment) {
235             rc = _libssh2_error(hosts->session, LIBSSH2_ERROR_ALLOC,
236                                 "Unable to allocate memory for comment");
237             goto error;
238         }
239         memcpy(entry->comment, comment, commentlen+1);
240         entry->comment[commentlen]=0; /* force a terminating zero trailer */
241         entry->comment_len = commentlen;
242     }
243     else {
244         entry->comment = NULL;
245     }
246 
247     /* add this new host to the big list of known hosts */
248     _libssh2_list_add(&hosts->head, &entry->node);
249 
250     if(store)
251         *store = knownhost_to_external(entry);
252 
253     return LIBSSH2_ERROR_NONE;
254   error:
255     free_host(hosts->session, entry);
256     return rc;
257 }
258 
259 /*
260  * libssh2_knownhost_add
261  *
262  * Add a host and its associated key to the collection of known hosts.
263  *
264  * The 'type' argument specifies on what format the given host and keys are:
265  *
266  * plain  - ascii "hostname.domain.tld"
267  * sha1   - SHA1(<salt> <host>) base64-encoded!
268  * custom - another hash
269  *
270  * If 'sha1' is selected as type, the salt must be provided to the salt
271  * argument. This too base64 encoded.
272  *
273  * The SHA-1 hash is what OpenSSH can be told to use in known_hosts files.  If
274  * a custom type is used, salt is ignored and you must provide the host
275  * pre-hashed when checking for it in the libssh2_knownhost_check() function.
276  *
277  * The keylen parameter may be omitted (zero) if the key is provided as a
278  * NULL-terminated base64-encoded string.
279  */
280 
281 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)282 libssh2_knownhost_add(LIBSSH2_KNOWNHOSTS *hosts,
283                       const char *host, const char *salt,
284                       const char *key, size_t keylen,
285                       int typemask, struct libssh2_knownhost **store)
286 {
287     return knownhost_add(hosts, host, salt, NULL, 0, key, keylen, NULL,
288                          0, typemask, store);
289 }
290 
291 
292 /*
293  * libssh2_knownhost_addc
294  *
295  * Add a host and its associated key to the collection of known hosts.
296  *
297  * Takes a comment argument that may be NULL.  A NULL comment indicates
298  * there is no comment and the entry will end directly after the key
299  * when written out to a file.  An empty string "" comment will indicate an
300  * empty comment which will cause a single space to be written after the key.
301  *
302  * The 'type' argument specifies on what format the given host and keys are:
303  *
304  * plain  - ascii "hostname.domain.tld"
305  * sha1   - SHA1(<salt> <host>) base64-encoded!
306  * custom - another hash
307  *
308  * If 'sha1' is selected as type, the salt must be provided to the salt
309  * argument. This too base64 encoded.
310  *
311  * The SHA-1 hash is what OpenSSH can be told to use in known_hosts files.  If
312  * a custom type is used, salt is ignored and you must provide the host
313  * pre-hashed when checking for it in the libssh2_knownhost_check() function.
314  *
315  * The keylen parameter may be omitted (zero) if the key is provided as a
316  * NULL-terminated base64-encoded string.
317  */
318 
319 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)320 libssh2_knownhost_addc(LIBSSH2_KNOWNHOSTS *hosts,
321                        const char *host, const char *salt,
322                        const char *key, size_t keylen,
323                        const char *comment, size_t commentlen,
324                        int typemask, struct libssh2_knownhost **store)
325 {
326     return knownhost_add(hosts, host, salt, NULL, 0, key, keylen,
327                          comment, commentlen, typemask, store);
328 }
329 
330 /*
331  * knownhost_check
332  *
333  * Check a host and its associated key against the collection of known hosts.
334  *
335  * The typemask is the type/format of the given host name and key
336  *
337  * plain  - ascii "hostname.domain.tld"
338  * sha1   - NOT SUPPORTED AS INPUT
339  * custom - prehashed base64 encoded. Note that this cannot use any salts.
340  *
341  * Returns:
342  *
343  * LIBSSH2_KNOWNHOST_CHECK_FAILURE
344  * LIBSSH2_KNOWNHOST_CHECK_NOTFOUND
345  * LIBSSH2_KNOWNHOST_CHECK_MATCH
346  * LIBSSH2_KNOWNHOST_CHECK_MISMATCH
347  */
348 static int
knownhost_check(LIBSSH2_KNOWNHOSTS * hosts,const char * hostp,int port,const char * key,size_t keylen,int typemask,struct libssh2_knownhost ** ext)349 knownhost_check(LIBSSH2_KNOWNHOSTS *hosts,
350                 const char *hostp, int port,
351                 const char *key, size_t keylen,
352                 int typemask,
353                 struct libssh2_knownhost **ext)
354 {
355     struct known_host *node;
356     struct known_host *badkey = NULL;
357     int type = typemask & LIBSSH2_KNOWNHOST_TYPE_MASK;
358     char *keyalloc = NULL;
359     int rc = LIBSSH2_KNOWNHOST_CHECK_NOTFOUND;
360     char hostbuff[270]; /* most host names can't be longer than like 256 */
361     const char *host;
362     int numcheck; /* number of host combos to check */
363     int match = 0;
364 
365     if(type == LIBSSH2_KNOWNHOST_TYPE_SHA1)
366         /* we can't work with a sha1 as given input */
367         return LIBSSH2_KNOWNHOST_CHECK_MISMATCH;
368 
369     /* if a port number is given, check for a '[host]:port' first before the
370        plain 'host' */
371     if(port >= 0) {
372         int len = snprintf(hostbuff, sizeof(hostbuff), "[%s]:%d", hostp, port);
373         if (len < 0 || len >= (int)sizeof(hostbuff)) {
374             _libssh2_error(hosts->session,
375                            LIBSSH2_ERROR_BUFFER_TOO_SMALL,
376                            "Known-host write buffer too small");
377             return LIBSSH2_KNOWNHOST_CHECK_FAILURE;
378         }
379         host = hostbuff;
380         numcheck = 2; /* check both combos, start with this */
381     }
382     else {
383         host = hostp;
384         numcheck = 1; /* only check this host version */
385     }
386 
387     if(!(typemask & LIBSSH2_KNOWNHOST_KEYENC_BASE64)) {
388         /* we got a raw key input, convert it to base64 for the checks below */
389         size_t nlen = _libssh2_base64_encode(hosts->session, key, keylen,
390                                              &keyalloc);
391         if(!nlen) {
392             _libssh2_error(hosts->session, LIBSSH2_ERROR_ALLOC,
393                            "Unable to allocate memory for base64-encoded "
394                            "key");
395             return LIBSSH2_KNOWNHOST_CHECK_FAILURE;
396         }
397 
398         /* make the key point to this */
399         key = keyalloc;
400     }
401 
402     do {
403         node = _libssh2_list_first(&hosts->head);
404         while (node) {
405             switch(node->typemask & LIBSSH2_KNOWNHOST_TYPE_MASK) {
406             case LIBSSH2_KNOWNHOST_TYPE_PLAIN:
407                 if(type == LIBSSH2_KNOWNHOST_TYPE_PLAIN)
408                     match = !strcmp(host, node->name);
409                 break;
410             case LIBSSH2_KNOWNHOST_TYPE_CUSTOM:
411                 if(type == LIBSSH2_KNOWNHOST_TYPE_CUSTOM)
412                     match = !strcmp(host, node->name);
413                 break;
414             case LIBSSH2_KNOWNHOST_TYPE_SHA1:
415                 if(type == LIBSSH2_KNOWNHOST_TYPE_PLAIN) {
416                     /* when we have the sha1 version stored, we can use a
417                        plain input to produce a hash to compare with the
418                        stored hash.
419                     */
420                     unsigned char hash[SHA_DIGEST_LENGTH];
421                     libssh2_hmac_ctx ctx;
422                     libssh2_hmac_ctx_init(ctx);
423 
424                     if(SHA_DIGEST_LENGTH != node->name_len) {
425                         /* the name hash length must be the sha1 size or
426                            we can't match it */
427                         break;
428                     }
429                     libssh2_hmac_sha1_init(&ctx, (unsigned char *)node->salt,
430                                            node->salt_len);
431                     libssh2_hmac_update(ctx, (unsigned char *)host,
432                                         strlen(host));
433                     libssh2_hmac_final(ctx, hash);
434                     libssh2_hmac_cleanup(&ctx);
435 
436                     if(!memcmp(hash, node->name, SHA_DIGEST_LENGTH))
437                         /* this is a node we're interested in */
438                         match = 1;
439                 }
440                 break;
441             default: /* unsupported type */
442                 break;
443             }
444             if(match) {
445                 int host_key_type = typemask & LIBSSH2_KNOWNHOST_KEY_MASK;
446                 int known_key_type =
447                     node->typemask & LIBSSH2_KNOWNHOST_KEY_MASK;
448                 /* match on key type as follows:
449                    - never match on an unknown key type
450                    - if key_type is set to zero, ignore it an match always
451                    - otherwise match when both key types are equal
452                 */
453                 if ( (host_key_type != LIBSSH2_KNOWNHOST_KEY_UNKNOWN ) &&
454                      ( (host_key_type == 0) ||
455                        (host_key_type == known_key_type) ) ) {
456                     /* host name and key type match, now compare the keys */
457                     if(!strcmp(key, node->key)) {
458                         /* they match! */
459                         if (ext)
460                             *ext = knownhost_to_external(node);
461                         badkey = NULL;
462                         rc = LIBSSH2_KNOWNHOST_CHECK_MATCH;
463                         break;
464                     }
465                     else {
466                         /* remember the first node that had a host match but a
467                            failed key match since we continue our search from
468                            here */
469                         if(!badkey)
470                             badkey = node;
471                     }
472                 }
473                 match = 0; /* don't count this as a match anymore */
474             }
475             node= _libssh2_list_next(&node->node);
476         }
477         host = hostp;
478     } while(!match && --numcheck);
479 
480     if(badkey) {
481         /* key mismatch */
482         if (ext)
483             *ext = knownhost_to_external(badkey);
484         rc = LIBSSH2_KNOWNHOST_CHECK_MISMATCH;
485     }
486 
487     if(keyalloc)
488         LIBSSH2_FREE(hosts->session, keyalloc);
489 
490     return rc;
491 }
492 
493 /*
494  * libssh2_knownhost_check
495  *
496  * Check a host and its associated key against the collection of known hosts.
497  *
498  * The typemask is the type/format of the given host name and key
499  *
500  * plain  - ascii "hostname.domain.tld"
501  * sha1   - NOT SUPPORTED AS INPUT
502  * custom - prehashed base64 encoded. Note that this cannot use any salts.
503  *
504  * Returns:
505  *
506  * LIBSSH2_KNOWNHOST_CHECK_FAILURE
507  * LIBSSH2_KNOWNHOST_CHECK_NOTFOUND
508  * LIBSSH2_KNOWNHOST_CHECK_MATCH
509  * LIBSSH2_KNOWNHOST_CHECK_MISMATCH
510  */
511 LIBSSH2_API int
libssh2_knownhost_check(LIBSSH2_KNOWNHOSTS * hosts,const char * hostp,const char * key,size_t keylen,int typemask,struct libssh2_knownhost ** ext)512 libssh2_knownhost_check(LIBSSH2_KNOWNHOSTS *hosts,
513                         const char *hostp, const char *key, size_t keylen,
514                         int typemask,
515                         struct libssh2_knownhost **ext)
516 {
517     return knownhost_check(hosts, hostp, -1, key, keylen,
518                            typemask, ext);
519 }
520 
521 /*
522  * libssh2_knownhost_checkp
523  *
524  * Check a host+port and its associated key against the collection of known
525  * hosts.
526  *
527  * Note that if 'port' is specified as greater than zero, the check function
528  * will be able to check for a dedicated key for this particular host+port
529  * combo, and if 'port' is negative it only checks for the generic host key.
530  *
531  * The typemask is the type/format of the given host name and key
532  *
533  * plain  - ascii "hostname.domain.tld"
534  * sha1   - NOT SUPPORTED AS INPUT
535  * custom - prehashed base64 encoded. Note that this cannot use any salts.
536  *
537  * Returns:
538  *
539  * LIBSSH2_KNOWNHOST_CHECK_FAILURE
540  * LIBSSH2_KNOWNHOST_CHECK_NOTFOUND
541  * LIBSSH2_KNOWNHOST_CHECK_MATCH
542  * LIBSSH2_KNOWNHOST_CHECK_MISMATCH
543  */
544 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)545 libssh2_knownhost_checkp(LIBSSH2_KNOWNHOSTS *hosts,
546                          const char *hostp, int port,
547                          const char *key, size_t keylen,
548                          int typemask,
549                          struct libssh2_knownhost **ext)
550 {
551     return knownhost_check(hosts, hostp, port, key, keylen,
552                            typemask, ext);
553 }
554 
555 
556 /*
557  * libssh2_knownhost_del
558  *
559  * Remove a host from the collection of known hosts.
560  *
561  */
562 LIBSSH2_API int
libssh2_knownhost_del(LIBSSH2_KNOWNHOSTS * hosts,struct libssh2_knownhost * entry)563 libssh2_knownhost_del(LIBSSH2_KNOWNHOSTS *hosts,
564                       struct libssh2_knownhost *entry)
565 {
566     struct known_host *node;
567 
568     /* check that this was retrieved the right way or get out */
569     if(!entry || (entry->magic != KNOWNHOST_MAGIC))
570         return _libssh2_error(hosts->session, LIBSSH2_ERROR_INVAL,
571                               "Invalid host information");
572 
573     /* get the internal node pointer */
574     node = entry->node;
575 
576     /* unlink from the list of all hosts */
577     _libssh2_list_remove(&node->node);
578 
579     /* clear the struct now since the memory in which it is allocated is
580        about to be freed! */
581     memset(entry, 0, sizeof(struct libssh2_knownhost));
582 
583     /* free all resources */
584     free_host(hosts->session, node);
585 
586     return 0;
587 }
588 
589 /*
590  * libssh2_knownhost_free
591  *
592  * Free an entire collection of known hosts.
593  *
594  */
595 LIBSSH2_API void
libssh2_knownhost_free(LIBSSH2_KNOWNHOSTS * hosts)596 libssh2_knownhost_free(LIBSSH2_KNOWNHOSTS *hosts)
597 {
598     struct known_host *node;
599     struct known_host *next;
600 
601     for(node = _libssh2_list_first(&hosts->head); node; node = next) {
602         next = _libssh2_list_next(&node->node);
603         free_host(hosts->session, node);
604     }
605     LIBSSH2_FREE(hosts->session, hosts);
606 }
607 
608 
609 /* old style plain text: [name]([,][name])*
610 
611    for the sake of simplicity, we add them as separate hosts with the same
612    key
613 */
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)614 static int oldstyle_hostline(LIBSSH2_KNOWNHOSTS *hosts,
615                              const char *host, size_t hostlen,
616                              const char *key_type_name, size_t key_type_len,
617                              const char *key, size_t keylen, int key_type,
618                              const char *comment, size_t commentlen)
619 {
620     int rc = 0;
621     size_t namelen = 0;
622     const char *name = host + hostlen;
623 
624     if(hostlen < 1)
625         return _libssh2_error(hosts->session,
626                               LIBSSH2_ERROR_METHOD_NOT_SUPPORTED,
627                               "Failed to parse known_hosts line "
628                               "(no host names)");
629 
630     while(name > host) {
631         --name;
632         ++namelen;
633 
634         /* when we get the the start or see a comma coming up, add the host
635            name to the collection */
636         if((name == host) || (*(name-1) == ',')) {
637 
638             char hostbuf[256];
639 
640             /* make sure we don't overflow the buffer */
641             if(namelen >= sizeof(hostbuf)-1)
642                 return _libssh2_error(hosts->session,
643                                       LIBSSH2_ERROR_METHOD_NOT_SUPPORTED,
644                                       "Failed to parse known_hosts line "
645                                       "(unexpected length)");
646 
647             /* copy host name to the temp buffer and zero terminate */
648             memcpy(hostbuf, name, namelen);
649             hostbuf[namelen]=0;
650 
651             rc = knownhost_add(hosts, hostbuf, NULL,
652                                key_type_name, key_type_len,
653                                key, keylen,
654                                comment, commentlen,
655                                key_type | LIBSSH2_KNOWNHOST_TYPE_PLAIN |
656                                LIBSSH2_KNOWNHOST_KEYENC_BASE64, NULL);
657             if(rc)
658                 return rc;
659 
660             if(name > host) {
661                 namelen = 0;
662                 --name; /* skip comma */
663             }
664         }
665     }
666 
667     return rc;
668 }
669 
670 /* |1|[salt]|[hash] */
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)671 static int hashed_hostline(LIBSSH2_KNOWNHOSTS *hosts,
672                            const char *host, size_t hostlen,
673                            const char *key_type_name, size_t key_type_len,
674                            const char *key, size_t keylen, int key_type,
675                            const char *comment, size_t commentlen)
676 {
677     const char *p;
678     char saltbuf[32];
679     char hostbuf[256];
680 
681     const char *salt = &host[3]; /* skip the magic marker */
682     hostlen -= 3;    /* deduct the marker */
683 
684     /* this is where the salt starts, find the end of it */
685     for(p = salt; *p && (*p != '|'); p++)
686         ;
687 
688     if(*p=='|') {
689         const char *hash = NULL;
690         size_t saltlen = p - salt;
691         if(saltlen >= (sizeof(saltbuf)-1)) /* weird length */
692             return _libssh2_error(hosts->session,
693                                   LIBSSH2_ERROR_METHOD_NOT_SUPPORTED,
694                                   "Failed to parse known_hosts line "
695                                   "(unexpectedly long salt)");
696 
697         memcpy(saltbuf, salt, saltlen);
698         saltbuf[saltlen] = 0; /* zero terminate */
699         salt = saltbuf; /* point to the stack based buffer */
700 
701         hash = p+1; /* the host hash is after the separator */
702 
703         /* now make the host point to the hash */
704         host = hash;
705         hostlen -= saltlen+1; /* deduct the salt and separator */
706 
707         /* check that the lengths seem sensible */
708         if(hostlen >= sizeof(hostbuf)-1)
709             return _libssh2_error(hosts->session,
710                                   LIBSSH2_ERROR_METHOD_NOT_SUPPORTED,
711                                   "Failed to parse known_hosts line "
712                                   "(unexpected length)");
713 
714         memcpy(hostbuf, host, hostlen);
715         hostbuf[hostlen]=0;
716 
717         return knownhost_add(hosts, hostbuf, salt,
718                              key_type_name, key_type_len,
719                              key, keylen,
720                              comment, commentlen,
721                              key_type | LIBSSH2_KNOWNHOST_TYPE_SHA1 |
722                              LIBSSH2_KNOWNHOST_KEYENC_BASE64, NULL);
723     }
724     else
725         return 0; /* XXX: This should be an error, shouldn't it? */
726 }
727 
728 /*
729  * hostline()
730  *
731  * Parse a single known_host line pre-split into host and key.
732  *
733  * The key part may include an optional comment which will be parsed here
734  * for ssh-rsa and ssh-dsa keys.  Comments in other key types aren't handled.
735  *
736  * The function assumes new-lines have already been removed from the arguments.
737  */
hostline(LIBSSH2_KNOWNHOSTS * hosts,const char * host,size_t hostlen,const char * key,size_t keylen)738 static int hostline(LIBSSH2_KNOWNHOSTS *hosts,
739                     const char *host, size_t hostlen,
740                     const char *key, size_t keylen)
741 {
742     const char *comment = NULL;
743     const char *key_type_name = NULL;
744     size_t commentlen = 0;
745     size_t key_type_len = 0;
746     int key_type;
747 
748     /* make some checks that the lengths seem sensible */
749     if(keylen < 20)
750         return _libssh2_error(hosts->session,
751                               LIBSSH2_ERROR_METHOD_NOT_SUPPORTED,
752                               "Failed to parse known_hosts line "
753                               "(key too short)");
754 
755     switch(key[0]) {
756     case '0': case '1': case '2': case '3': case '4':
757     case '5': case '6': case '7': case '8': case '9':
758         key_type = LIBSSH2_KNOWNHOST_KEY_RSA1;
759 
760         /* Note that the old-style keys (RSA1) aren't truly base64, but we
761          * claim it is for now since we can get away with strcmp()ing the
762          * entire anything anyway! We need to check and fix these to make them
763          * work properly.
764          */
765         break;
766 
767     default:
768         key_type_name = key;
769         while (keylen && *key &&
770                (*key != ' ') && (*key != '\t')) {
771             key++;
772             keylen--;
773         }
774         key_type_len = key - key_type_name;
775 
776         if (!strncmp(key_type_name, "ssh-dss", key_type_len))
777             key_type = LIBSSH2_KNOWNHOST_KEY_SSHDSS;
778         else if (!strncmp(key_type_name, "ssh-rsa", key_type_len))
779             key_type = LIBSSH2_KNOWNHOST_KEY_SSHRSA;
780         else
781             key_type = LIBSSH2_KNOWNHOST_KEY_UNKNOWN;
782 
783         /* skip whitespaces */
784         while((*key ==' ') || (*key == '\t')) {
785             key++;
786             keylen--;
787         }
788 
789         comment = key;
790         commentlen = keylen;
791 
792         /* move over key */
793         while(commentlen && *comment &&
794               (*comment != ' ') && (*comment != '\t')) {
795             comment++;
796             commentlen--;
797         }
798 
799         /* reduce key by comment length */
800         keylen -= commentlen;
801 
802         /* Distinguish empty comment (a space) from no comment (no space) */
803         if (commentlen == 0)
804             comment = NULL;
805 
806         /* skip whitespaces */
807         while(commentlen && *comment &&
808               ((*comment ==' ') || (*comment == '\t'))) {
809             comment++;
810             commentlen--;
811         }
812         break;
813     }
814 
815     /* Figure out host format */
816     if((hostlen >2) && memcmp(host, "|1|", 3)) {
817         /* old style plain text: [name]([,][name])*
818 
819            for the sake of simplicity, we add them as separate hosts with the
820            same key
821         */
822         return oldstyle_hostline(hosts, host, hostlen, key_type_name,
823                                  key_type_len, key, keylen, key_type,
824                                  comment, commentlen);
825     }
826     else {
827         /* |1|[salt]|[hash] */
828         return hashed_hostline(hosts, host, hostlen, key_type_name,
829                                key_type_len, key, keylen, key_type,
830                                comment, commentlen);
831     }
832 }
833 
834 /*
835  * libssh2_knownhost_readline()
836  *
837  * Pass in a line of a file of 'type'.
838  *
839  * LIBSSH2_KNOWNHOST_FILE_OPENSSH is the only supported type.
840  *
841  * OpenSSH line format:
842  *
843  * <host> <key>
844  *
845  * Where the two parts can be created like:
846  *
847  * <host> can be either
848  * <name> or <hash>
849  *
850  * <name> consists of
851  * [name] optionally followed by [,name] one or more times
852  *
853  * <hash> consists of
854  * |1|<salt>|hash
855  *
856  * <key> can be one of:
857  * [RSA bits] [e] [n as a decimal number]
858  * 'ssh-dss' [base64-encoded-key]
859  * 'ssh-rsa' [base64-encoded-key]
860  *
861  */
862 LIBSSH2_API int
libssh2_knownhost_readline(LIBSSH2_KNOWNHOSTS * hosts,const char * line,size_t len,int type)863 libssh2_knownhost_readline(LIBSSH2_KNOWNHOSTS *hosts,
864                            const char *line, size_t len, int type)
865 {
866     const char *cp;
867     const char *hostp;
868     const char *keyp;
869     size_t hostlen;
870     size_t keylen;
871     int rc;
872 
873     if(type != LIBSSH2_KNOWNHOST_FILE_OPENSSH)
874         return _libssh2_error(hosts->session,
875                               LIBSSH2_ERROR_METHOD_NOT_SUPPORTED,
876                               "Unsupported type of known-host information "
877                               "store");
878 
879     cp = line;
880 
881     /* skip leading whitespaces */
882     while(len && ((*cp==' ') || (*cp == '\t'))) {
883         cp++;
884         len--;
885     }
886 
887     if(!len || !*cp || (*cp == '#') || (*cp == '\n'))
888         /* comment or empty line */
889         return LIBSSH2_ERROR_NONE;
890 
891     /* the host part starts here */
892     hostp = cp;
893 
894     /* move over the host to the separator */
895     while(len && *cp && (*cp!=' ') && (*cp != '\t')) {
896         cp++;
897         len--;
898     }
899 
900     hostlen = cp - hostp;
901 
902     /* the key starts after the whitespaces */
903     while(len && *cp && ((*cp==' ') || (*cp == '\t'))) {
904         cp++;
905         len--;
906     }
907 
908     if(!*cp || !len) /* illegal line */
909         return _libssh2_error(hosts->session,
910                               LIBSSH2_ERROR_METHOD_NOT_SUPPORTED,
911                               "Failed to parse known_hosts line");
912 
913     keyp = cp; /* the key starts here */
914     keylen = len;
915 
916     /* check if the line (key) ends with a newline and if so kill it */
917     while(len && *cp && (*cp != '\n')) {
918         cp++;
919         len--;
920     }
921 
922     /* zero terminate where the newline is */
923     if(*cp == '\n')
924         keylen--; /* don't include this in the count */
925 
926     /* deal with this one host+key line */
927     rc = hostline(hosts, hostp, hostlen, keyp, keylen);
928     if(rc)
929         return rc; /* failed */
930 
931     return LIBSSH2_ERROR_NONE; /* success */
932 }
933 
934 /*
935  * libssh2_knownhost_readfile
936  *
937  * Read hosts+key pairs from a given file.
938  *
939  * Returns a negative value for error or number of successfully added hosts.
940  *
941  */
942 
943 LIBSSH2_API int
libssh2_knownhost_readfile(LIBSSH2_KNOWNHOSTS * hosts,const char * filename,int type)944 libssh2_knownhost_readfile(LIBSSH2_KNOWNHOSTS *hosts,
945                            const char *filename, int type)
946 {
947     FILE *file;
948     int num = 0;
949     char buf[2048];
950 
951     if(type != LIBSSH2_KNOWNHOST_FILE_OPENSSH)
952         return _libssh2_error(hosts->session,
953                               LIBSSH2_ERROR_METHOD_NOT_SUPPORTED,
954                               "Unsupported type of known-host information "
955                               "store");
956 
957     file = fopen(filename, "r");
958     if(file) {
959         while(fgets(buf, sizeof(buf), file)) {
960             if(libssh2_knownhost_readline(hosts, buf, strlen(buf), type)) {
961                 num = _libssh2_error(hosts->session, LIBSSH2_ERROR_KNOWN_HOSTS,
962                                      "Failed to parse known hosts file");
963                 break;
964             }
965             num++;
966         }
967         fclose(file);
968     }
969     else
970         return _libssh2_error(hosts->session, LIBSSH2_ERROR_FILE,
971                               "Failed to open file");
972 
973     return num;
974 }
975 
976 /*
977  * knownhost_writeline()
978  *
979  * Ask libssh2 to convert a known host to an output line for storage.
980  *
981  * Note that this function returns LIBSSH2_ERROR_BUFFER_TOO_SMALL if the given
982  * output buffer is too small to hold the desired output. The 'outlen' field
983  * will then contain the size libssh2 wanted to store, which then is the
984  * smallest sufficient buffer it would require.
985  *
986  */
987 static int
knownhost_writeline(LIBSSH2_KNOWNHOSTS * hosts,struct known_host * node,char * buf,size_t buflen,size_t * outlen,int type)988 knownhost_writeline(LIBSSH2_KNOWNHOSTS *hosts,
989                     struct known_host *node,
990                     char *buf, size_t buflen,
991                     size_t *outlen, int type)
992 {
993     size_t required_size;
994 
995     const char *key_type_name;
996     size_t key_type_len;
997 
998     /* we only support this single file type for now, bail out on all other
999        attempts */
1000     if(type != LIBSSH2_KNOWNHOST_FILE_OPENSSH)
1001         return _libssh2_error(hosts->session,
1002                               LIBSSH2_ERROR_METHOD_NOT_SUPPORTED,
1003                               "Unsupported type of known-host information "
1004                               "store");
1005 
1006     switch(node->typemask & LIBSSH2_KNOWNHOST_KEY_MASK) {
1007     case LIBSSH2_KNOWNHOST_KEY_RSA1:
1008         key_type_name = NULL;
1009         key_type_len = 0;
1010         break;
1011     case LIBSSH2_KNOWNHOST_KEY_SSHRSA:
1012         key_type_name = "ssh-rsa";
1013         key_type_len = 7;
1014         break;
1015     case LIBSSH2_KNOWNHOST_KEY_SSHDSS:
1016         key_type_name = "ssh-dss";
1017         key_type_len = 7;
1018         break;
1019     case LIBSSH2_KNOWNHOST_KEY_UNKNOWN:
1020         key_type_name = node->key_type_name;
1021         if (key_type_name) {
1022             key_type_len = node->key_type_len;
1023             break;
1024         }
1025         /* otherwise fallback to default and error */
1026     default:
1027         return _libssh2_error(hosts->session,
1028                               LIBSSH2_ERROR_METHOD_NOT_SUPPORTED,
1029                               "Unsupported type of known-host entry");
1030     }
1031 
1032     /* When putting together the host line there are three aspects to consider:
1033        - Hashed (SHA1) or unhashed hostname
1034        - key name or no key name (RSA1)
1035        - comment or no comment
1036 
1037        This means there are 2^3 different formats:
1038        ("|1|%s|%s %s %s %s\n", salt, hashed_host, key_name, key, comment)
1039        ("|1|%s|%s %s %s\n", salt, hashed_host, key_name, key)
1040        ("|1|%s|%s %s %s\n", salt, hashed_host, key, comment)
1041        ("|1|%s|%s %s\n", salt, hashed_host, key)
1042        ("%s %s %s %s\n", host, key_name, key, comment)
1043        ("%s %s %s\n", host, key_name, key)
1044        ("%s %s %s\n", host, key, comment)
1045        ("%s %s\n", host, key)
1046 
1047        Even if the buffer is too small, we have to set outlen to the number of
1048        characters the complete line would have taken.  We also don't write
1049        anything to the buffer unless we are sure we can write everything to the
1050        buffer. */
1051 
1052     required_size = strlen(node->key);
1053 
1054     if(key_type_len)
1055         required_size += key_type_len + 1; /* ' ' = 1 */
1056     if(node->comment)
1057         required_size += node->comment_len + 1; /* ' ' = 1 */
1058 
1059     if((node->typemask & LIBSSH2_KNOWNHOST_TYPE_MASK) ==
1060        LIBSSH2_KNOWNHOST_TYPE_SHA1) {
1061         char *namealloc;
1062         size_t name_base64_len;
1063         char *saltalloc;
1064         size_t salt_base64_len;
1065 
1066         name_base64_len = _libssh2_base64_encode(hosts->session, node->name,
1067                                                  node->name_len, &namealloc);
1068         if(!name_base64_len)
1069             return _libssh2_error(hosts->session, LIBSSH2_ERROR_ALLOC,
1070                                   "Unable to allocate memory for "
1071                                   "base64-encoded host name");
1072 
1073         salt_base64_len = _libssh2_base64_encode(hosts->session,
1074                                                  node->salt, node->salt_len,
1075                                                  &saltalloc);
1076         if(!salt_base64_len) {
1077             LIBSSH2_FREE(hosts->session, namealloc);
1078             return _libssh2_error(hosts->session, LIBSSH2_ERROR_ALLOC,
1079                                   "Unable to allocate memory for "
1080                                   "base64-encoded salt");
1081         }
1082 
1083         required_size += salt_base64_len + name_base64_len + 7;
1084         /* |1| + | + ' ' + \n + \0 = 7 */
1085 
1086         if(required_size <= buflen) {
1087             if(node->comment && key_type_len)
1088                 snprintf(buf, buflen, "|1|%s|%s %s %s %s\n", saltalloc,
1089                          namealloc, key_type_name, node->key, node->comment);
1090             else if (node->comment)
1091                 snprintf(buf, buflen, "|1|%s|%s %s %s\n", saltalloc, namealloc,
1092                          node->key, node->comment);
1093             else if (key_type_len)
1094                 snprintf(buf, buflen, "|1|%s|%s %s %s\n", saltalloc, namealloc,
1095                          key_type_name, node->key);
1096             else
1097                 snprintf(buf, buflen, "|1|%s|%s %s\n", saltalloc, namealloc,
1098                          node->key);
1099         }
1100 
1101         LIBSSH2_FREE(hosts->session, namealloc);
1102         LIBSSH2_FREE(hosts->session, saltalloc);
1103     }
1104     else {
1105         required_size += node->name_len + 3;
1106         /* ' ' + '\n' + \0 = 3 */
1107 
1108         if(required_size <= buflen) {
1109             if(node->comment && key_type_len)
1110                 snprintf(buf, buflen, "%s %s %s %s\n", node->name,
1111                          key_type_name, node->key, node->comment);
1112             else if (node->comment)
1113                 snprintf(buf, buflen, "%s %s %s\n", node->name, node->key,
1114                          node->comment);
1115             else if (key_type_len)
1116                 snprintf(buf, buflen, "%s %s %s\n", node->name, key_type_name,
1117                          node->key);
1118             else
1119                 snprintf(buf, buflen, "%s %s\n", node->name, node->key);
1120         }
1121     }
1122 
1123     /* we report the full length of the data with the trailing zero excluded */
1124     *outlen = required_size-1;
1125 
1126     if(required_size <= buflen)
1127         return LIBSSH2_ERROR_NONE;
1128     else
1129         return _libssh2_error(hosts->session, LIBSSH2_ERROR_BUFFER_TOO_SMALL,
1130                               "Known-host write buffer too small");
1131 }
1132 
1133 /*
1134  * libssh2_knownhost_writeline()
1135  *
1136  * Ask libssh2 to convert a known host to an output line for storage.
1137  *
1138  * Note that this function returns LIBSSH2_ERROR_BUFFER_TOO_SMALL if the given
1139  * output buffer is too small to hold the desired output.
1140  */
1141 LIBSSH2_API int
libssh2_knownhost_writeline(LIBSSH2_KNOWNHOSTS * hosts,struct libssh2_knownhost * known,char * buffer,size_t buflen,size_t * outlen,int type)1142 libssh2_knownhost_writeline(LIBSSH2_KNOWNHOSTS *hosts,
1143                             struct libssh2_knownhost *known,
1144                             char *buffer, size_t buflen,
1145                             size_t *outlen, /* the amount of written data */
1146                             int type)
1147 {
1148     struct known_host *node;
1149 
1150     if(known->magic != KNOWNHOST_MAGIC)
1151         return _libssh2_error(hosts->session, LIBSSH2_ERROR_INVAL,
1152                               "Invalid host information");
1153 
1154     node = known->node;
1155 
1156     return knownhost_writeline(hosts, node, buffer, buflen, outlen, type);
1157 }
1158 
1159 /*
1160  * libssh2_knownhost_writefile()
1161  *
1162  * Write hosts+key pairs to the given file.
1163  */
1164 LIBSSH2_API int
libssh2_knownhost_writefile(LIBSSH2_KNOWNHOSTS * hosts,const char * filename,int type)1165 libssh2_knownhost_writefile(LIBSSH2_KNOWNHOSTS *hosts,
1166                             const char *filename, int type)
1167 {
1168     struct known_host *node;
1169     FILE *file;
1170     int rc = LIBSSH2_ERROR_NONE;
1171     char buffer[2048];
1172 
1173     /* we only support this single file type for now, bail out on all other
1174        attempts */
1175     if(type != LIBSSH2_KNOWNHOST_FILE_OPENSSH)
1176         return _libssh2_error(hosts->session,
1177                               LIBSSH2_ERROR_METHOD_NOT_SUPPORTED,
1178                               "Unsupported type of known-host information "
1179                               "store");
1180 
1181     file = fopen(filename, "w");
1182     if(!file)
1183         return _libssh2_error(hosts->session, LIBSSH2_ERROR_FILE,
1184                               "Failed to open file");
1185 
1186     for(node = _libssh2_list_first(&hosts->head);
1187         node;
1188         node = _libssh2_list_next(&node->node)) {
1189         size_t wrote = 0;
1190         size_t nwrote;
1191         rc = knownhost_writeline(hosts, node, buffer, sizeof(buffer), &wrote,
1192                                  type);
1193         if(rc)
1194             break;
1195 
1196         nwrote = fwrite(buffer, 1, wrote, file);
1197         if(nwrote != wrote) {
1198             /* failed to write the whole thing, bail out */
1199             rc = _libssh2_error(hosts->session, LIBSSH2_ERROR_FILE,
1200                                 "Write failed");
1201             break;
1202         }
1203     }
1204     fclose(file);
1205 
1206     return rc;
1207 }
1208 
1209 
1210 /*
1211  * libssh2_knownhost_get()
1212  *
1213  * Traverse the internal list of known hosts. Pass NULL to 'prev' to get
1214  * the first one.
1215  *
1216  * Returns:
1217  * 0 if a fine host was stored in 'store'
1218  * 1 if end of hosts
1219  * [negative] on errors
1220  */
1221 LIBSSH2_API int
libssh2_knownhost_get(LIBSSH2_KNOWNHOSTS * hosts,struct libssh2_knownhost ** ext,struct libssh2_knownhost * oprev)1222 libssh2_knownhost_get(LIBSSH2_KNOWNHOSTS *hosts,
1223                       struct libssh2_knownhost **ext,
1224                       struct libssh2_knownhost *oprev)
1225 {
1226     struct known_host *node;
1227     if(oprev && oprev->node) {
1228         /* we have a starting point */
1229         struct known_host *prev = oprev->node;
1230 
1231         /* get the next node in the list */
1232         node = _libssh2_list_next(&prev->node);
1233 
1234     }
1235     else
1236         node = _libssh2_list_first(&hosts->head);
1237 
1238     if(!node)
1239         /* no (more) node */
1240         return 1;
1241 
1242     *ext = knownhost_to_external(node);
1243 
1244     return 0;
1245 }
1246