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