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