]> git.proxmox.com Git - rustc.git/blob - src/vendor/libssh2-sys/libssh2/src/knownhost.c
New upstream version 1.19.0+dfsg1
[rustc.git] / src / vendor / libssh2-sys / libssh2 / src / knownhost.c
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
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 *
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 */
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
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
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
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
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
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
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
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
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 */
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] */
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 */
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
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
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
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
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
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
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 }