/src/libssh/src/knownhosts.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * known_hosts: Host and public key verification. |
3 | | * |
4 | | * This file is part of the SSH Library |
5 | | * |
6 | | * Copyright (c) 2003-2009 by Aris Adamantiadis |
7 | | * Copyright (c) 2009-2017 by Andreas Schneider <asn@cryptomilk.org> |
8 | | * |
9 | | * The SSH Library is free software; you can redistribute it and/or modify |
10 | | * it under the terms of the GNU Lesser General Public License as published by |
11 | | * the Free Software Foundation; either version 2.1 of the License, or (at your |
12 | | * option) any later version. |
13 | | * |
14 | | * The SSH Library is distributed in the hope that it will be useful, but |
15 | | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY |
16 | | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public |
17 | | * License for more details. |
18 | | * |
19 | | * You should have received a copy of the GNU Lesser General Public License |
20 | | * along with the SSH Library; see the file COPYING. If not, write to |
21 | | * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, |
22 | | * MA 02111-1307, USA. |
23 | | */ |
24 | | |
25 | | #include "config.h" |
26 | | |
27 | | #include <ctype.h> |
28 | | #include <errno.h> |
29 | | #include <stdio.h> |
30 | | #include <stdlib.h> |
31 | | |
32 | | #ifndef _WIN32 |
33 | | #include <arpa/inet.h> |
34 | | #include <netinet/in.h> |
35 | | #endif |
36 | | |
37 | | #include "libssh/priv.h" |
38 | | #include "libssh/dh.h" |
39 | | #include "libssh/session.h" |
40 | | #include "libssh/options.h" |
41 | | #include "libssh/misc.h" |
42 | | #include "libssh/pki.h" |
43 | | #include "libssh/dh.h" |
44 | | #include "libssh/knownhosts.h" |
45 | | #include "libssh/token.h" |
46 | | |
47 | | #ifndef MAX_LINE_SIZE |
48 | | #define MAX_LINE_SIZE 8192 |
49 | | #endif |
50 | | |
51 | | /** |
52 | | * @addtogroup libssh_session |
53 | | * |
54 | | * @{ |
55 | | */ |
56 | | |
57 | | static int hash_hostname(const char *name, |
58 | | unsigned char *salt, |
59 | | unsigned int salt_size, |
60 | | unsigned char **hash, |
61 | | size_t *hash_size) |
62 | 0 | { |
63 | 0 | int rc; |
64 | 0 | HMACCTX mac_ctx = NULL; |
65 | |
|
66 | 0 | mac_ctx = hmac_init(salt, salt_size, SSH_HMAC_SHA1); |
67 | 0 | if (mac_ctx == NULL) { |
68 | 0 | return SSH_ERROR; |
69 | 0 | } |
70 | | |
71 | 0 | rc = hmac_update(mac_ctx, name, strlen(name)); |
72 | 0 | if (rc != 1) |
73 | 0 | return SSH_ERROR; |
74 | | |
75 | 0 | rc = hmac_final(mac_ctx, *hash, hash_size); |
76 | 0 | if (rc != 1) |
77 | 0 | return SSH_ERROR; |
78 | | |
79 | 0 | return SSH_OK; |
80 | 0 | } |
81 | | |
82 | | static int match_hashed_hostname(const char *host, const char *hashed_host) |
83 | 0 | { |
84 | 0 | char *hashed = NULL; |
85 | 0 | char *b64_hash = NULL; |
86 | 0 | ssh_buffer salt = NULL; |
87 | 0 | ssh_buffer hash = NULL; |
88 | 0 | unsigned char hashed_buf[256] = {0}; |
89 | 0 | unsigned char *hashed_buf_ptr = hashed_buf; |
90 | 0 | size_t hashed_buf_size = sizeof(hashed_buf); |
91 | 0 | int cmp; |
92 | 0 | int rc; |
93 | 0 | int match = 0; |
94 | |
|
95 | 0 | cmp = strncmp(hashed_host, "|1|", 3); |
96 | 0 | if (cmp != 0) { |
97 | 0 | return 0; |
98 | 0 | } |
99 | | |
100 | 0 | hashed = strdup(hashed_host + 3); |
101 | 0 | if (hashed == NULL) { |
102 | 0 | return 0; |
103 | 0 | } |
104 | | |
105 | 0 | b64_hash = strchr(hashed, '|'); |
106 | 0 | if (b64_hash == NULL) { |
107 | 0 | goto error; |
108 | 0 | } |
109 | 0 | *b64_hash = '\0'; |
110 | 0 | b64_hash++; |
111 | |
|
112 | 0 | salt = base64_to_bin(hashed); |
113 | 0 | if (salt == NULL) { |
114 | 0 | goto error; |
115 | 0 | } |
116 | | |
117 | 0 | hash = base64_to_bin(b64_hash); |
118 | 0 | if (hash == NULL) { |
119 | 0 | goto error; |
120 | 0 | } |
121 | | |
122 | 0 | rc = hash_hostname(host, |
123 | 0 | ssh_buffer_get(salt), |
124 | 0 | ssh_buffer_get_len(salt), |
125 | 0 | &hashed_buf_ptr, |
126 | 0 | &hashed_buf_size); |
127 | 0 | if (rc != SSH_OK) { |
128 | 0 | goto error; |
129 | 0 | } |
130 | | |
131 | 0 | if (hashed_buf_size != ssh_buffer_get_len(hash)) { |
132 | 0 | goto error; |
133 | 0 | } |
134 | | |
135 | 0 | cmp = memcmp(hashed_buf, ssh_buffer_get(hash), hashed_buf_size); |
136 | 0 | if (cmp == 0) { |
137 | 0 | match = 1; |
138 | 0 | } |
139 | |
|
140 | 0 | error: |
141 | 0 | free(hashed); |
142 | 0 | SSH_BUFFER_FREE(salt); |
143 | 0 | SSH_BUFFER_FREE(hash); |
144 | |
|
145 | 0 | return match; |
146 | 0 | } |
147 | | |
148 | | /** |
149 | | * @brief Free an allocated ssh_knownhosts_entry. |
150 | | * |
151 | | * Use SSH_KNOWNHOSTS_ENTRY_FREE() to set the pointer to NULL. |
152 | | * |
153 | | * @param[in] entry The entry to free. |
154 | | */ |
155 | | void ssh_knownhosts_entry_free(struct ssh_knownhosts_entry *entry) |
156 | 0 | { |
157 | 0 | if (entry == NULL) { |
158 | 0 | return; |
159 | 0 | } |
160 | | |
161 | 0 | SAFE_FREE(entry->hostname); |
162 | 0 | SAFE_FREE(entry->unparsed); |
163 | 0 | ssh_key_free(entry->publickey); |
164 | 0 | SAFE_FREE(entry->comment); |
165 | 0 | SAFE_FREE(entry); |
166 | 0 | } |
167 | | |
168 | | static int known_hosts_read_line(FILE *fp, |
169 | | char *buf, |
170 | | size_t buf_size, |
171 | | size_t *buf_len, |
172 | | size_t *lineno) |
173 | 0 | { |
174 | 0 | while (fgets(buf, (int)buf_size, fp) != NULL) { |
175 | 0 | size_t len; |
176 | 0 | if (buf[0] == '\0') { |
177 | 0 | continue; |
178 | 0 | } |
179 | | |
180 | 0 | *lineno += 1; |
181 | 0 | len = strlen(buf); |
182 | 0 | if (buf_len != NULL) { |
183 | 0 | *buf_len = len; |
184 | 0 | } |
185 | 0 | if (buf[len - 1] == '\n' || feof(fp)) { |
186 | 0 | return 0; |
187 | 0 | } else { |
188 | 0 | errno = E2BIG; |
189 | 0 | return -1; |
190 | 0 | } |
191 | 0 | } |
192 | | |
193 | 0 | return -1; |
194 | 0 | } |
195 | | |
196 | | static int |
197 | | ssh_known_hosts_entries_compare(struct ssh_knownhosts_entry *k1, |
198 | | struct ssh_knownhosts_entry *k2) |
199 | 0 | { |
200 | 0 | int cmp; |
201 | |
|
202 | 0 | if (k1 == NULL || k2 == NULL) { |
203 | 0 | return 1; |
204 | 0 | } |
205 | | |
206 | 0 | cmp = strcmp(k1->hostname, k2->hostname); |
207 | 0 | if (cmp != 0) { |
208 | 0 | return cmp; |
209 | 0 | } |
210 | | |
211 | 0 | cmp = ssh_key_cmp(k1->publickey, k2->publickey, SSH_KEY_CMP_PUBLIC); |
212 | 0 | if (cmp != 0) { |
213 | 0 | return cmp; |
214 | 0 | } |
215 | | |
216 | 0 | return 0; |
217 | 0 | } |
218 | | |
219 | | /* This method reads the known_hosts file referenced by the path |
220 | | * in filename argument, and entries matching the match argument |
221 | | * will be added to the list in entries argument. |
222 | | * If the entries list is NULL, it will allocate a new list. Caller |
223 | | * is responsible to free it even if an error occurs. |
224 | | */ |
225 | | static int ssh_known_hosts_read_entries(const char *match, |
226 | | const char *filename, |
227 | | struct ssh_list **entries) |
228 | 0 | { |
229 | 0 | char line[MAX_LINE_SIZE]; |
230 | 0 | size_t lineno = 0; |
231 | 0 | size_t len = 0; |
232 | 0 | FILE *fp = NULL; |
233 | 0 | int rc; |
234 | |
|
235 | 0 | fp = fopen(filename, "r"); |
236 | 0 | if (fp == NULL) { |
237 | 0 | char err_msg[SSH_ERRNO_MSG_MAX] = {0}; |
238 | 0 | SSH_LOG(SSH_LOG_TRACE, "Failed to open the known_hosts file '%s': %s", |
239 | 0 | filename, ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); |
240 | | /* The missing file is not an error here */ |
241 | 0 | return SSH_OK; |
242 | 0 | } |
243 | | |
244 | 0 | if (*entries == NULL) { |
245 | 0 | *entries = ssh_list_new(); |
246 | 0 | if (*entries == NULL) { |
247 | 0 | fclose(fp); |
248 | 0 | return SSH_ERROR; |
249 | 0 | } |
250 | 0 | } |
251 | | |
252 | 0 | for (rc = known_hosts_read_line(fp, line, sizeof(line), &len, &lineno); |
253 | 0 | rc == 0; |
254 | 0 | rc = known_hosts_read_line(fp, line, sizeof(line), &len, &lineno)) { |
255 | 0 | struct ssh_knownhosts_entry *entry = NULL; |
256 | 0 | struct ssh_iterator *it = NULL; |
257 | 0 | char *p = NULL; |
258 | |
|
259 | 0 | if (line[len] != '\n') { |
260 | 0 | len = strcspn(line, "\n"); |
261 | 0 | } |
262 | 0 | line[len] = '\0'; |
263 | | |
264 | | /* Skip leading spaces */ |
265 | 0 | for (p = line; isspace((int)p[0]); p++); |
266 | | |
267 | | /* Skip comments and empty lines */ |
268 | 0 | if (p[0] == '\0' || p[0] == '#') { |
269 | 0 | continue; |
270 | 0 | } |
271 | | |
272 | | /* Skip lines starting with markers (@cert-authority, @revoked): |
273 | | * we do not completely support them anyway */ |
274 | 0 | if (p[0] == '@') { |
275 | 0 | continue; |
276 | 0 | } |
277 | | |
278 | 0 | rc = ssh_known_hosts_parse_line(match, |
279 | 0 | line, |
280 | 0 | &entry); |
281 | 0 | if (rc == SSH_AGAIN) { |
282 | 0 | continue; |
283 | 0 | } else if (rc != SSH_OK) { |
284 | 0 | goto error; |
285 | 0 | } |
286 | | |
287 | | /* Check for duplicates */ |
288 | 0 | for (it = ssh_list_get_iterator(*entries); |
289 | 0 | it != NULL; |
290 | 0 | it = it->next) { |
291 | 0 | struct ssh_knownhosts_entry *entry2 = NULL; |
292 | 0 | int cmp; |
293 | 0 | entry2 = ssh_iterator_value(struct ssh_knownhosts_entry *, it); |
294 | 0 | cmp = ssh_known_hosts_entries_compare(entry, entry2); |
295 | 0 | if (cmp == 0) { |
296 | 0 | ssh_knownhosts_entry_free(entry); |
297 | 0 | entry = NULL; |
298 | 0 | break; |
299 | 0 | } |
300 | 0 | } |
301 | 0 | if (entry != NULL) { |
302 | 0 | ssh_list_append(*entries, entry); |
303 | 0 | } |
304 | 0 | } |
305 | | |
306 | 0 | fclose(fp); |
307 | 0 | return SSH_OK; |
308 | 0 | error: |
309 | 0 | fclose(fp); |
310 | 0 | return SSH_ERROR; |
311 | 0 | } |
312 | | |
313 | | static char *ssh_session_get_host_port(ssh_session session) |
314 | 0 | { |
315 | 0 | char *host_port = NULL; |
316 | 0 | char *host = NULL; |
317 | |
|
318 | 0 | if (session->opts.host == NULL) { |
319 | 0 | ssh_set_error(session, |
320 | 0 | SSH_FATAL, |
321 | 0 | "Can't verify server in known hosts if the host we " |
322 | 0 | "should connect to has not been set"); |
323 | |
|
324 | 0 | return NULL; |
325 | 0 | } |
326 | | |
327 | 0 | host = ssh_lowercase(session->opts.host); |
328 | 0 | if (host == NULL) { |
329 | 0 | ssh_set_error_oom(session); |
330 | 0 | return NULL; |
331 | 0 | } |
332 | | |
333 | 0 | if (session->opts.port == 0 || session->opts.port == 22) { |
334 | 0 | host_port = host; |
335 | 0 | } else { |
336 | 0 | host_port = ssh_hostport(host, session->opts.port); |
337 | 0 | SAFE_FREE(host); |
338 | 0 | if (host_port == NULL) { |
339 | 0 | ssh_set_error_oom(session); |
340 | 0 | return NULL; |
341 | 0 | } |
342 | 0 | } |
343 | | |
344 | 0 | return host_port; |
345 | 0 | } |
346 | | |
347 | | /** |
348 | | * @internal |
349 | | * @brief Check which host keys should be preferred for the session. |
350 | | * |
351 | | * This checks the known_hosts file to find out which algorithms should be |
352 | | * preferred for the connection we are going to establish. |
353 | | * |
354 | | * @param[in] session The ssh session to use. |
355 | | * |
356 | | * @return A list of supported key types, NULL on error. |
357 | | */ |
358 | | struct ssh_list *ssh_known_hosts_get_algorithms(ssh_session session) |
359 | 0 | { |
360 | 0 | struct ssh_list *entry_list = NULL; |
361 | 0 | struct ssh_iterator *it = NULL; |
362 | 0 | char *host_port = NULL; |
363 | 0 | size_t count; |
364 | 0 | struct ssh_list *list = NULL; |
365 | 0 | int list_error = 0; |
366 | 0 | int rc; |
367 | |
|
368 | 0 | if (session->opts.knownhosts == NULL || |
369 | 0 | session->opts.global_knownhosts == NULL) { |
370 | 0 | if (ssh_options_apply(session) < 0) { |
371 | 0 | ssh_set_error(session, |
372 | 0 | SSH_REQUEST_DENIED, |
373 | 0 | "Can't find a known_hosts file"); |
374 | |
|
375 | 0 | return NULL; |
376 | 0 | } |
377 | 0 | } |
378 | | |
379 | 0 | host_port = ssh_session_get_host_port(session); |
380 | 0 | if (host_port == NULL) { |
381 | 0 | return NULL; |
382 | 0 | } |
383 | | |
384 | 0 | list = ssh_list_new(); |
385 | 0 | if (list == NULL) { |
386 | 0 | ssh_set_error_oom(session); |
387 | 0 | SAFE_FREE(host_port); |
388 | 0 | return NULL; |
389 | 0 | } |
390 | | |
391 | 0 | rc = ssh_known_hosts_read_entries(host_port, |
392 | 0 | session->opts.knownhosts, |
393 | 0 | &entry_list); |
394 | 0 | if (rc != 0) { |
395 | 0 | ssh_list_free(entry_list); |
396 | 0 | ssh_list_free(list); |
397 | 0 | return NULL; |
398 | 0 | } |
399 | | |
400 | 0 | rc = ssh_known_hosts_read_entries(host_port, |
401 | 0 | session->opts.global_knownhosts, |
402 | 0 | &entry_list); |
403 | 0 | SAFE_FREE(host_port); |
404 | 0 | if (rc != 0) { |
405 | 0 | ssh_list_free(entry_list); |
406 | 0 | ssh_list_free(list); |
407 | 0 | return NULL; |
408 | 0 | } |
409 | | |
410 | 0 | if (entry_list == NULL) { |
411 | 0 | ssh_list_free(list); |
412 | 0 | return NULL; |
413 | 0 | } |
414 | | |
415 | 0 | count = ssh_list_count(entry_list); |
416 | 0 | if (count == 0) { |
417 | 0 | ssh_list_free(list); |
418 | 0 | ssh_list_free(entry_list); |
419 | 0 | return NULL; |
420 | 0 | } |
421 | | |
422 | 0 | for (it = ssh_list_get_iterator(entry_list); |
423 | 0 | it != NULL; |
424 | 0 | it = ssh_list_get_iterator(entry_list)) { |
425 | 0 | struct ssh_iterator *it2 = NULL; |
426 | 0 | struct ssh_knownhosts_entry *entry = NULL; |
427 | 0 | const char *algo = NULL; |
428 | 0 | bool present = false; |
429 | |
|
430 | 0 | entry = ssh_iterator_value(struct ssh_knownhosts_entry *, it); |
431 | 0 | algo = entry->publickey->type_c; |
432 | | |
433 | | /* Check for duplicates */ |
434 | 0 | for (it2 = ssh_list_get_iterator(list); |
435 | 0 | it2 != NULL; |
436 | 0 | it2 = it2->next) { |
437 | 0 | char *alg2 = ssh_iterator_value(char *, it2); |
438 | 0 | int cmp = strcmp(alg2, algo); |
439 | 0 | if (cmp == 0) { |
440 | 0 | present = true; |
441 | 0 | break; |
442 | 0 | } |
443 | 0 | } |
444 | | |
445 | | /* Add to the new list only if it is unique */ |
446 | 0 | if (!present) { |
447 | 0 | rc = ssh_list_append(list, algo); |
448 | 0 | if (rc != SSH_OK) { |
449 | 0 | list_error = 1; |
450 | 0 | } |
451 | 0 | } |
452 | |
|
453 | 0 | ssh_knownhosts_entry_free(entry); |
454 | 0 | ssh_list_remove(entry_list, it); |
455 | 0 | } |
456 | 0 | ssh_list_free(entry_list); |
457 | 0 | if (list_error) { |
458 | 0 | goto error; |
459 | 0 | } |
460 | | |
461 | 0 | return list; |
462 | 0 | error: |
463 | 0 | ssh_list_free(list); |
464 | 0 | return NULL; |
465 | 0 | } |
466 | | |
467 | | /** |
468 | | * @internal |
469 | | * |
470 | | * @brief Returns a static string containing a list of the signature types the |
471 | | * given key type can generate. |
472 | | * |
473 | | * @returns A static cstring containing the signature types the key is able to |
474 | | * generate separated by commas; NULL in case of error |
475 | | */ |
476 | | static const char *ssh_known_host_sigs_from_hostkey_type(enum ssh_keytypes_e type) |
477 | 0 | { |
478 | 0 | switch (type) { |
479 | 0 | case SSH_KEYTYPE_RSA: |
480 | 0 | return "rsa-sha2-512,rsa-sha2-256,ssh-rsa"; |
481 | 0 | case SSH_KEYTYPE_ED25519: |
482 | 0 | return "ssh-ed25519"; |
483 | 0 | case SSH_KEYTYPE_SK_ED25519: |
484 | 0 | return "sk-ssh-ed25519@openssh.com"; |
485 | 0 | #ifdef HAVE_ECC |
486 | 0 | case SSH_KEYTYPE_ECDSA_P256: |
487 | 0 | return "ecdsa-sha2-nistp256"; |
488 | 0 | case SSH_KEYTYPE_ECDSA_P384: |
489 | 0 | return "ecdsa-sha2-nistp384"; |
490 | 0 | case SSH_KEYTYPE_ECDSA_P521: |
491 | 0 | return "ecdsa-sha2-nistp521"; |
492 | 0 | case SSH_KEYTYPE_SK_ECDSA: |
493 | 0 | return "sk-ecdsa-sha2-nistp256@openssh.com"; |
494 | | #else |
495 | | case SSH_KEYTYPE_ECDSA_P256: |
496 | | case SSH_KEYTYPE_ECDSA_P384: |
497 | | case SSH_KEYTYPE_ECDSA_P521: |
498 | | SSH_LOG(SSH_LOG_WARN, "ECDSA keys are not supported by this build"); |
499 | | break; |
500 | | #endif |
501 | 0 | case SSH_KEYTYPE_UNKNOWN: |
502 | 0 | default: |
503 | 0 | SSH_LOG(SSH_LOG_TRACE, |
504 | 0 | "The given type %d is not a base private key type " |
505 | 0 | "or is unsupported", |
506 | 0 | type); |
507 | 0 | } |
508 | | |
509 | 0 | return NULL; |
510 | 0 | } |
511 | | |
512 | | /** |
513 | | * @internal |
514 | | * @brief Get the host keys algorithms identifiers from the known_hosts files |
515 | | * |
516 | | * This expands the signatures types that can be generated from the keys types |
517 | | * present in the known_hosts files |
518 | | * |
519 | | * @param[in] session The ssh session to use. |
520 | | * |
521 | | * @return A newly allocated cstring containing a list of signature algorithms |
522 | | * that can be generated by the host using the keys listed in the known_hosts |
523 | | * files, NULL on error. |
524 | | */ |
525 | | char *ssh_known_hosts_get_algorithms_names(ssh_session session) |
526 | 0 | { |
527 | 0 | char methods_buffer[256 + 1] = {0}; |
528 | 0 | struct ssh_list *entry_list = NULL; |
529 | 0 | struct ssh_iterator *it = NULL; |
530 | 0 | char *host_port = NULL; |
531 | 0 | size_t count; |
532 | 0 | bool needcomma = false; |
533 | 0 | char *names = NULL; |
534 | |
|
535 | 0 | int rc; |
536 | |
|
537 | 0 | if (session->opts.knownhosts == NULL || |
538 | 0 | session->opts.global_knownhosts == NULL) { |
539 | 0 | if (ssh_options_apply(session) < 0) { |
540 | 0 | ssh_set_error(session, |
541 | 0 | SSH_REQUEST_DENIED, |
542 | 0 | "Can't find a known_hosts file"); |
543 | |
|
544 | 0 | return NULL; |
545 | 0 | } |
546 | 0 | } |
547 | | |
548 | 0 | host_port = ssh_session_get_host_port(session); |
549 | 0 | if (host_port == NULL) { |
550 | 0 | return NULL; |
551 | 0 | } |
552 | | |
553 | 0 | rc = ssh_known_hosts_read_entries(host_port, |
554 | 0 | session->opts.knownhosts, |
555 | 0 | &entry_list); |
556 | 0 | if (rc != 0) { |
557 | 0 | SAFE_FREE(host_port); |
558 | 0 | ssh_list_free(entry_list); |
559 | 0 | return NULL; |
560 | 0 | } |
561 | | |
562 | 0 | rc = ssh_known_hosts_read_entries(host_port, |
563 | 0 | session->opts.global_knownhosts, |
564 | 0 | &entry_list); |
565 | 0 | SAFE_FREE(host_port); |
566 | 0 | if (rc != 0) { |
567 | 0 | ssh_list_free(entry_list); |
568 | 0 | return NULL; |
569 | 0 | } |
570 | | |
571 | 0 | if (entry_list == NULL) { |
572 | 0 | return NULL; |
573 | 0 | } |
574 | | |
575 | 0 | count = ssh_list_count(entry_list); |
576 | 0 | if (count == 0) { |
577 | 0 | ssh_list_free(entry_list); |
578 | 0 | return NULL; |
579 | 0 | } |
580 | | |
581 | 0 | for (it = ssh_list_get_iterator(entry_list); |
582 | 0 | it != NULL; |
583 | 0 | it = ssh_list_get_iterator(entry_list)) |
584 | 0 | { |
585 | 0 | struct ssh_knownhosts_entry *entry = NULL; |
586 | 0 | const char *algo = NULL; |
587 | |
|
588 | 0 | entry = ssh_iterator_value(struct ssh_knownhosts_entry *, it); |
589 | 0 | algo = ssh_known_host_sigs_from_hostkey_type(entry->publickey->type); |
590 | 0 | if (algo == NULL) { |
591 | 0 | ssh_knownhosts_entry_free(entry); |
592 | 0 | ssh_list_remove(entry_list, it); |
593 | 0 | continue; |
594 | 0 | } |
595 | | |
596 | 0 | if (needcomma) { |
597 | 0 | strncat(methods_buffer, |
598 | 0 | ",", |
599 | 0 | sizeof(methods_buffer) - strlen(methods_buffer) - 1); |
600 | 0 | } |
601 | |
|
602 | 0 | strncat(methods_buffer, |
603 | 0 | algo, |
604 | 0 | sizeof(methods_buffer) - strlen(methods_buffer) - 1); |
605 | 0 | needcomma = true; |
606 | |
|
607 | 0 | ssh_knownhosts_entry_free(entry); |
608 | 0 | ssh_list_remove(entry_list, it); |
609 | 0 | } |
610 | |
|
611 | 0 | ssh_list_free(entry_list); |
612 | |
|
613 | 0 | names = ssh_remove_duplicates(methods_buffer); |
614 | |
|
615 | 0 | return names; |
616 | 0 | } |
617 | | |
618 | | /** |
619 | | * @brief Parse a line from a known_hosts entry into a structure |
620 | | * |
621 | | * This parses a known_hosts entry into a structure with the key in a libssh |
622 | | * consumeable form. You can use the PKI key function to further work with it. |
623 | | * |
624 | | * @param[in] hostname The hostname to match the line to |
625 | | * |
626 | | * @param[in] line The line to compare and parse if we have a hostname |
627 | | * match. |
628 | | * |
629 | | * @param[in] entry A pointer to store the allocated known_hosts |
630 | | * entry structure. The user needs to free the memory |
631 | | * using SSH_KNOWNHOSTS_ENTRY_FREE(). |
632 | | * |
633 | | * @return SSH_OK on success, SSH_ERROR otherwise. |
634 | | */ |
635 | | int ssh_known_hosts_parse_line(const char *hostname, |
636 | | const char *line, |
637 | | struct ssh_knownhosts_entry **entry) |
638 | 0 | { |
639 | 0 | struct ssh_knownhosts_entry *e = NULL; |
640 | 0 | char *known_host = NULL; |
641 | 0 | char *p = NULL; |
642 | 0 | char *save_tok = NULL; |
643 | 0 | enum ssh_keytypes_e key_type; |
644 | 0 | int match = 0; |
645 | 0 | int rc = SSH_OK; |
646 | |
|
647 | 0 | known_host = strdup(line); |
648 | 0 | if (known_host == NULL) { |
649 | 0 | return SSH_ERROR; |
650 | 0 | } |
651 | | |
652 | | /* match pattern for hostname or hashed hostname */ |
653 | 0 | p = strtok_r(known_host, " ", &save_tok); |
654 | 0 | if (p == NULL ) { |
655 | 0 | free(known_host); |
656 | 0 | return SSH_ERROR; |
657 | 0 | } |
658 | | |
659 | 0 | e = calloc(1, sizeof(struct ssh_knownhosts_entry)); |
660 | 0 | if (e == NULL) { |
661 | 0 | free(known_host); |
662 | 0 | return SSH_ERROR; |
663 | 0 | } |
664 | | |
665 | 0 | if (hostname != NULL) { |
666 | 0 | char *host_port = NULL; |
667 | 0 | char *q = NULL; |
668 | | |
669 | | /* Hashed */ |
670 | 0 | if (p[0] == '|') { |
671 | 0 | match = match_hashed_hostname(hostname, p); |
672 | 0 | } |
673 | |
|
674 | 0 | save_tok = NULL; |
675 | |
|
676 | 0 | for (q = strtok_r(p, ",", &save_tok); |
677 | 0 | q != NULL; |
678 | 0 | q = strtok_r(NULL, ",", &save_tok)) { |
679 | 0 | int cmp; |
680 | |
|
681 | 0 | if (q[0] == '[' && hostname[0] != '[') { |
682 | | /* Corner case: We have standard port so we do not have |
683 | | * hostname in square braces. But the pattern is enclosed |
684 | | * in braces with, possibly standard or wildcard, port. |
685 | | * We need to test against [host]:port pair here. |
686 | | */ |
687 | 0 | if (host_port == NULL) { |
688 | 0 | host_port = ssh_hostport(hostname, 22); |
689 | 0 | if (host_port == NULL) { |
690 | 0 | rc = SSH_ERROR; |
691 | 0 | goto out; |
692 | 0 | } |
693 | 0 | } |
694 | | |
695 | 0 | cmp = match_hostname(host_port, q, strlen(q)); |
696 | 0 | } else { |
697 | 0 | cmp = match_hostname(hostname, q, strlen(q)); |
698 | 0 | } |
699 | 0 | if (cmp == 1) { |
700 | 0 | match = 1; |
701 | 0 | break; |
702 | 0 | } |
703 | 0 | } |
704 | 0 | free(host_port); |
705 | |
|
706 | 0 | if (match == 0) { |
707 | 0 | rc = SSH_AGAIN; |
708 | 0 | goto out; |
709 | 0 | } |
710 | | |
711 | 0 | e->hostname = strdup(hostname); |
712 | 0 | if (e->hostname == NULL) { |
713 | 0 | rc = SSH_ERROR; |
714 | 0 | goto out; |
715 | 0 | } |
716 | 0 | } |
717 | | |
718 | | /* Restart parsing */ |
719 | 0 | SAFE_FREE(known_host); |
720 | 0 | known_host = strdup(line); |
721 | 0 | if (known_host == NULL) { |
722 | 0 | rc = SSH_ERROR; |
723 | 0 | goto out; |
724 | 0 | } |
725 | | |
726 | 0 | save_tok = NULL; |
727 | |
|
728 | 0 | p = strtok_r(known_host, " ", &save_tok); |
729 | 0 | if (p == NULL ) { |
730 | 0 | rc = SSH_ERROR; |
731 | 0 | goto out; |
732 | 0 | } |
733 | | |
734 | 0 | e->unparsed = strdup(p); |
735 | 0 | if (e->unparsed == NULL) { |
736 | 0 | rc = SSH_ERROR; |
737 | 0 | goto out; |
738 | 0 | } |
739 | | |
740 | | /* pubkey type */ |
741 | 0 | p = strtok_r(NULL, " ", &save_tok); |
742 | 0 | if (p == NULL) { |
743 | 0 | rc = SSH_ERROR; |
744 | 0 | goto out; |
745 | 0 | } |
746 | | |
747 | 0 | key_type = ssh_key_type_from_name(p); |
748 | 0 | if (key_type == SSH_KEYTYPE_UNKNOWN) { |
749 | 0 | SSH_LOG(SSH_LOG_TRACE, "key type '%s' unknown!", p); |
750 | 0 | rc = SSH_ERROR; |
751 | 0 | goto out; |
752 | 0 | } |
753 | | |
754 | | /* public key */ |
755 | 0 | p = strtok_r(NULL, " ", &save_tok); |
756 | 0 | if (p == NULL) { |
757 | 0 | rc = SSH_ERROR; |
758 | 0 | goto out; |
759 | 0 | } |
760 | | |
761 | 0 | rc = ssh_pki_import_pubkey_base64(p, |
762 | 0 | key_type, |
763 | 0 | &e->publickey); |
764 | 0 | if (rc != SSH_OK) { |
765 | 0 | SSH_LOG(SSH_LOG_TRACE, |
766 | 0 | "Failed to parse %s key for entry: %s!", |
767 | 0 | ssh_key_type_to_char(key_type), |
768 | 0 | e->unparsed); |
769 | 0 | goto out; |
770 | 0 | } |
771 | | |
772 | | /* comment */ |
773 | 0 | p = strtok_r(NULL, " ", &save_tok); |
774 | 0 | if (p != NULL) { |
775 | 0 | p = strstr(line, p); |
776 | 0 | if (p != NULL) { |
777 | 0 | e->comment = strdup(p); |
778 | 0 | if (e->comment == NULL) { |
779 | 0 | rc = SSH_ERROR; |
780 | 0 | goto out; |
781 | 0 | } |
782 | 0 | } |
783 | 0 | } |
784 | | |
785 | 0 | *entry = e; |
786 | 0 | SAFE_FREE(known_host); |
787 | |
|
788 | 0 | return SSH_OK; |
789 | 0 | out: |
790 | 0 | SAFE_FREE(known_host); |
791 | 0 | ssh_knownhosts_entry_free(e); |
792 | 0 | return rc; |
793 | 0 | } |
794 | | |
795 | | /** |
796 | | * @brief Check if the set hostname and port match an entry in known_hosts. |
797 | | * |
798 | | * This check if the set hostname and port have an entry in the known_hosts file. |
799 | | * You need to set at least the hostname using ssh_options_set(). |
800 | | * |
801 | | * @param[in] session The session with the values set to check. |
802 | | * |
803 | | * @return A ssh_known_hosts_e return value. |
804 | | */ |
805 | | enum ssh_known_hosts_e ssh_session_has_known_hosts_entry(ssh_session session) |
806 | 0 | { |
807 | 0 | struct ssh_list *entry_list = NULL; |
808 | 0 | struct ssh_iterator *it = NULL; |
809 | 0 | char *host_port = NULL; |
810 | 0 | bool global_known_hosts_found = false; |
811 | 0 | bool known_hosts_found = false; |
812 | 0 | int rc; |
813 | |
|
814 | 0 | if (session->opts.knownhosts == NULL) { |
815 | 0 | if (ssh_options_apply(session) < 0) { |
816 | 0 | ssh_set_error(session, |
817 | 0 | SSH_REQUEST_DENIED, |
818 | 0 | "Cannot find a known_hosts file"); |
819 | |
|
820 | 0 | return SSH_KNOWN_HOSTS_NOT_FOUND; |
821 | 0 | } |
822 | 0 | } |
823 | | |
824 | 0 | if (session->opts.knownhosts == NULL && |
825 | 0 | session->opts.global_knownhosts == NULL) { |
826 | 0 | ssh_set_error(session, |
827 | 0 | SSH_REQUEST_DENIED, |
828 | 0 | "No path set for a known_hosts file"); |
829 | |
|
830 | 0 | return SSH_KNOWN_HOSTS_NOT_FOUND; |
831 | 0 | } |
832 | | |
833 | 0 | if (session->opts.knownhosts != NULL) { |
834 | 0 | known_hosts_found = ssh_file_readaccess_ok(session->opts.knownhosts); |
835 | 0 | if (!known_hosts_found) { |
836 | 0 | SSH_LOG(SSH_LOG_TRACE, "Cannot access file %s", |
837 | 0 | session->opts.knownhosts); |
838 | 0 | } |
839 | 0 | } |
840 | |
|
841 | 0 | if (session->opts.global_knownhosts != NULL) { |
842 | 0 | global_known_hosts_found = |
843 | 0 | ssh_file_readaccess_ok(session->opts.global_knownhosts); |
844 | 0 | if (!global_known_hosts_found) { |
845 | 0 | SSH_LOG(SSH_LOG_TRACE, "Cannot access file %s", |
846 | 0 | session->opts.global_knownhosts); |
847 | 0 | } |
848 | 0 | } |
849 | |
|
850 | 0 | if ((!known_hosts_found) && (!global_known_hosts_found)) { |
851 | 0 | ssh_set_error(session, |
852 | 0 | SSH_REQUEST_DENIED, |
853 | 0 | "Cannot find a known_hosts file"); |
854 | |
|
855 | 0 | return SSH_KNOWN_HOSTS_NOT_FOUND; |
856 | 0 | } |
857 | | |
858 | 0 | host_port = ssh_session_get_host_port(session); |
859 | 0 | if (host_port == NULL) { |
860 | 0 | return SSH_KNOWN_HOSTS_ERROR; |
861 | 0 | } |
862 | | |
863 | 0 | if (known_hosts_found) { |
864 | 0 | rc = ssh_known_hosts_read_entries(host_port, |
865 | 0 | session->opts.knownhosts, |
866 | 0 | &entry_list); |
867 | 0 | if (rc != 0) { |
868 | 0 | SAFE_FREE(host_port); |
869 | 0 | ssh_list_free(entry_list); |
870 | 0 | return SSH_KNOWN_HOSTS_ERROR; |
871 | 0 | } |
872 | 0 | } |
873 | | |
874 | 0 | if (global_known_hosts_found) { |
875 | 0 | rc = ssh_known_hosts_read_entries(host_port, |
876 | 0 | session->opts.global_knownhosts, |
877 | 0 | &entry_list); |
878 | 0 | if (rc != 0) { |
879 | 0 | SAFE_FREE(host_port); |
880 | 0 | ssh_list_free(entry_list); |
881 | 0 | return SSH_KNOWN_HOSTS_ERROR; |
882 | 0 | } |
883 | 0 | } |
884 | | |
885 | 0 | SAFE_FREE(host_port); |
886 | |
|
887 | 0 | if (ssh_list_count(entry_list) == 0) { |
888 | 0 | ssh_list_free(entry_list); |
889 | 0 | return SSH_KNOWN_HOSTS_UNKNOWN; |
890 | 0 | } |
891 | | |
892 | 0 | for (it = ssh_list_get_iterator(entry_list); |
893 | 0 | it != NULL; |
894 | 0 | it = ssh_list_get_iterator(entry_list)) { |
895 | 0 | struct ssh_knownhosts_entry *entry = NULL; |
896 | |
|
897 | 0 | entry = ssh_iterator_value(struct ssh_knownhosts_entry *, it); |
898 | 0 | ssh_knownhosts_entry_free(entry); |
899 | 0 | ssh_list_remove(entry_list, it); |
900 | 0 | } |
901 | 0 | ssh_list_free(entry_list); |
902 | |
|
903 | 0 | return SSH_KNOWN_HOSTS_OK; |
904 | 0 | } |
905 | | |
906 | | /** |
907 | | * @brief Export the current session information to a known_hosts string. |
908 | | * |
909 | | * This exports the current information of a session which is connected so a |
910 | | * ssh server into an entry line which can be added to a known_hosts file. |
911 | | * |
912 | | * @param[in] session The session with information to export. |
913 | | * |
914 | | * @param[in] pentry_string A pointer to a string to store the allocated |
915 | | * line of the entry. The user must free it using |
916 | | * ssh_string_free_char(). |
917 | | * |
918 | | * @return SSH_OK on success, SSH_ERROR otherwise. |
919 | | */ |
920 | | int ssh_session_export_known_hosts_entry(ssh_session session, |
921 | | char **pentry_string) |
922 | 0 | { |
923 | 0 | ssh_key server_pubkey = NULL; |
924 | 0 | char *host = NULL; |
925 | 0 | char entry_buf[MAX_LINE_SIZE] = {0}; |
926 | 0 | char *b64_key = NULL; |
927 | 0 | int rc; |
928 | |
|
929 | 0 | if (pentry_string == NULL) { |
930 | 0 | ssh_set_error_invalid(session); |
931 | 0 | return SSH_ERROR; |
932 | 0 | } |
933 | | |
934 | 0 | if (session->opts.host == NULL) { |
935 | 0 | ssh_set_error(session, SSH_FATAL, |
936 | 0 | "Can't create known_hosts entry - hostname unknown"); |
937 | 0 | return SSH_ERROR; |
938 | 0 | } |
939 | | |
940 | 0 | host = ssh_session_get_host_port(session); |
941 | 0 | if (host == NULL) { |
942 | 0 | return SSH_ERROR; |
943 | 0 | } |
944 | | |
945 | 0 | if (session->current_crypto == NULL) { |
946 | 0 | ssh_set_error(session, SSH_FATAL, |
947 | 0 | "No current crypto context, please connect first"); |
948 | 0 | SAFE_FREE(host); |
949 | 0 | return SSH_ERROR; |
950 | 0 | } |
951 | | |
952 | 0 | server_pubkey = ssh_dh_get_current_server_publickey(session); |
953 | 0 | if (server_pubkey == NULL){ |
954 | 0 | ssh_set_error(session, SSH_FATAL, "No public key present"); |
955 | 0 | SAFE_FREE(host); |
956 | 0 | return SSH_ERROR; |
957 | 0 | } |
958 | | |
959 | 0 | rc = ssh_pki_export_pubkey_base64(server_pubkey, &b64_key); |
960 | 0 | if (rc < 0) { |
961 | 0 | SAFE_FREE(host); |
962 | 0 | return SSH_ERROR; |
963 | 0 | } |
964 | | |
965 | 0 | snprintf(entry_buf, sizeof(entry_buf), |
966 | 0 | "%s %s %s\n", |
967 | 0 | host, |
968 | 0 | server_pubkey->type_c, |
969 | 0 | b64_key); |
970 | |
|
971 | 0 | SAFE_FREE(host); |
972 | 0 | SAFE_FREE(b64_key); |
973 | |
|
974 | 0 | *pentry_string = strdup(entry_buf); |
975 | 0 | if (*pentry_string == NULL) { |
976 | 0 | return SSH_ERROR; |
977 | 0 | } |
978 | | |
979 | 0 | return SSH_OK; |
980 | 0 | } |
981 | | |
982 | | /** |
983 | | * @brief Adds the currently connected server to the user known_hosts file. |
984 | | * |
985 | | * This adds the currently connected server to the known_hosts file by |
986 | | * appending a new line at the end. The global known_hosts file is considered |
987 | | * read-only so it is not touched by this function. |
988 | | * |
989 | | * @param[in] session The session to use to write the entry. |
990 | | * |
991 | | * @return SSH_OK on success, SSH_ERROR otherwise. |
992 | | */ |
993 | | int ssh_session_update_known_hosts(ssh_session session) |
994 | 0 | { |
995 | 0 | FILE *fp = NULL; |
996 | 0 | char *entry = NULL; |
997 | 0 | char *dir = NULL; |
998 | 0 | size_t nwritten; |
999 | 0 | size_t len; |
1000 | 0 | int rc; |
1001 | 0 | char err_msg[SSH_ERRNO_MSG_MAX] = {0}; |
1002 | |
|
1003 | 0 | if (session->opts.knownhosts == NULL) { |
1004 | 0 | rc = ssh_options_apply(session); |
1005 | 0 | if (rc != SSH_OK) { |
1006 | 0 | ssh_set_error(session, SSH_FATAL, "Can't find a known_hosts file"); |
1007 | 0 | return SSH_ERROR; |
1008 | 0 | } |
1009 | 0 | } |
1010 | | |
1011 | 0 | errno = 0; |
1012 | 0 | fp = fopen(session->opts.knownhosts, "a"); |
1013 | 0 | if (fp == NULL) { |
1014 | 0 | if (errno == ENOENT) { |
1015 | 0 | dir = ssh_dirname(session->opts.knownhosts); |
1016 | 0 | if (dir == NULL) { |
1017 | 0 | ssh_set_error(session, SSH_FATAL, "%s", |
1018 | 0 | ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); |
1019 | 0 | return SSH_ERROR; |
1020 | 0 | } |
1021 | | |
1022 | 0 | rc = ssh_mkdirs(dir, 0700); |
1023 | 0 | if (rc < 0) { |
1024 | 0 | ssh_set_error(session, SSH_FATAL, |
1025 | 0 | "Cannot create %s directory: %s", |
1026 | 0 | dir, |
1027 | 0 | ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); |
1028 | 0 | SAFE_FREE(dir); |
1029 | 0 | return SSH_ERROR; |
1030 | 0 | } |
1031 | 0 | SAFE_FREE(dir); |
1032 | |
|
1033 | 0 | errno = 0; |
1034 | 0 | fp = fopen(session->opts.knownhosts, "a"); |
1035 | 0 | if (fp == NULL) { |
1036 | 0 | ssh_set_error(session, SSH_FATAL, |
1037 | 0 | "Couldn't open known_hosts file %s" |
1038 | 0 | " for appending: %s", |
1039 | 0 | session->opts.knownhosts, |
1040 | 0 | ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); |
1041 | 0 | return SSH_ERROR; |
1042 | 0 | } |
1043 | 0 | } else { |
1044 | 0 | ssh_set_error(session, SSH_FATAL, |
1045 | 0 | "Couldn't open known_hosts file %s for appending: %s", |
1046 | 0 | session->opts.knownhosts, strerror(errno)); |
1047 | 0 | return SSH_ERROR; |
1048 | 0 | } |
1049 | 0 | } |
1050 | | |
1051 | 0 | rc = ssh_session_export_known_hosts_entry(session, &entry); |
1052 | 0 | if (rc != SSH_OK) { |
1053 | 0 | fclose(fp); |
1054 | 0 | return rc; |
1055 | 0 | } |
1056 | | |
1057 | 0 | len = strlen(entry); |
1058 | 0 | nwritten = fwrite(entry, sizeof(char), len, fp); |
1059 | 0 | SAFE_FREE(entry); |
1060 | 0 | if (nwritten != len || ferror(fp)) { |
1061 | 0 | ssh_set_error(session, SSH_FATAL, |
1062 | 0 | "Couldn't append to known_hosts file %s: %s", |
1063 | 0 | session->opts.knownhosts, |
1064 | 0 | ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); |
1065 | 0 | fclose(fp); |
1066 | 0 | return SSH_ERROR; |
1067 | 0 | } |
1068 | | |
1069 | 0 | fclose(fp); |
1070 | 0 | return SSH_OK; |
1071 | 0 | } |
1072 | | |
1073 | | static enum ssh_known_hosts_e |
1074 | | ssh_known_hosts_check_server_key(const char *hosts_entry, |
1075 | | const char *filename, |
1076 | | ssh_key server_key, |
1077 | | struct ssh_knownhosts_entry **pentry) |
1078 | 0 | { |
1079 | 0 | struct ssh_list *entry_list = NULL; |
1080 | 0 | struct ssh_iterator *it = NULL; |
1081 | 0 | enum ssh_known_hosts_e found = SSH_KNOWN_HOSTS_UNKNOWN; |
1082 | 0 | int rc; |
1083 | |
|
1084 | 0 | rc = ssh_known_hosts_read_entries(hosts_entry, |
1085 | 0 | filename, |
1086 | 0 | &entry_list); |
1087 | 0 | if (rc != 0) { |
1088 | 0 | ssh_list_free(entry_list); |
1089 | 0 | return SSH_KNOWN_HOSTS_UNKNOWN; |
1090 | 0 | } |
1091 | | |
1092 | 0 | it = ssh_list_get_iterator(entry_list); |
1093 | 0 | if (it == NULL) { |
1094 | 0 | ssh_list_free(entry_list); |
1095 | 0 | return SSH_KNOWN_HOSTS_UNKNOWN; |
1096 | 0 | } |
1097 | | |
1098 | 0 | for (;it != NULL; it = it->next) { |
1099 | 0 | struct ssh_knownhosts_entry *entry = NULL; |
1100 | 0 | int cmp; |
1101 | |
|
1102 | 0 | entry = ssh_iterator_value(struct ssh_knownhosts_entry *, it); |
1103 | |
|
1104 | 0 | cmp = ssh_key_cmp(server_key, entry->publickey, SSH_KEY_CMP_PUBLIC); |
1105 | 0 | if (cmp == 0) { |
1106 | 0 | found = SSH_KNOWN_HOSTS_OK; |
1107 | 0 | if (pentry != NULL) { |
1108 | 0 | *pentry = entry; |
1109 | 0 | ssh_list_remove(entry_list, it); |
1110 | 0 | } |
1111 | 0 | break; |
1112 | 0 | } |
1113 | | |
1114 | 0 | if (ssh_key_type(server_key) == ssh_key_type(entry->publickey)) { |
1115 | 0 | found = SSH_KNOWN_HOSTS_CHANGED; |
1116 | 0 | continue; |
1117 | 0 | } |
1118 | | |
1119 | 0 | if (found != SSH_KNOWN_HOSTS_CHANGED) { |
1120 | 0 | found = SSH_KNOWN_HOSTS_OTHER; |
1121 | 0 | } |
1122 | 0 | } |
1123 | |
|
1124 | 0 | for (it = ssh_list_get_iterator(entry_list); |
1125 | 0 | it != NULL; |
1126 | 0 | it = ssh_list_get_iterator(entry_list)) { |
1127 | 0 | struct ssh_knownhosts_entry *entry = NULL; |
1128 | |
|
1129 | 0 | entry = ssh_iterator_value(struct ssh_knownhosts_entry *, it); |
1130 | 0 | ssh_knownhosts_entry_free(entry); |
1131 | 0 | ssh_list_remove(entry_list, it); |
1132 | 0 | } |
1133 | 0 | ssh_list_free(entry_list); |
1134 | |
|
1135 | 0 | return found; |
1136 | 0 | } |
1137 | | |
1138 | | /** |
1139 | | * @brief Get the known_hosts entry for the currently connected session. |
1140 | | * |
1141 | | * @param[in] session The session to validate. |
1142 | | * |
1143 | | * @param[in] pentry A pointer to store the allocated known hosts entry. |
1144 | | * |
1145 | | * @returns SSH_KNOWN_HOSTS_OK: The server is known and has not changed.\n |
1146 | | * SSH_KNOWN_HOSTS_CHANGED: The server key has changed. Either you |
1147 | | * are under attack or the administrator |
1148 | | * changed the key. You HAVE to warn the |
1149 | | * user about a possible attack.\n |
1150 | | * SSH_KNOWN_HOSTS_OTHER: The server gave use a key of a type while |
1151 | | * we had an other type recorded. It is a |
1152 | | * possible attack.\n |
1153 | | * SSH_KNOWN_HOSTS_UNKNOWN: The server is unknown. User should |
1154 | | * confirm the public key hash is correct.\n |
1155 | | * SSH_KNOWN_HOSTS_NOT_FOUND: The known host file does not exist. The |
1156 | | * host is thus unknown. File will be |
1157 | | * created if host key is accepted.\n |
1158 | | * SSH_KNOWN_HOSTS_ERROR: There had been an error checking the host. |
1159 | | * |
1160 | | * @see ssh_knownhosts_entry_free() |
1161 | | */ |
1162 | | enum ssh_known_hosts_e |
1163 | | ssh_session_get_known_hosts_entry(ssh_session session, |
1164 | | struct ssh_knownhosts_entry **pentry) |
1165 | 0 | { |
1166 | 0 | enum ssh_known_hosts_e old_rv, rv = SSH_KNOWN_HOSTS_UNKNOWN; |
1167 | |
|
1168 | 0 | if (session->opts.knownhosts == NULL) { |
1169 | 0 | if (ssh_options_apply(session) < 0) { |
1170 | 0 | ssh_set_error(session, |
1171 | 0 | SSH_REQUEST_DENIED, |
1172 | 0 | "Can't find a known_hosts file"); |
1173 | |
|
1174 | 0 | return SSH_KNOWN_HOSTS_NOT_FOUND; |
1175 | 0 | } |
1176 | 0 | } |
1177 | | |
1178 | 0 | rv = ssh_session_get_known_hosts_entry_file(session, |
1179 | 0 | session->opts.knownhosts, |
1180 | 0 | pentry); |
1181 | 0 | if (rv == SSH_KNOWN_HOSTS_OK) { |
1182 | | /* We already found a match in the first file: return */ |
1183 | 0 | return rv; |
1184 | 0 | } |
1185 | | |
1186 | 0 | old_rv = rv; |
1187 | 0 | rv = ssh_session_get_known_hosts_entry_file(session, |
1188 | 0 | session->opts.global_knownhosts, |
1189 | 0 | pentry); |
1190 | | |
1191 | | /* If we did not find any match at all: we report the previous result */ |
1192 | 0 | if (rv == SSH_KNOWN_HOSTS_UNKNOWN) { |
1193 | 0 | if (session->opts.StrictHostKeyChecking == 0) { |
1194 | 0 | return SSH_KNOWN_HOSTS_OK; |
1195 | 0 | } |
1196 | 0 | return old_rv; |
1197 | 0 | } |
1198 | | |
1199 | | /* We found some match: return it */ |
1200 | 0 | return rv; |
1201 | |
|
1202 | 0 | } |
1203 | | |
1204 | | /** |
1205 | | * @brief Get the known_hosts entry for the current connected session |
1206 | | * from the given known_hosts file. |
1207 | | * |
1208 | | * @param[in] session The session to validate. |
1209 | | * |
1210 | | * @param[in] filename The filename to parse. |
1211 | | * |
1212 | | * @param[in] pentry A pointer to store the allocated known hosts entry. |
1213 | | * |
1214 | | * @returns SSH_KNOWN_HOSTS_OK: The server is known and has not changed.\n |
1215 | | * SSH_KNOWN_HOSTS_CHANGED: The server key has changed. Either you |
1216 | | * are under attack or the administrator |
1217 | | * changed the key. You HAVE to warn the |
1218 | | * user about a possible attack.\n |
1219 | | * SSH_KNOWN_HOSTS_OTHER: The server gave use a key of a type while |
1220 | | * we had an other type recorded. It is a |
1221 | | * possible attack.\n |
1222 | | * SSH_KNOWN_HOSTS_UNKNOWN: The server is unknown. User should |
1223 | | * confirm the public key hash is correct.\n |
1224 | | * SSH_KNOWN_HOSTS_NOT_FOUND: The known host file does not exist. The |
1225 | | * host is thus unknown. File will be |
1226 | | * created if host key is accepted.\n |
1227 | | * SSH_KNOWN_HOSTS_ERROR: There had been an error checking the host. |
1228 | | * |
1229 | | * @see ssh_knownhosts_entry_free() |
1230 | | */ |
1231 | | enum ssh_known_hosts_e |
1232 | | ssh_session_get_known_hosts_entry_file(ssh_session session, |
1233 | | const char *filename, |
1234 | | struct ssh_knownhosts_entry **pentry) |
1235 | 0 | { |
1236 | 0 | ssh_key server_pubkey = NULL; |
1237 | 0 | char *host_port = NULL; |
1238 | 0 | enum ssh_known_hosts_e found = SSH_KNOWN_HOSTS_UNKNOWN; |
1239 | |
|
1240 | 0 | server_pubkey = ssh_dh_get_current_server_publickey(session); |
1241 | 0 | if (server_pubkey == NULL) { |
1242 | 0 | ssh_set_error(session, |
1243 | 0 | SSH_FATAL, |
1244 | 0 | "ssh_session_is_known_host called without a " |
1245 | 0 | "server_key!"); |
1246 | |
|
1247 | 0 | return SSH_KNOWN_HOSTS_ERROR; |
1248 | 0 | } |
1249 | | |
1250 | 0 | host_port = ssh_session_get_host_port(session); |
1251 | 0 | if (host_port == NULL) { |
1252 | 0 | return SSH_KNOWN_HOSTS_ERROR; |
1253 | 0 | } |
1254 | | |
1255 | 0 | found = ssh_known_hosts_check_server_key(host_port, |
1256 | 0 | filename, |
1257 | 0 | server_pubkey, |
1258 | 0 | pentry); |
1259 | 0 | SAFE_FREE(host_port); |
1260 | |
|
1261 | 0 | return found; |
1262 | 0 | } |
1263 | | |
1264 | | /** |
1265 | | * @brief Check if the servers public key for the connected session is known. |
1266 | | * |
1267 | | * This checks if we already know the public key of the server we want to |
1268 | | * connect to. This allows to detect if there is a MITM attach going on |
1269 | | * of if there have been changes on the server we don't know about. |
1270 | | * |
1271 | | * @param[in] session The SSH to validate. |
1272 | | * |
1273 | | * @returns SSH_KNOWN_HOSTS_OK: The server is known and has not changed.\n |
1274 | | * SSH_KNOWN_HOSTS_CHANGED: The server key has changed. Either you |
1275 | | * are under attack or the administrator |
1276 | | * changed the key. You HAVE to warn the |
1277 | | * user about a possible attack.\n |
1278 | | * SSH_KNOWN_HOSTS_OTHER: The server gave use a key of a type while |
1279 | | * we had an other type recorded. It is a |
1280 | | * possible attack.\n |
1281 | | * SSH_KNOWN_HOSTS_UNKNOWN: The server is unknown. User should |
1282 | | * confirm the public key hash is correct.\n |
1283 | | * SSH_KNOWN_HOSTS_NOT_FOUND: The known host file does not exist. The |
1284 | | * host is thus unknown. File will be |
1285 | | * created if host key is accepted.\n |
1286 | | * SSH_KNOWN_HOSTS_ERROR: There had been an error checking the host. |
1287 | | */ |
1288 | | enum ssh_known_hosts_e ssh_session_is_known_server(ssh_session session) |
1289 | 0 | { |
1290 | 0 | return ssh_session_get_known_hosts_entry(session, NULL); |
1291 | 0 | } |
1292 | | |
1293 | | /** @} */ |