/src/openssh/sshconnect.c
Line | Count | Source |
1 | | /* $OpenBSD: sshconnect.c,v 1.386 2026/09/16 00:29:44 djm Exp $ */ |
2 | | /* |
3 | | * Author: Tatu Ylonen <ylo@cs.hut.fi> |
4 | | * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland |
5 | | * All rights reserved |
6 | | * Code to connect to a remote host, and to perform the client side of the |
7 | | * login (authentication) dialog. |
8 | | * |
9 | | * As far as I am concerned, the code I have written for this software |
10 | | * can be used freely for any purpose. Any derived versions of this |
11 | | * software must be clearly marked as such, and if the derived work is |
12 | | * incompatible with the protocol description in the RFC file, it must be |
13 | | * called by a name other than "ssh" or "Secure Shell". |
14 | | */ |
15 | | |
16 | | #include "includes.h" |
17 | | |
18 | | #include <sys/types.h> |
19 | | #include <sys/wait.h> |
20 | | #include <sys/socket.h> |
21 | | |
22 | | #include <net/if.h> |
23 | | #include <netinet/in.h> |
24 | | #include <arpa/inet.h> |
25 | | |
26 | | #include <errno.h> |
27 | | #include <fcntl.h> |
28 | | #include <limits.h> |
29 | | #include <netdb.h> |
30 | | #include <paths.h> |
31 | | #include <pwd.h> |
32 | | #include <poll.h> |
33 | | #include <signal.h> |
34 | | #include <stdio.h> |
35 | | #include <stdlib.h> |
36 | | #include <stdarg.h> |
37 | | #include <string.h> |
38 | | #include <unistd.h> |
39 | | #include <ifaddrs.h> |
40 | | |
41 | | #include "xmalloc.h" |
42 | | #include "hostfile.h" |
43 | | #include "ssh.h" |
44 | | #include "compat.h" |
45 | | #include "packet.h" |
46 | | #include "sshkey.h" |
47 | | #include "sshconnect.h" |
48 | | #include "log.h" |
49 | | #include "match.h" |
50 | | #include "misc.h" |
51 | | #include "readconf.h" |
52 | | #include "dns.h" |
53 | | #include "monitor_fdpass.h" |
54 | | #include "authfile.h" |
55 | | #include "ssherr.h" |
56 | | #include "authfd.h" |
57 | | #include "kex.h" |
58 | | |
59 | | struct sshkey *previous_host_key = NULL; |
60 | | |
61 | | static int matching_host_key_dns = 0; |
62 | | |
63 | | static pid_t proxy_command_pid = 0; |
64 | | |
65 | | /* import */ |
66 | | extern int debug_flag; |
67 | | extern Options options; |
68 | | extern char *__progname; |
69 | | |
70 | | static int show_other_keys(struct hostkeys *, struct sshkey *); |
71 | | static void warn_changed_key(struct sshkey *); |
72 | | |
73 | | void |
74 | | ssh_conn_info_free(struct ssh_conn_info *cinfo) |
75 | 0 | { |
76 | 0 | if (cinfo == NULL) |
77 | 0 | return; |
78 | 0 | free(cinfo->conn_hash_hex); |
79 | 0 | free(cinfo->shorthost); |
80 | 0 | free(cinfo->uidstr); |
81 | 0 | free(cinfo->keyalias); |
82 | 0 | free(cinfo->thishost); |
83 | 0 | free(cinfo->host_arg); |
84 | 0 | free(cinfo->portstr); |
85 | 0 | free(cinfo->remhost); |
86 | 0 | free(cinfo->remuser); |
87 | 0 | free(cinfo->homedir); |
88 | 0 | free(cinfo->locuser); |
89 | 0 | free(cinfo->jmphost); |
90 | 0 | freezero(cinfo, sizeof(*cinfo)); |
91 | 0 | } |
92 | | |
93 | | struct ssh_conn_info * |
94 | | ssh_conn_info_dup(const struct ssh_conn_info *cinfo) |
95 | 0 | { |
96 | 0 | struct ssh_conn_info *ret; |
97 | |
|
98 | 0 | if (cinfo == NULL) |
99 | 0 | return NULL; |
100 | 0 | ret = xcalloc(1, sizeof(*ret)); |
101 | 0 | ret->conn_hash_hex = xstrdup(cinfo->conn_hash_hex); |
102 | 0 | ret->shorthost = xstrdup(cinfo->shorthost); |
103 | 0 | ret->uidstr = xstrdup(cinfo->uidstr); |
104 | 0 | ret->keyalias = xstrdup(cinfo->keyalias); |
105 | 0 | ret->thishost = xstrdup(cinfo->thishost); |
106 | 0 | ret->host_arg = xstrdup(cinfo->host_arg); |
107 | 0 | ret->portstr = xstrdup(cinfo->portstr); |
108 | 0 | ret->remhost = xstrdup(cinfo->remhost); |
109 | 0 | ret->remuser = xstrdup(cinfo->remuser); |
110 | 0 | ret->homedir = xstrdup(cinfo->homedir); |
111 | 0 | ret->locuser = xstrdup(cinfo->locuser); |
112 | 0 | ret->jmphost = xstrdup(cinfo->jmphost); |
113 | 0 | return ret; |
114 | 0 | } |
115 | | |
116 | | /* Expand a proxy command */ |
117 | | static char * |
118 | | expand_proxy_command(const char *proxy_command, const char *user, |
119 | | const char *host, const char *host_arg, int port) |
120 | 0 | { |
121 | 0 | char *tmp, *ret, strport[NI_MAXSERV]; |
122 | 0 | const char *keyalias = options.host_key_alias ? |
123 | 0 | options.host_key_alias : host_arg; |
124 | |
|
125 | 0 | snprintf(strport, sizeof strport, "%d", port); |
126 | 0 | xasprintf(&tmp, "exec %s", proxy_command); |
127 | 0 | ret = percent_expand(tmp, |
128 | 0 | "h", host, |
129 | 0 | "k", keyalias, |
130 | 0 | "n", host_arg, |
131 | 0 | "p", strport, |
132 | 0 | "r", options.user, |
133 | 0 | (char *)NULL); |
134 | 0 | free(tmp); |
135 | 0 | return ret; |
136 | 0 | } |
137 | | |
138 | | /* |
139 | | * Connect to the given ssh server using a proxy command that passes a |
140 | | * a connected fd back to us. |
141 | | */ |
142 | | static int |
143 | | ssh_proxy_fdpass_connect(struct ssh *ssh, const char *host, |
144 | | const char *host_arg, u_short port, const char *proxy_command) |
145 | 0 | { |
146 | 0 | char *command_string; |
147 | 0 | int sp[2], sock; |
148 | 0 | pid_t pid; |
149 | 0 | char *shell; |
150 | |
|
151 | 0 | if ((shell = getenv("SHELL")) == NULL) |
152 | 0 | shell = _PATH_BSHELL; |
153 | |
|
154 | 0 | if (socketpair(AF_UNIX, SOCK_STREAM, 0, sp) == -1) |
155 | 0 | fatal("Could not create socketpair to communicate with " |
156 | 0 | "proxy dialer: %.100s", strerror(errno)); |
157 | | |
158 | 0 | command_string = expand_proxy_command(proxy_command, options.user, |
159 | 0 | host, host_arg, port); |
160 | 0 | debug("Executing proxy dialer command: %.500s", command_string); |
161 | | |
162 | | /* Fork and execute the proxy command. */ |
163 | 0 | if ((pid = fork()) == 0) { |
164 | 0 | char *argv[10]; |
165 | |
|
166 | 0 | close(sp[1]); |
167 | | /* Redirect stdin and stdout. */ |
168 | 0 | if (sp[0] != 0) { |
169 | 0 | if (dup2(sp[0], 0) == -1) |
170 | 0 | perror("dup2 stdin"); |
171 | 0 | } |
172 | 0 | if (sp[0] != 1) { |
173 | 0 | if (dup2(sp[0], 1) == -1) |
174 | 0 | perror("dup2 stdout"); |
175 | 0 | } |
176 | 0 | if (sp[0] >= 2) |
177 | 0 | close(sp[0]); |
178 | | |
179 | | /* |
180 | | * Stderr is left for non-ControlPersist connections is so |
181 | | * error messages may be printed on the user's terminal. |
182 | | */ |
183 | 0 | if (!debug_flag && options.control_path != NULL && |
184 | 0 | options.control_persist && stdfd_devnull(0, 0, 1) == -1) |
185 | 0 | error_f("stdfd_devnull failed"); |
186 | |
|
187 | 0 | argv[0] = shell; |
188 | 0 | argv[1] = "-c"; |
189 | 0 | argv[2] = command_string; |
190 | 0 | argv[3] = NULL; |
191 | | |
192 | | /* |
193 | | * Execute the proxy command. |
194 | | * Note that we gave up any extra privileges above. |
195 | | */ |
196 | 0 | execv(argv[0], argv); |
197 | 0 | perror(argv[0]); |
198 | 0 | exit(1); |
199 | 0 | } |
200 | | /* Parent. */ |
201 | 0 | if (pid == -1) |
202 | 0 | fatal("fork failed: %.100s", strerror(errno)); |
203 | 0 | close(sp[0]); |
204 | 0 | free(command_string); |
205 | |
|
206 | 0 | if ((sock = mm_receive_fd(sp[1])) == -1) |
207 | 0 | fatal("proxy dialer did not pass back a connection"); |
208 | 0 | close(sp[1]); |
209 | |
|
210 | 0 | while (waitpid(pid, NULL, 0) == -1) |
211 | 0 | if (errno != EINTR) |
212 | 0 | fatal("Couldn't wait for child: %s", strerror(errno)); |
213 | | |
214 | | /* Set the connection file descriptors. */ |
215 | 0 | if (ssh_packet_set_connection(ssh, sock, sock) == NULL) |
216 | 0 | return -1; /* ssh_packet_set_connection logs error */ |
217 | | |
218 | 0 | return 0; |
219 | 0 | } |
220 | | |
221 | | /* |
222 | | * Connect to the given ssh server using a proxy command. |
223 | | */ |
224 | | static int |
225 | | ssh_proxy_connect(struct ssh *ssh, const char *host, const char *host_arg, |
226 | | u_short port, const char *proxy_command) |
227 | 0 | { |
228 | 0 | char *command_string; |
229 | 0 | int pin[2], pout[2]; |
230 | 0 | pid_t pid; |
231 | 0 | char *shell; |
232 | |
|
233 | 0 | if ((shell = getenv("SHELL")) == NULL || *shell == '\0') |
234 | 0 | shell = _PATH_BSHELL; |
235 | | |
236 | | /* Create pipes for communicating with the proxy. */ |
237 | 0 | if (pipe(pin) == -1 || pipe(pout) == -1) |
238 | 0 | fatal("Could not create pipes to communicate with the proxy: %.100s", |
239 | 0 | strerror(errno)); |
240 | | |
241 | 0 | command_string = expand_proxy_command(proxy_command, options.user, |
242 | 0 | host, host_arg, port); |
243 | 0 | debug("Executing proxy command: %.500s", command_string); |
244 | | |
245 | | /* Fork and execute the proxy command. */ |
246 | 0 | if ((pid = fork()) == 0) { |
247 | 0 | char *argv[10]; |
248 | | |
249 | | /* Redirect stdin and stdout. */ |
250 | 0 | close(pin[1]); |
251 | 0 | if (pin[0] != 0) { |
252 | 0 | if (dup2(pin[0], 0) == -1) |
253 | 0 | perror("dup2 stdin"); |
254 | 0 | close(pin[0]); |
255 | 0 | } |
256 | 0 | close(pout[0]); |
257 | 0 | if (dup2(pout[1], 1) == -1) |
258 | 0 | perror("dup2 stdout"); |
259 | | /* Cannot be 1 because pin allocated two descriptors. */ |
260 | 0 | close(pout[1]); |
261 | | |
262 | | /* |
263 | | * Stderr is left for non-ControlPersist connections is so |
264 | | * error messages may be printed on the user's terminal. |
265 | | */ |
266 | 0 | if (!debug_flag && options.control_path != NULL && |
267 | 0 | options.control_persist && stdfd_devnull(0, 0, 1) == -1) |
268 | 0 | error_f("stdfd_devnull failed"); |
269 | |
|
270 | 0 | argv[0] = shell; |
271 | 0 | argv[1] = "-c"; |
272 | 0 | argv[2] = command_string; |
273 | 0 | argv[3] = NULL; |
274 | | |
275 | | /* |
276 | | * Execute the proxy command. Note that we gave up any |
277 | | * extra privileges above. |
278 | | */ |
279 | 0 | ssh_signal(SIGPIPE, SIG_DFL); |
280 | 0 | execv(argv[0], argv); |
281 | 0 | perror(argv[0]); |
282 | 0 | exit(1); |
283 | 0 | } |
284 | | /* Parent. */ |
285 | 0 | if (pid == -1) |
286 | 0 | fatal("fork failed: %.100s", strerror(errno)); |
287 | 0 | else |
288 | 0 | proxy_command_pid = pid; /* save pid to clean up later */ |
289 | | |
290 | | /* Close child side of the descriptors. */ |
291 | 0 | close(pin[0]); |
292 | 0 | close(pout[1]); |
293 | | |
294 | | /* Free the command name. */ |
295 | 0 | free(command_string); |
296 | | |
297 | | /* Set the connection file descriptors. */ |
298 | 0 | if (ssh_packet_set_connection(ssh, pout[0], pin[1]) == NULL) |
299 | 0 | return -1; /* ssh_packet_set_connection logs error */ |
300 | | |
301 | 0 | return 0; |
302 | 0 | } |
303 | | |
304 | | void |
305 | | ssh_kill_proxy_command(void) |
306 | 0 | { |
307 | | /* |
308 | | * Send SIGHUP to proxy command if used. We don't wait() in |
309 | | * case it hangs and instead rely on init to reap the child |
310 | | */ |
311 | 0 | if (proxy_command_pid > 1) |
312 | 0 | kill(proxy_command_pid, SIGHUP); |
313 | 0 | } |
314 | | |
315 | | #ifdef HAVE_IFADDRS_H |
316 | | /* |
317 | | * Search a interface address list (returned from getifaddrs(3)) for an |
318 | | * address that matches the desired address family on the specified interface. |
319 | | * Returns 0 and fills in *resultp and *rlenp on success. Returns -1 on failure. |
320 | | */ |
321 | | static int |
322 | | check_ifaddrs(const char *ifname, int af, const struct ifaddrs *ifaddrs, |
323 | | struct sockaddr_storage *resultp, socklen_t *rlenp) |
324 | 0 | { |
325 | 0 | struct sockaddr_in6 *sa6; |
326 | 0 | struct sockaddr_in *sa; |
327 | 0 | struct in6_addr *v6addr; |
328 | 0 | const struct ifaddrs *ifa; |
329 | 0 | int allow_local; |
330 | | |
331 | | /* |
332 | | * Prefer addresses that are not loopback or linklocal, but use them |
333 | | * if nothing else matches. |
334 | | */ |
335 | 0 | for (allow_local = 0; allow_local < 2; allow_local++) { |
336 | 0 | for (ifa = ifaddrs; ifa != NULL; ifa = ifa->ifa_next) { |
337 | 0 | if (ifa->ifa_addr == NULL || ifa->ifa_name == NULL || |
338 | 0 | (ifa->ifa_flags & IFF_UP) == 0 || |
339 | 0 | ifa->ifa_addr->sa_family != af || |
340 | 0 | strcmp(ifa->ifa_name, options.bind_interface) != 0) |
341 | 0 | continue; |
342 | 0 | switch (ifa->ifa_addr->sa_family) { |
343 | 0 | case AF_INET: |
344 | 0 | sa = (struct sockaddr_in *)ifa->ifa_addr; |
345 | 0 | if (!allow_local && sa->sin_addr.s_addr == |
346 | 0 | htonl(INADDR_LOOPBACK)) |
347 | 0 | continue; |
348 | 0 | if (*rlenp < sizeof(struct sockaddr_in)) { |
349 | 0 | error_f("v4 addr doesn't fit"); |
350 | 0 | return -1; |
351 | 0 | } |
352 | 0 | *rlenp = sizeof(struct sockaddr_in); |
353 | 0 | memcpy(resultp, sa, *rlenp); |
354 | 0 | return 0; |
355 | 0 | case AF_INET6: |
356 | 0 | sa6 = (struct sockaddr_in6 *)ifa->ifa_addr; |
357 | 0 | v6addr = &sa6->sin6_addr; |
358 | 0 | if (!allow_local && |
359 | 0 | (IN6_IS_ADDR_LINKLOCAL(v6addr) || |
360 | 0 | IN6_IS_ADDR_LOOPBACK(v6addr))) |
361 | 0 | continue; |
362 | 0 | if (*rlenp < sizeof(struct sockaddr_in6)) { |
363 | 0 | error_f("v6 addr doesn't fit"); |
364 | 0 | return -1; |
365 | 0 | } |
366 | 0 | *rlenp = sizeof(struct sockaddr_in6); |
367 | 0 | memcpy(resultp, sa6, *rlenp); |
368 | 0 | return 0; |
369 | 0 | } |
370 | 0 | } |
371 | 0 | } |
372 | 0 | return -1; |
373 | 0 | } |
374 | | #endif |
375 | | |
376 | | /* |
377 | | * Creates a socket for use as the ssh connection. |
378 | | */ |
379 | | static int |
380 | | ssh_create_socket(struct addrinfo *ai) |
381 | 0 | { |
382 | 0 | int sock, r; |
383 | 0 | struct sockaddr_storage bindaddr; |
384 | 0 | socklen_t bindaddrlen = 0; |
385 | 0 | struct addrinfo hints, *res = NULL; |
386 | 0 | #ifdef HAVE_IFADDRS_H |
387 | 0 | struct ifaddrs *ifaddrs = NULL; |
388 | 0 | #endif |
389 | 0 | char ntop[NI_MAXHOST]; |
390 | |
|
391 | 0 | sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); |
392 | 0 | if (sock == -1) { |
393 | 0 | error("socket: %s", strerror(errno)); |
394 | 0 | return -1; |
395 | 0 | } |
396 | 0 | (void)fcntl(sock, F_SETFD, FD_CLOEXEC); |
397 | | |
398 | | /* Use interactive QOS (if specified) until authentication completed */ |
399 | 0 | if (options.ip_qos_interactive != INT_MAX) |
400 | 0 | set_sock_tos(sock, options.ip_qos_interactive); |
401 | | |
402 | | /* Bind the socket to an alternative local IP address */ |
403 | 0 | if (options.bind_address == NULL && options.bind_interface == NULL) |
404 | 0 | return sock; |
405 | | |
406 | 0 | if (options.bind_address != NULL) { |
407 | 0 | memset(&hints, 0, sizeof(hints)); |
408 | 0 | hints.ai_family = ai->ai_family; |
409 | 0 | hints.ai_socktype = ai->ai_socktype; |
410 | 0 | hints.ai_protocol = ai->ai_protocol; |
411 | 0 | hints.ai_flags = AI_PASSIVE; |
412 | 0 | if ((r = getaddrinfo(options.bind_address, NULL, |
413 | 0 | &hints, &res)) != 0) { |
414 | 0 | error("getaddrinfo: %s: %s", options.bind_address, |
415 | 0 | ssh_gai_strerror(r)); |
416 | 0 | goto fail; |
417 | 0 | } |
418 | 0 | if (res == NULL) { |
419 | 0 | error("getaddrinfo: no addrs"); |
420 | 0 | goto fail; |
421 | 0 | } |
422 | 0 | memcpy(&bindaddr, res->ai_addr, res->ai_addrlen); |
423 | 0 | bindaddrlen = res->ai_addrlen; |
424 | 0 | } else if (options.bind_interface != NULL) { |
425 | 0 | #ifdef HAVE_IFADDRS_H |
426 | 0 | if ((r = getifaddrs(&ifaddrs)) != 0) { |
427 | 0 | error("getifaddrs: %s: %s", options.bind_interface, |
428 | 0 | strerror(errno)); |
429 | 0 | goto fail; |
430 | 0 | } |
431 | 0 | bindaddrlen = sizeof(bindaddr); |
432 | 0 | if (check_ifaddrs(options.bind_interface, ai->ai_family, |
433 | 0 | ifaddrs, &bindaddr, &bindaddrlen) != 0) { |
434 | 0 | logit("getifaddrs: %s: no suitable addresses", |
435 | 0 | options.bind_interface); |
436 | 0 | goto fail; |
437 | 0 | } |
438 | | #else |
439 | | error("BindInterface not supported on this platform."); |
440 | | #endif |
441 | 0 | } |
442 | 0 | if ((r = getnameinfo((struct sockaddr *)&bindaddr, bindaddrlen, |
443 | 0 | ntop, sizeof(ntop), NULL, 0, NI_NUMERICHOST)) != 0) { |
444 | 0 | error_f("getnameinfo failed: %s", ssh_gai_strerror(r)); |
445 | 0 | goto fail; |
446 | 0 | } |
447 | 0 | if (bind(sock, (struct sockaddr *)&bindaddr, bindaddrlen) != 0) { |
448 | 0 | error("bind %s: %s", ntop, strerror(errno)); |
449 | 0 | goto fail; |
450 | 0 | } |
451 | 0 | debug_f("bound to %s", ntop); |
452 | | /* success */ |
453 | 0 | goto out; |
454 | 0 | fail: |
455 | 0 | close(sock); |
456 | 0 | sock = -1; |
457 | 0 | out: |
458 | 0 | if (res != NULL) |
459 | 0 | freeaddrinfo(res); |
460 | 0 | #ifdef HAVE_IFADDRS_H |
461 | 0 | if (ifaddrs != NULL) |
462 | 0 | freeifaddrs(ifaddrs); |
463 | 0 | #endif |
464 | 0 | return sock; |
465 | 0 | } |
466 | | |
467 | | /* |
468 | | * Opens a TCP/IP connection to the remote server on the given host. |
469 | | * The address of the remote host will be returned in hostaddr. |
470 | | * If port is 0, the default port will be used. |
471 | | * Connection_attempts specifies the maximum number of tries (one per |
472 | | * second). If proxy_command is non-NULL, it specifies the command (with %h |
473 | | * and %p substituted for host and port, respectively) to use to contact |
474 | | * the daemon. |
475 | | */ |
476 | | static int |
477 | | ssh_connect_direct(struct ssh *ssh, const char *host, struct addrinfo *aitop, |
478 | | struct sockaddr_storage *hostaddr, u_short port, int connection_attempts, |
479 | | int *timeout_ms, int want_keepalive) |
480 | 0 | { |
481 | 0 | int saved_timeout_ms = *timeout_ms; |
482 | 0 | int oerrno, sock = -1, attempt; |
483 | 0 | char ntop[NI_MAXHOST], strport[NI_MAXSERV]; |
484 | 0 | struct addrinfo *ai; |
485 | |
|
486 | 0 | debug3_f("entering"); |
487 | 0 | memset(ntop, 0, sizeof(ntop)); |
488 | 0 | memset(strport, 0, sizeof(strport)); |
489 | |
|
490 | 0 | for (attempt = 0; attempt < connection_attempts; attempt++) { |
491 | 0 | if (attempt > 0) { |
492 | | /* Sleep a moment before retrying. */ |
493 | 0 | sleep(1); |
494 | 0 | debug("Trying again..."); |
495 | 0 | } |
496 | | /* |
497 | | * Loop through addresses for this host, and try each one in |
498 | | * sequence until the connection succeeds. |
499 | | */ |
500 | 0 | for (ai = aitop; ai; ai = ai->ai_next) { |
501 | 0 | if (ai->ai_family != AF_INET && |
502 | 0 | ai->ai_family != AF_INET6) { |
503 | 0 | errno = EAFNOSUPPORT; |
504 | 0 | continue; |
505 | 0 | } |
506 | 0 | if (getnameinfo(ai->ai_addr, ai->ai_addrlen, |
507 | 0 | ntop, sizeof(ntop), strport, sizeof(strport), |
508 | 0 | NI_NUMERICHOST|NI_NUMERICSERV) != 0) { |
509 | 0 | oerrno = errno; |
510 | 0 | error_f("getnameinfo failed"); |
511 | 0 | errno = oerrno; |
512 | 0 | continue; |
513 | 0 | } |
514 | 0 | if (options.address_family != AF_UNSPEC && |
515 | 0 | ai->ai_family != options.address_family) { |
516 | 0 | debug2_f("skipping address [%s]:%s: " |
517 | 0 | "wrong address family", ntop, strport); |
518 | 0 | errno = EAFNOSUPPORT; |
519 | 0 | continue; |
520 | 0 | } |
521 | | |
522 | 0 | debug("Connecting to %.200s [%.100s] port %s.", |
523 | 0 | host, ntop, strport); |
524 | | |
525 | | /* Create a socket for connecting. */ |
526 | 0 | sock = ssh_create_socket(ai); |
527 | 0 | if (sock < 0) { |
528 | | /* Any error is already output */ |
529 | 0 | errno = 0; |
530 | 0 | continue; |
531 | 0 | } |
532 | | |
533 | 0 | *timeout_ms = saved_timeout_ms; |
534 | 0 | if (timeout_connect(sock, ai->ai_addr, ai->ai_addrlen, |
535 | 0 | timeout_ms) >= 0) { |
536 | | /* Successful connection. */ |
537 | 0 | memcpy(hostaddr, ai->ai_addr, ai->ai_addrlen); |
538 | 0 | break; |
539 | 0 | } else { |
540 | 0 | oerrno = errno; |
541 | 0 | debug("connect to address %s port %s: %s", |
542 | 0 | ntop, strport, strerror(errno)); |
543 | 0 | close(sock); |
544 | 0 | sock = -1; |
545 | 0 | errno = oerrno; |
546 | 0 | } |
547 | 0 | } |
548 | 0 | if (sock != -1) |
549 | 0 | break; /* Successful connection. */ |
550 | 0 | } |
551 | | |
552 | | /* Return failure if we didn't get a successful connection. */ |
553 | 0 | if (sock == -1) { |
554 | 0 | error("ssh: connect to host %s port %s: %s", |
555 | 0 | host, strport, errno == 0 ? "failure" : strerror(errno)); |
556 | 0 | return -1; |
557 | 0 | } |
558 | | |
559 | 0 | debug("Connection established."); |
560 | | |
561 | | /* Set SO_KEEPALIVE if requested. */ |
562 | 0 | if (want_keepalive) |
563 | 0 | set_keepalive(sock); /* logs errors */ |
564 | | |
565 | | /* Set the connection. */ |
566 | 0 | if (ssh_packet_set_connection(ssh, sock, sock) == NULL) |
567 | 0 | return -1; /* ssh_packet_set_connection logs error */ |
568 | | |
569 | 0 | return 0; |
570 | 0 | } |
571 | | |
572 | | int |
573 | | ssh_connect(struct ssh *ssh, const char *host, const char *host_arg, |
574 | | struct addrinfo *addrs, struct sockaddr_storage *hostaddr, u_short port, |
575 | | int connection_attempts, int *timeout_ms, int want_keepalive) |
576 | 0 | { |
577 | 0 | int in, out; |
578 | |
|
579 | 0 | if (options.proxy_command == NULL) { |
580 | 0 | return ssh_connect_direct(ssh, host, addrs, hostaddr, port, |
581 | 0 | connection_attempts, timeout_ms, want_keepalive); |
582 | 0 | } else if (strcmp(options.proxy_command, "-") == 0) { |
583 | 0 | if ((in = dup(STDIN_FILENO)) == -1 || |
584 | 0 | (out = dup(STDOUT_FILENO)) == -1) { |
585 | 0 | if (in >= 0) |
586 | 0 | close(in); |
587 | 0 | error_f("dup() in/out failed"); |
588 | 0 | return -1; /* ssh_packet_set_connection logs error */ |
589 | 0 | } |
590 | 0 | if ((ssh_packet_set_connection(ssh, in, out)) == NULL) |
591 | 0 | return -1; /* ssh_packet_set_connection logs error */ |
592 | 0 | return 0; |
593 | 0 | } else if (options.proxy_use_fdpass) { |
594 | 0 | return ssh_proxy_fdpass_connect(ssh, host, host_arg, port, |
595 | 0 | options.proxy_command); |
596 | 0 | } |
597 | 0 | return ssh_proxy_connect(ssh, host, host_arg, port, |
598 | 0 | options.proxy_command); |
599 | 0 | } |
600 | | |
601 | | /* defaults to 'no' */ |
602 | | static int |
603 | | confirm(const char *prompt, const char *fingerprint) |
604 | 0 | { |
605 | 0 | const char *msg, *again = "Please type 'yes' or 'no': "; |
606 | 0 | const char *again_fp = "Please type 'yes', 'no' or the fingerprint: "; |
607 | 0 | char *p, *cp; |
608 | 0 | int ret = -1; |
609 | |
|
610 | 0 | if (options.batch_mode) |
611 | 0 | return 0; |
612 | 0 | for (msg = prompt;;msg = fingerprint ? again_fp : again) { |
613 | 0 | cp = p = read_passphrase(msg, RP_ECHO); |
614 | 0 | if (p == NULL) |
615 | 0 | return 0; |
616 | 0 | p += strspn(p, " \t"); /* skip leading whitespace */ |
617 | 0 | p[strcspn(p, " \t\n")] = '\0'; /* remove trailing whitespace */ |
618 | 0 | if (p[0] == '\0' || strcasecmp(p, "no") == 0) |
619 | 0 | ret = 0; |
620 | 0 | else if (strcasecmp(p, "yes") == 0 || (fingerprint != NULL && |
621 | 0 | strcmp(p, fingerprint) == 0)) |
622 | 0 | ret = 1; |
623 | 0 | free(cp); |
624 | 0 | if (ret != -1) |
625 | 0 | return ret; |
626 | 0 | } |
627 | 0 | } |
628 | | |
629 | | static int |
630 | | sockaddr_is_local(struct sockaddr *hostaddr) |
631 | 0 | { |
632 | 0 | switch (hostaddr->sa_family) { |
633 | 0 | case AF_INET: |
634 | 0 | return (ntohl(((struct sockaddr_in *)hostaddr)-> |
635 | 0 | sin_addr.s_addr) >> 24) == IN_LOOPBACKNET; |
636 | 0 | case AF_INET6: |
637 | 0 | return IN6_IS_ADDR_LOOPBACK( |
638 | 0 | &(((struct sockaddr_in6 *)hostaddr)->sin6_addr)); |
639 | 0 | default: |
640 | 0 | return 0; |
641 | 0 | } |
642 | 0 | } |
643 | | |
644 | | /* |
645 | | * Prepare the hostname and ip address strings that are used to lookup |
646 | | * host keys in known_hosts files. These may have a port number appended. |
647 | | */ |
648 | | void |
649 | | get_hostfile_hostname_ipaddr(char *hostname, struct sockaddr *hostaddr, |
650 | | u_short port, char **hostfile_hostname, char **hostfile_ipaddr) |
651 | 0 | { |
652 | 0 | char ntop[NI_MAXHOST]; |
653 | 0 | socklen_t addrlen; |
654 | |
|
655 | 0 | switch (hostaddr == NULL ? -1 : hostaddr->sa_family) { |
656 | 0 | case -1: |
657 | 0 | addrlen = 0; |
658 | 0 | break; |
659 | 0 | case AF_INET: |
660 | 0 | addrlen = sizeof(struct sockaddr_in); |
661 | 0 | break; |
662 | 0 | case AF_INET6: |
663 | 0 | addrlen = sizeof(struct sockaddr_in6); |
664 | 0 | break; |
665 | 0 | default: |
666 | 0 | addrlen = sizeof(struct sockaddr); |
667 | 0 | break; |
668 | 0 | } |
669 | | |
670 | | /* |
671 | | * We don't have the remote ip-address for connections |
672 | | * using a proxy command |
673 | | */ |
674 | 0 | if (hostfile_ipaddr != NULL) { |
675 | 0 | if (options.proxy_command == NULL) { |
676 | 0 | if (getnameinfo(hostaddr, addrlen, |
677 | 0 | ntop, sizeof(ntop), NULL, 0, NI_NUMERICHOST) != 0) |
678 | 0 | fatal_f("getnameinfo failed"); |
679 | 0 | *hostfile_ipaddr = put_host_port(ntop, port); |
680 | 0 | } else { |
681 | 0 | *hostfile_ipaddr = xstrdup("<no hostip for proxy " |
682 | 0 | "command>"); |
683 | 0 | } |
684 | 0 | } |
685 | | |
686 | | /* |
687 | | * Allow the user to record the key under a different name or |
688 | | * differentiate a non-standard port. This is useful for ssh |
689 | | * tunneling over forwarded connections or if you run multiple |
690 | | * sshd's on different ports on the same machine. |
691 | | */ |
692 | 0 | if (hostfile_hostname != NULL) { |
693 | 0 | if (options.host_key_alias != NULL) { |
694 | 0 | *hostfile_hostname = xstrdup(options.host_key_alias); |
695 | 0 | debug("using hostkeyalias: %s", *hostfile_hostname); |
696 | 0 | } else { |
697 | 0 | *hostfile_hostname = put_host_port(hostname, port); |
698 | 0 | } |
699 | 0 | } |
700 | 0 | } |
701 | | |
702 | | /* returns non-zero if path appears in hostfiles, or 0 if not. */ |
703 | | static int |
704 | | path_in_hostfiles(const char *path, char **hostfiles, u_int num_hostfiles) |
705 | 0 | { |
706 | 0 | u_int i; |
707 | |
|
708 | 0 | for (i = 0; i < num_hostfiles; i++) { |
709 | 0 | if (strcmp(path, hostfiles[i]) == 0) |
710 | 0 | return 1; |
711 | 0 | } |
712 | 0 | return 0; |
713 | 0 | } |
714 | | |
715 | | struct find_by_key_ctx { |
716 | | const char *host, *ip; |
717 | | const struct sshkey *key; |
718 | | char **names; |
719 | | u_int nnames; |
720 | | }; |
721 | | |
722 | | /* Try to replace home directory prefix (per $HOME) with a ~/ sequence */ |
723 | | static char * |
724 | | try_tilde_unexpand(const char *path) |
725 | 0 | { |
726 | 0 | char *home, *ret = NULL; |
727 | 0 | size_t l; |
728 | |
|
729 | 0 | if (*path != '/') |
730 | 0 | return xstrdup(path); |
731 | 0 | if ((home = getenv("HOME")) == NULL || (l = strlen(home)) == 0) |
732 | 0 | return xstrdup(path); |
733 | 0 | if (strncmp(path, home, l) != 0) |
734 | 0 | return xstrdup(path); |
735 | | /* |
736 | | * ensure we have matched on a path boundary: either the $HOME that |
737 | | * we just compared ends with a '/' or the next character of the path |
738 | | * must be a '/'. |
739 | | */ |
740 | 0 | if (home[l - 1] != '/' && path[l] != '/') |
741 | 0 | return xstrdup(path); |
742 | 0 | if (path[l] == '/') |
743 | 0 | l++; |
744 | 0 | xasprintf(&ret, "~/%s", path + l); |
745 | 0 | return ret; |
746 | 0 | } |
747 | | |
748 | | /* |
749 | | * Returns non-zero if the key is accepted by HostkeyAlgorithms. |
750 | | * Made slightly less trivial by the multiple RSA signature algorithm names. |
751 | | */ |
752 | | int |
753 | | hostkey_accepted_by_hostkeyalgs(const struct sshkey *key) |
754 | 0 | { |
755 | 0 | const char *ktype = sshkey_ssh_name(key); |
756 | 0 | const char *hostkeyalgs = options.hostkeyalgorithms; |
757 | |
|
758 | 0 | if (key->type == KEY_UNSPEC) |
759 | 0 | return 0; |
760 | 0 | if (key->type == KEY_RSA && |
761 | 0 | (match_pattern_list("rsa-sha2-256", hostkeyalgs, 0) == 1 || |
762 | 0 | match_pattern_list("rsa-sha2-512", hostkeyalgs, 0) == 1)) |
763 | 0 | return 1; |
764 | 0 | if (key->type == KEY_RSA_CERT && |
765 | 0 | (match_pattern_list("rsa-sha2-512-cert-v01@openssh.com", hostkeyalgs, 0) == 1 || |
766 | 0 | match_pattern_list("rsa-sha2-256-cert-v01@openssh.com", hostkeyalgs, 0) == 1)) |
767 | 0 | return 1; |
768 | 0 | return match_pattern_list(ktype, hostkeyalgs, 0) == 1; |
769 | 0 | } |
770 | | |
771 | | static int |
772 | | hostkeys_find_by_key_cb(struct hostkey_foreach_line *l, void *_ctx) |
773 | 0 | { |
774 | 0 | struct find_by_key_ctx *ctx = (struct find_by_key_ctx *)_ctx; |
775 | 0 | char *path; |
776 | | |
777 | | /* we are looking for keys with names that *do not* match */ |
778 | 0 | if ((l->match & HKF_MATCH_HOST) != 0) |
779 | 0 | return 0; |
780 | | /* not interested in marker lines */ |
781 | 0 | if (l->marker != MRK_NONE) |
782 | 0 | return 0; |
783 | | /* we are only interested in exact key matches */ |
784 | 0 | if (l->key == NULL || !sshkey_equal(ctx->key, l->key)) |
785 | 0 | return 0; |
786 | 0 | path = try_tilde_unexpand(l->path); |
787 | 0 | debug_f("found matching key in %s:%lu", path, l->linenum); |
788 | 0 | ctx->names = xrecallocarray(ctx->names, |
789 | 0 | ctx->nnames, ctx->nnames + 1, sizeof(*ctx->names)); |
790 | 0 | xasprintf(&ctx->names[ctx->nnames], "%s:%lu: %s", path, l->linenum, |
791 | 0 | strncmp(l->hosts, HASH_MAGIC, strlen(HASH_MAGIC)) == 0 ? |
792 | 0 | "[hashed name]" : l->hosts); |
793 | 0 | ctx->nnames++; |
794 | 0 | free(path); |
795 | 0 | return 0; |
796 | 0 | } |
797 | | |
798 | | static int |
799 | | hostkeys_find_by_key_hostfile(const char *file, const char *which, |
800 | | struct find_by_key_ctx *ctx) |
801 | 0 | { |
802 | 0 | int r; |
803 | |
|
804 | 0 | debug3_f("trying %s hostfile \"%s\"", which, file); |
805 | 0 | if ((r = hostkeys_foreach(file, hostkeys_find_by_key_cb, ctx, |
806 | 0 | ctx->host, ctx->ip, HKF_WANT_PARSE_KEY, 0)) != 0) { |
807 | 0 | if (r == SSH_ERR_SYSTEM_ERROR && errno == ENOENT) { |
808 | 0 | debug_f("hostkeys file %s does not exist", file); |
809 | 0 | return 0; |
810 | 0 | } |
811 | 0 | error_fr(r, "hostkeys_foreach failed for %s", file); |
812 | 0 | return r; |
813 | 0 | } |
814 | 0 | return 0; |
815 | 0 | } |
816 | | |
817 | | /* |
818 | | * Find 'key' in known hosts file(s) that do not match host/ip. |
819 | | * Used to display also-known-as information for previously-unseen hostkeys. |
820 | | */ |
821 | | static void |
822 | | hostkeys_find_by_key(const char *host, const char *ip, const struct sshkey *key, |
823 | | char **user_hostfiles, u_int num_user_hostfiles, |
824 | | char **system_hostfiles, u_int num_system_hostfiles, |
825 | | char ***names, u_int *nnames) |
826 | 0 | { |
827 | 0 | struct find_by_key_ctx ctx = {NULL, NULL, NULL, NULL, 0}; |
828 | 0 | u_int i; |
829 | |
|
830 | 0 | *names = NULL; |
831 | 0 | *nnames = 0; |
832 | |
|
833 | 0 | if (key == NULL || sshkey_is_cert(key)) |
834 | 0 | return; |
835 | | |
836 | 0 | ctx.host = host; |
837 | 0 | ctx.ip = ip; |
838 | 0 | ctx.key = key; |
839 | |
|
840 | 0 | for (i = 0; i < num_user_hostfiles; i++) { |
841 | 0 | if (hostkeys_find_by_key_hostfile(user_hostfiles[i], |
842 | 0 | "user", &ctx) != 0) |
843 | 0 | goto fail; |
844 | 0 | } |
845 | 0 | for (i = 0; i < num_system_hostfiles; i++) { |
846 | 0 | if (hostkeys_find_by_key_hostfile(system_hostfiles[i], |
847 | 0 | "system", &ctx) != 0) |
848 | 0 | goto fail; |
849 | 0 | } |
850 | | /* success */ |
851 | 0 | *names = ctx.names; |
852 | 0 | *nnames = ctx.nnames; |
853 | 0 | ctx.names = NULL; |
854 | 0 | ctx.nnames = 0; |
855 | 0 | return; |
856 | 0 | fail: |
857 | 0 | for (i = 0; i < ctx.nnames; i++) |
858 | 0 | free(ctx.names[i]); |
859 | 0 | free(ctx.names); |
860 | 0 | } |
861 | | |
862 | 0 | #define MAX_OTHER_NAMES 8 /* Maximum number of names to list */ |
863 | | static char * |
864 | | other_hostkeys_message(const char *host, const char *ip, |
865 | | const struct sshkey *key, |
866 | | char **user_hostfiles, u_int num_user_hostfiles, |
867 | | char **system_hostfiles, u_int num_system_hostfiles) |
868 | 0 | { |
869 | 0 | char *ret = NULL, **othernames = NULL; |
870 | 0 | u_int i, n, num_othernames = 0; |
871 | |
|
872 | 0 | hostkeys_find_by_key(host, ip, key, |
873 | 0 | user_hostfiles, num_user_hostfiles, |
874 | 0 | system_hostfiles, num_system_hostfiles, |
875 | 0 | &othernames, &num_othernames); |
876 | 0 | if (num_othernames == 0) |
877 | 0 | return xstrdup("This key is not known by any other names."); |
878 | | |
879 | 0 | xasprintf(&ret, "This host key is known by the following other " |
880 | 0 | "names/addresses:"); |
881 | |
|
882 | 0 | n = num_othernames; |
883 | 0 | if (n > MAX_OTHER_NAMES) |
884 | 0 | n = MAX_OTHER_NAMES; |
885 | 0 | for (i = 0; i < n; i++) { |
886 | 0 | xextendf(&ret, "\n", " %s", othernames[i]); |
887 | 0 | } |
888 | 0 | if (n < num_othernames) { |
889 | 0 | xextendf(&ret, "\n", " (%d additional names omitted)", |
890 | 0 | num_othernames - n); |
891 | 0 | } |
892 | 0 | for (i = 0; i < num_othernames; i++) |
893 | 0 | free(othernames[i]); |
894 | 0 | free(othernames); |
895 | 0 | return ret; |
896 | 0 | } |
897 | | |
898 | | void |
899 | | load_hostkeys_command(struct hostkeys *hostkeys, const char *command_template, |
900 | | const char *invocation, const struct ssh_conn_info *cinfo, |
901 | | const struct sshkey *host_key, const char *hostfile_hostname) |
902 | 0 | { |
903 | 0 | int r, i, ac = 0; |
904 | 0 | char *key_fp = NULL, *keytext = NULL, *tmp; |
905 | 0 | char *command = NULL, *tag = NULL, **av = NULL; |
906 | 0 | FILE *f = NULL; |
907 | 0 | pid_t pid; |
908 | 0 | void (*osigchld)(int); |
909 | |
|
910 | 0 | xasprintf(&tag, "KnownHostsCommand-%s", invocation); |
911 | |
|
912 | 0 | if (host_key != NULL) { |
913 | 0 | if ((key_fp = sshkey_fingerprint(host_key, |
914 | 0 | options.fingerprint_hash, SSH_FP_DEFAULT)) == NULL) |
915 | 0 | fatal_f("sshkey_fingerprint failed"); |
916 | 0 | if ((r = sshkey_to_base64(host_key, &keytext)) != 0) |
917 | 0 | fatal_fr(r, "sshkey_to_base64 failed"); |
918 | 0 | } |
919 | | /* |
920 | | * NB. all returns later this function should go via "out" to |
921 | | * ensure the original SIGCHLD handler is restored properly. |
922 | | */ |
923 | 0 | osigchld = ssh_signal(SIGCHLD, SIG_DFL); |
924 | | |
925 | | /* Turn the command into an argument vector */ |
926 | 0 | if (argv_split(command_template, &ac, &av, 0) != 0) { |
927 | 0 | error("%s \"%s\" contains invalid quotes", tag, |
928 | 0 | command_template); |
929 | 0 | goto out; |
930 | 0 | } |
931 | 0 | if (ac == 0) { |
932 | 0 | error("%s \"%s\" yielded no arguments", tag, |
933 | 0 | command_template); |
934 | 0 | goto out; |
935 | 0 | } |
936 | 0 | for (i = 1; i < ac; i++) { |
937 | 0 | tmp = percent_dollar_expand(av[i], |
938 | 0 | DEFAULT_CLIENT_PERCENT_EXPAND_ARGS(cinfo), |
939 | 0 | "H", hostfile_hostname, |
940 | 0 | "I", invocation, |
941 | 0 | "t", host_key == NULL ? "NONE" : sshkey_ssh_name(host_key), |
942 | 0 | "f", key_fp == NULL ? "NONE" : key_fp, |
943 | 0 | "K", keytext == NULL ? "NONE" : keytext, |
944 | 0 | (char *)NULL); |
945 | 0 | if (tmp == NULL) |
946 | 0 | fatal_f("percent_expand failed"); |
947 | 0 | free(av[i]); |
948 | 0 | av[i] = tmp; |
949 | 0 | } |
950 | | /* Prepare a printable command for logs, etc. */ |
951 | 0 | command = argv_assemble(ac, av); |
952 | |
|
953 | 0 | if ((pid = subprocess(tag, command, ac, av, &f, |
954 | 0 | SSH_SUBPROCESS_STDOUT_CAPTURE|SSH_SUBPROCESS_UNSAFE_PATH| |
955 | 0 | SSH_SUBPROCESS_PRESERVE_ENV, NULL, NULL, NULL)) == 0) |
956 | 0 | goto out; |
957 | | |
958 | 0 | load_hostkeys_file(hostkeys, hostfile_hostname, tag, f, 1); |
959 | |
|
960 | 0 | if (exited_cleanly(pid, tag, command, 0) != 0) |
961 | 0 | fatal("KnownHostsCommand failed"); |
962 | | |
963 | 0 | out: |
964 | 0 | if (f != NULL) |
965 | 0 | fclose(f); |
966 | 0 | ssh_signal(SIGCHLD, osigchld); |
967 | 0 | for (i = 0; i < ac; i++) |
968 | 0 | free(av[i]); |
969 | 0 | free(av); |
970 | 0 | free(tag); |
971 | 0 | free(command); |
972 | 0 | free(key_fp); |
973 | 0 | free(keytext); |
974 | 0 | } |
975 | | |
976 | | /* |
977 | | * check whether the supplied host key is valid, return -1 if the key |
978 | | * is not valid. user_hostfile[0] will not be updated if 'readonly' is true. |
979 | | */ |
980 | 0 | #define RDRW 0 |
981 | 0 | #define RDONLY 1 |
982 | 0 | #define ROQUIET 2 |
983 | | static int |
984 | | check_host_key(char *hostname, const struct ssh_conn_info *cinfo, |
985 | | struct sockaddr *hostaddr, u_short port, |
986 | | struct sshkey *host_key, int readonly, int clobber_port, |
987 | | char **user_hostfiles, u_int num_user_hostfiles, |
988 | | char **system_hostfiles, u_int num_system_hostfiles, |
989 | | const char *hostfile_command) |
990 | 0 | { |
991 | 0 | HostStatus host_status = -1, ip_status = -1; |
992 | 0 | struct sshkey *raw_key = NULL; |
993 | 0 | char *ip = NULL, *host = NULL; |
994 | 0 | char hostline[1000], *hostp, *fp, *ra; |
995 | 0 | char msg[1024]; |
996 | 0 | const char *type, *fail_reason = NULL; |
997 | 0 | const struct hostkey_entry *host_found = NULL, *ip_found = NULL; |
998 | 0 | int len, cancelled_forwarding = 0, confirmed; |
999 | 0 | int local = sockaddr_is_local(hostaddr); |
1000 | 0 | int r, want_cert = sshkey_is_cert(host_key), host_ip_differ = 0; |
1001 | 0 | int hostkey_trusted = 0; /* Known or explicitly accepted by user */ |
1002 | 0 | struct hostkeys *host_hostkeys, *ip_hostkeys; |
1003 | 0 | u_int i; |
1004 | | |
1005 | | /* |
1006 | | * Force accepting of the host key for loopback/localhost. The |
1007 | | * problem is that if the home directory is NFS-mounted to multiple |
1008 | | * machines, localhost will refer to a different machine in each of |
1009 | | * them, and the user will get bogus HOST_CHANGED warnings. This |
1010 | | * essentially disables host authentication for localhost; however, |
1011 | | * this is probably not a real problem. |
1012 | | */ |
1013 | 0 | if (options.no_host_authentication_for_localhost == 1 && local && |
1014 | 0 | options.host_key_alias == NULL) { |
1015 | 0 | debug("Forcing accepting of host key for " |
1016 | 0 | "loopback/localhost."); |
1017 | 0 | options.update_hostkeys = 0; |
1018 | 0 | return 0; |
1019 | 0 | } |
1020 | | |
1021 | | /* |
1022 | | * Don't ever try to write an invalid name to a known hosts file. |
1023 | | * Note: do this before get_hostfile_hostname_ipaddr() to catch |
1024 | | * '[' or ']' in the name before they are added. |
1025 | | */ |
1026 | 0 | if (strcspn(hostname, "@?*#[]|'\'\"\\") != strlen(hostname)) { |
1027 | 0 | debug_f("invalid hostname \"%s\"; will not record: %s", |
1028 | 0 | hostname, fail_reason); |
1029 | 0 | readonly = RDONLY; |
1030 | 0 | } |
1031 | | |
1032 | | /* |
1033 | | * Prepare the hostname and address strings used for hostkey lookup. |
1034 | | * In some cases, these will have a port number appended. |
1035 | | */ |
1036 | 0 | get_hostfile_hostname_ipaddr(hostname, hostaddr, |
1037 | 0 | clobber_port ? 0 : port, &host, &ip); |
1038 | | |
1039 | | /* |
1040 | | * Turn off check_host_ip if the connection is to localhost, via proxy |
1041 | | * command or if we don't have a hostname to compare with |
1042 | | */ |
1043 | 0 | if (options.check_host_ip && (local || |
1044 | 0 | strcmp(hostname, ip) == 0 || options.proxy_command != NULL)) |
1045 | 0 | options.check_host_ip = 0; |
1046 | |
|
1047 | 0 | host_hostkeys = init_hostkeys(); |
1048 | 0 | for (i = 0; i < num_user_hostfiles; i++) |
1049 | 0 | load_hostkeys(host_hostkeys, host, user_hostfiles[i], 0); |
1050 | 0 | for (i = 0; i < num_system_hostfiles; i++) |
1051 | 0 | load_hostkeys(host_hostkeys, host, system_hostfiles[i], 0); |
1052 | 0 | if (hostfile_command != NULL && !clobber_port) { |
1053 | 0 | load_hostkeys_command(host_hostkeys, hostfile_command, |
1054 | 0 | "HOSTNAME", cinfo, host_key, host); |
1055 | 0 | } |
1056 | |
|
1057 | 0 | ip_hostkeys = NULL; |
1058 | 0 | if (!want_cert && options.check_host_ip) { |
1059 | 0 | ip_hostkeys = init_hostkeys(); |
1060 | 0 | for (i = 0; i < num_user_hostfiles; i++) |
1061 | 0 | load_hostkeys(ip_hostkeys, ip, user_hostfiles[i], 0); |
1062 | 0 | for (i = 0; i < num_system_hostfiles; i++) |
1063 | 0 | load_hostkeys(ip_hostkeys, ip, system_hostfiles[i], 0); |
1064 | 0 | if (hostfile_command != NULL && !clobber_port) { |
1065 | 0 | load_hostkeys_command(ip_hostkeys, hostfile_command, |
1066 | 0 | "ADDRESS", cinfo, host_key, ip); |
1067 | 0 | } |
1068 | 0 | } |
1069 | |
|
1070 | 0 | retry: |
1071 | 0 | if (!hostkey_accepted_by_hostkeyalgs(host_key)) { |
1072 | 0 | error("host key %s not permitted by HostkeyAlgorithms", |
1073 | 0 | sshkey_ssh_name(host_key)); |
1074 | 0 | goto fail; |
1075 | 0 | } |
1076 | | |
1077 | | /* Reload these as they may have changed on cert->key downgrade */ |
1078 | 0 | want_cert = sshkey_is_cert(host_key); |
1079 | 0 | type = sshkey_type(host_key); |
1080 | | |
1081 | | /* |
1082 | | * Check if the host key is present in the user's list of known |
1083 | | * hosts or in the systemwide list. |
1084 | | */ |
1085 | 0 | host_status = check_key_in_hostkeys(host_hostkeys, host_key, |
1086 | 0 | &host_found); |
1087 | | |
1088 | | /* |
1089 | | * If there are no hostfiles, or if the hostkey was found via |
1090 | | * KnownHostsCommand, then don't try to touch the disk. |
1091 | | */ |
1092 | 0 | if (!readonly && (num_user_hostfiles == 0 || |
1093 | 0 | (host_found != NULL && host_found->note != 0))) |
1094 | 0 | readonly = RDONLY; |
1095 | | |
1096 | | /* |
1097 | | * Also perform check for the ip address, skip the check if we are |
1098 | | * localhost, looking for a certificate, or the hostname was an ip |
1099 | | * address to begin with. |
1100 | | */ |
1101 | 0 | if (!want_cert && ip_hostkeys != NULL) { |
1102 | 0 | ip_status = check_key_in_hostkeys(ip_hostkeys, host_key, |
1103 | 0 | &ip_found); |
1104 | 0 | if (host_status == HOST_CHANGED && |
1105 | 0 | (ip_status != HOST_CHANGED || |
1106 | 0 | (ip_found != NULL && |
1107 | 0 | !sshkey_equal(ip_found->key, host_found->key)))) |
1108 | 0 | host_ip_differ = 1; |
1109 | 0 | } else |
1110 | 0 | ip_status = host_status; |
1111 | |
|
1112 | 0 | switch (host_status) { |
1113 | 0 | case HOST_OK: |
1114 | | /* The host is known and the key matches. */ |
1115 | 0 | debug("Host '%.200s' is known and matches the %s host %s.", |
1116 | 0 | host, type, want_cert ? "certificate" : "key"); |
1117 | 0 | debug("Found %s in %s:%lu", want_cert ? "CA key" : "key", |
1118 | 0 | host_found->file, host_found->line); |
1119 | 0 | if (want_cert) { |
1120 | 0 | if (sshkey_cert_check_host(host_key, |
1121 | 0 | options.host_key_alias == NULL ? |
1122 | 0 | hostname : options.host_key_alias, |
1123 | 0 | options.ca_sign_algorithms, &fail_reason) != 0) { |
1124 | 0 | error("%s", fail_reason); |
1125 | 0 | goto fail; |
1126 | 0 | } |
1127 | | /* |
1128 | | * Do not attempt hostkey update if a certificate was |
1129 | | * successfully matched. |
1130 | | */ |
1131 | 0 | if (options.update_hostkeys != 0) { |
1132 | 0 | options.update_hostkeys = 0; |
1133 | 0 | debug3_f("certificate host key in use; " |
1134 | 0 | "disabling UpdateHostkeys"); |
1135 | 0 | } |
1136 | 0 | } |
1137 | | /* Turn off UpdateHostkeys if key was in system known_hosts */ |
1138 | 0 | if (options.update_hostkeys != 0 && |
1139 | 0 | (path_in_hostfiles(host_found->file, |
1140 | 0 | system_hostfiles, num_system_hostfiles) || |
1141 | 0 | (ip_status == HOST_OK && ip_found != NULL && |
1142 | 0 | path_in_hostfiles(ip_found->file, |
1143 | 0 | system_hostfiles, num_system_hostfiles)))) { |
1144 | 0 | options.update_hostkeys = 0; |
1145 | 0 | debug3_f("host key found in GlobalKnownHostsFile; " |
1146 | 0 | "disabling UpdateHostkeys"); |
1147 | 0 | } |
1148 | 0 | if (options.update_hostkeys != 0 && host_found->note) { |
1149 | 0 | options.update_hostkeys = 0; |
1150 | 0 | debug3_f("host key found via KnownHostsCommand; " |
1151 | 0 | "disabling UpdateHostkeys"); |
1152 | 0 | } |
1153 | 0 | if (options.check_host_ip && ip_status == HOST_NEW) { |
1154 | 0 | if (readonly || want_cert) |
1155 | 0 | logit("%s host key for IP address " |
1156 | 0 | "'%.128s' not in list of known hosts.", |
1157 | 0 | type, ip); |
1158 | 0 | else if (!add_host_to_hostfile(user_hostfiles[0], ip, |
1159 | 0 | host_key, options.hash_known_hosts)) |
1160 | 0 | logit("Failed to add the %s host key for IP " |
1161 | 0 | "address '%.128s' to the list of known " |
1162 | 0 | "hosts (%.500s).", type, ip, |
1163 | 0 | user_hostfiles[0]); |
1164 | 0 | else |
1165 | 0 | logit("Warning: Permanently added the %s host " |
1166 | 0 | "key for IP address '%.128s' to the list " |
1167 | 0 | "of known hosts.", type, ip); |
1168 | 0 | } else if (options.visual_host_key) { |
1169 | 0 | fp = sshkey_fingerprint(host_key, |
1170 | 0 | options.fingerprint_hash, SSH_FP_DEFAULT); |
1171 | 0 | ra = sshkey_fingerprint(host_key, |
1172 | 0 | options.fingerprint_hash, SSH_FP_RANDOMART); |
1173 | 0 | if (fp == NULL || ra == NULL) |
1174 | 0 | fatal_f("sshkey_fingerprint failed"); |
1175 | 0 | logit("Host key fingerprint is: %s\n%s", fp, ra); |
1176 | 0 | free(ra); |
1177 | 0 | free(fp); |
1178 | 0 | } |
1179 | 0 | hostkey_trusted = 1; |
1180 | 0 | break; |
1181 | 0 | case HOST_NEW: |
1182 | 0 | if (options.host_key_alias == NULL && port != 0 && |
1183 | 0 | port != SSH_DEFAULT_PORT && !clobber_port) { |
1184 | 0 | debug("checking without port identifier"); |
1185 | 0 | if (check_host_key(hostname, cinfo, hostaddr, 0, |
1186 | 0 | host_key, ROQUIET, 1, |
1187 | 0 | user_hostfiles, num_user_hostfiles, |
1188 | 0 | system_hostfiles, num_system_hostfiles, |
1189 | 0 | hostfile_command) == 0) { |
1190 | 0 | debug("found matching key w/out port"); |
1191 | 0 | break; |
1192 | 0 | } |
1193 | 0 | } |
1194 | 0 | if (readonly || want_cert) |
1195 | 0 | goto fail; |
1196 | | /* The host is new. */ |
1197 | 0 | if (options.strict_host_key_checking == |
1198 | 0 | SSH_STRICT_HOSTKEY_YES) { |
1199 | | /* |
1200 | | * User has requested strict host key checking. We |
1201 | | * will not add the host key automatically. The only |
1202 | | * alternative left is to abort. |
1203 | | */ |
1204 | 0 | error("No %s host key is known for %.200s and you " |
1205 | 0 | "have requested strict checking.", type, host); |
1206 | 0 | goto fail; |
1207 | 0 | } else if (options.strict_host_key_checking == |
1208 | 0 | SSH_STRICT_HOSTKEY_ASK) { |
1209 | 0 | char *msg1 = NULL, *msg2 = NULL; |
1210 | |
|
1211 | 0 | xasprintf(&msg1, "The authenticity of host " |
1212 | 0 | "'%.200s (%s)' can't be established", host, ip); |
1213 | |
|
1214 | 0 | if (show_other_keys(host_hostkeys, host_key)) { |
1215 | 0 | xextendf(&msg1, "\n", "but keys of different " |
1216 | 0 | "type are already known for this host."); |
1217 | 0 | } else |
1218 | 0 | xextendf(&msg1, "", "."); |
1219 | |
|
1220 | 0 | fp = sshkey_fingerprint(host_key, |
1221 | 0 | options.fingerprint_hash, SSH_FP_DEFAULT); |
1222 | 0 | ra = sshkey_fingerprint(host_key, |
1223 | 0 | options.fingerprint_hash, SSH_FP_RANDOMART); |
1224 | 0 | if (fp == NULL || ra == NULL) |
1225 | 0 | fatal_f("sshkey_fingerprint failed"); |
1226 | 0 | xextendf(&msg1, "\n", "%s key fingerprint is: %s", |
1227 | 0 | type, fp); |
1228 | 0 | if (options.visual_host_key) |
1229 | 0 | xextendf(&msg1, "\n", "%s", ra); |
1230 | 0 | if (options.verify_host_key_dns) { |
1231 | 0 | xextendf(&msg1, "\n", |
1232 | 0 | "%s host key fingerprint found in DNS.", |
1233 | 0 | matching_host_key_dns ? |
1234 | 0 | "Matching" : "No matching"); |
1235 | 0 | } |
1236 | | /* msg2 informs for other names matching this key */ |
1237 | 0 | if ((msg2 = other_hostkeys_message(host, ip, host_key, |
1238 | 0 | user_hostfiles, num_user_hostfiles, |
1239 | 0 | system_hostfiles, num_system_hostfiles)) != NULL) |
1240 | 0 | xextendf(&msg1, "\n", "%s", msg2); |
1241 | |
|
1242 | 0 | xextendf(&msg1, "\n", |
1243 | 0 | "Are you sure you want to continue connecting " |
1244 | 0 | "(yes/no/[fingerprint])? "); |
1245 | |
|
1246 | 0 | confirmed = confirm(msg1, fp); |
1247 | 0 | free(ra); |
1248 | 0 | free(fp); |
1249 | 0 | free(msg1); |
1250 | 0 | free(msg2); |
1251 | 0 | if (!confirmed) |
1252 | 0 | goto fail; |
1253 | 0 | hostkey_trusted = 1; /* user explicitly confirmed */ |
1254 | 0 | } |
1255 | | /* |
1256 | | * If in "new" or "off" strict mode, add the key automatically |
1257 | | * to the local known_hosts file. |
1258 | | */ |
1259 | 0 | if (options.check_host_ip && ip_status == HOST_NEW) { |
1260 | 0 | snprintf(hostline, sizeof(hostline), "%s,%s", host, ip); |
1261 | 0 | hostp = hostline; |
1262 | 0 | if (options.hash_known_hosts) { |
1263 | | /* Add hash of host and IP separately */ |
1264 | 0 | r = add_host_to_hostfile(user_hostfiles[0], |
1265 | 0 | host, host_key, options.hash_known_hosts) && |
1266 | 0 | add_host_to_hostfile(user_hostfiles[0], ip, |
1267 | 0 | host_key, options.hash_known_hosts); |
1268 | 0 | } else { |
1269 | | /* Add unhashed "host,ip" */ |
1270 | 0 | r = add_host_to_hostfile(user_hostfiles[0], |
1271 | 0 | hostline, host_key, |
1272 | 0 | options.hash_known_hosts); |
1273 | 0 | } |
1274 | 0 | } else { |
1275 | 0 | r = add_host_to_hostfile(user_hostfiles[0], host, |
1276 | 0 | host_key, options.hash_known_hosts); |
1277 | 0 | hostp = host; |
1278 | 0 | } |
1279 | |
|
1280 | 0 | if (!r) |
1281 | 0 | logit("Failed to add the host to the list of known " |
1282 | 0 | "hosts (%.500s).", user_hostfiles[0]); |
1283 | 0 | else |
1284 | 0 | logit("Warning: Permanently added '%.200s' (%s) to the " |
1285 | 0 | "list of known hosts.", hostp, type); |
1286 | 0 | break; |
1287 | 0 | case HOST_REVOKED: |
1288 | 0 | error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); |
1289 | 0 | error("@ WARNING: REVOKED HOST KEY DETECTED! @"); |
1290 | 0 | error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); |
1291 | 0 | error("The %s host key for %s is marked as revoked.", type, host); |
1292 | 0 | error("This could mean that a stolen key is being used to"); |
1293 | 0 | error("impersonate this host."); |
1294 | | |
1295 | | /* |
1296 | | * If strict host key checking is in use, the user will have |
1297 | | * to edit the key manually and we can only abort. |
1298 | | */ |
1299 | 0 | if (options.strict_host_key_checking != |
1300 | 0 | SSH_STRICT_HOSTKEY_OFF) { |
1301 | 0 | error("%s host key for %.200s was revoked and you have " |
1302 | 0 | "requested strict checking.", type, host); |
1303 | 0 | goto fail; |
1304 | 0 | } |
1305 | 0 | goto continue_unsafe; |
1306 | | |
1307 | 0 | case HOST_CHANGED: |
1308 | 0 | if (want_cert) { |
1309 | | /* |
1310 | | * This is only a debug() since it is valid to have |
1311 | | * CAs with wildcard DNS matches that don't match |
1312 | | * all hosts that one might visit. |
1313 | | */ |
1314 | 0 | debug("Host certificate authority does not " |
1315 | 0 | "match %s in %s:%lu", CA_MARKER, |
1316 | 0 | host_found->file, host_found->line); |
1317 | 0 | goto fail; |
1318 | 0 | } |
1319 | 0 | if (readonly == ROQUIET) |
1320 | 0 | goto fail; |
1321 | 0 | if (options.check_host_ip && host_ip_differ) { |
1322 | 0 | char *key_msg; |
1323 | 0 | if (ip_status == HOST_NEW) |
1324 | 0 | key_msg = "is unknown"; |
1325 | 0 | else if (ip_status == HOST_OK) |
1326 | 0 | key_msg = "is unchanged"; |
1327 | 0 | else |
1328 | 0 | key_msg = "has a different value"; |
1329 | 0 | error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); |
1330 | 0 | error("@ WARNING: POSSIBLE DNS SPOOFING DETECTED! @"); |
1331 | 0 | error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); |
1332 | 0 | error("The %s host key for %s has changed,", type, host); |
1333 | 0 | error("and the key for the corresponding IP address %s", ip); |
1334 | 0 | error("%s. This could either mean that", key_msg); |
1335 | 0 | error("DNS SPOOFING is happening or the IP address for the host"); |
1336 | 0 | error("and its host key have changed at the same time."); |
1337 | 0 | if (ip_status != HOST_NEW) |
1338 | 0 | error("Offending key for IP in %s:%lu", |
1339 | 0 | ip_found->file, ip_found->line); |
1340 | 0 | } |
1341 | | /* The host key has changed. */ |
1342 | 0 | warn_changed_key(host_key); |
1343 | 0 | if (num_user_hostfiles > 0 || num_system_hostfiles > 0) { |
1344 | 0 | error("Add correct host key in %.100s to get rid " |
1345 | 0 | "of this message.", num_user_hostfiles > 0 ? |
1346 | 0 | user_hostfiles[0] : system_hostfiles[0]); |
1347 | 0 | } |
1348 | 0 | error("Offending %s key in %s:%lu", |
1349 | 0 | sshkey_type(host_found->key), |
1350 | 0 | host_found->file, host_found->line); |
1351 | | |
1352 | | /* |
1353 | | * If strict host key checking is in use, the user will have |
1354 | | * to edit the key manually and we can only abort. |
1355 | | */ |
1356 | 0 | if (options.strict_host_key_checking != |
1357 | 0 | SSH_STRICT_HOSTKEY_OFF) { |
1358 | 0 | error("Host key for %.200s has changed and you have " |
1359 | 0 | "requested strict checking.", host); |
1360 | 0 | goto fail; |
1361 | 0 | } |
1362 | | |
1363 | 0 | continue_unsafe: |
1364 | | /* |
1365 | | * If strict host key checking has not been requested, allow |
1366 | | * the connection but without MITM-able authentication or |
1367 | | * forwarding. |
1368 | | */ |
1369 | 0 | if (options.password_authentication) { |
1370 | 0 | error("Password authentication is disabled to avoid " |
1371 | 0 | "man-in-the-middle attacks."); |
1372 | 0 | options.password_authentication = 0; |
1373 | 0 | cancelled_forwarding = 1; |
1374 | 0 | } |
1375 | 0 | if (options.kbd_interactive_authentication) { |
1376 | 0 | error("Keyboard-interactive authentication is disabled" |
1377 | 0 | " to avoid man-in-the-middle attacks."); |
1378 | 0 | options.kbd_interactive_authentication = 0; |
1379 | 0 | cancelled_forwarding = 1; |
1380 | 0 | } |
1381 | 0 | if (options.forward_agent) { |
1382 | 0 | error("Agent forwarding is disabled to avoid " |
1383 | 0 | "man-in-the-middle attacks."); |
1384 | 0 | options.forward_agent = 0; |
1385 | 0 | cancelled_forwarding = 1; |
1386 | 0 | } |
1387 | 0 | if (options.forward_x11) { |
1388 | 0 | error("X11 forwarding is disabled to avoid " |
1389 | 0 | "man-in-the-middle attacks."); |
1390 | 0 | options.forward_x11 = 0; |
1391 | 0 | cancelled_forwarding = 1; |
1392 | 0 | } |
1393 | 0 | if (options.num_local_forwards > 0 || |
1394 | 0 | options.num_remote_forwards > 0) { |
1395 | 0 | error("Port forwarding is disabled to avoid " |
1396 | 0 | "man-in-the-middle attacks."); |
1397 | 0 | options.num_local_forwards = |
1398 | 0 | options.num_remote_forwards = 0; |
1399 | 0 | cancelled_forwarding = 1; |
1400 | 0 | } |
1401 | 0 | if (options.tun_open != SSH_TUNMODE_NO) { |
1402 | 0 | error("Tunnel forwarding is disabled to avoid " |
1403 | 0 | "man-in-the-middle attacks."); |
1404 | 0 | options.tun_open = SSH_TUNMODE_NO; |
1405 | 0 | cancelled_forwarding = 1; |
1406 | 0 | } |
1407 | 0 | if (options.update_hostkeys != 0) { |
1408 | 0 | error("UpdateHostkeys is disabled because the host " |
1409 | 0 | "key is not trusted."); |
1410 | 0 | options.update_hostkeys = 0; |
1411 | 0 | } |
1412 | 0 | if (options.exit_on_forward_failure && cancelled_forwarding) |
1413 | 0 | fatal("Error: forwarding disabled due to host key " |
1414 | 0 | "check failure"); |
1415 | | |
1416 | | /* |
1417 | | * XXX Should permit the user to change to use the new id. |
1418 | | * This could be done by converting the host key to an |
1419 | | * identifying sentence, tell that the host identifies itself |
1420 | | * by that sentence, and ask the user if they wish to |
1421 | | * accept the authentication. |
1422 | | */ |
1423 | 0 | break; |
1424 | 0 | case HOST_FOUND: |
1425 | 0 | fatal("internal error"); |
1426 | 0 | break; |
1427 | 0 | } |
1428 | | |
1429 | 0 | if (options.check_host_ip && host_status != HOST_CHANGED && |
1430 | 0 | ip_status == HOST_CHANGED) { |
1431 | 0 | snprintf(msg, sizeof(msg), |
1432 | 0 | "Warning: the %s host key for '%.200s' " |
1433 | 0 | "differs from the key for the IP address '%.128s'" |
1434 | 0 | "\nOffending key for IP in %s:%lu", |
1435 | 0 | type, host, ip, ip_found->file, ip_found->line); |
1436 | 0 | if (host_status == HOST_OK) { |
1437 | 0 | len = strlen(msg); |
1438 | 0 | snprintf(msg + len, sizeof(msg) - len, |
1439 | 0 | "\nMatching host key in %s:%lu", |
1440 | 0 | host_found->file, host_found->line); |
1441 | 0 | } |
1442 | 0 | if (options.strict_host_key_checking == |
1443 | 0 | SSH_STRICT_HOSTKEY_ASK) { |
1444 | 0 | strlcat(msg, "\nAre you sure you want " |
1445 | 0 | "to continue connecting (yes/no)? ", sizeof(msg)); |
1446 | 0 | if (!confirm(msg, NULL)) |
1447 | 0 | goto fail; |
1448 | 0 | } else if (options.strict_host_key_checking != |
1449 | 0 | SSH_STRICT_HOSTKEY_OFF) { |
1450 | 0 | logit("%s", msg); |
1451 | 0 | error("Exiting, you have requested strict checking."); |
1452 | 0 | goto fail; |
1453 | 0 | } else { |
1454 | 0 | logit("%s", msg); |
1455 | 0 | } |
1456 | 0 | } |
1457 | | |
1458 | 0 | if (!hostkey_trusted && options.update_hostkeys) { |
1459 | 0 | debug_f("hostkey not known or explicitly trusted: " |
1460 | 0 | "disabling UpdateHostkeys"); |
1461 | 0 | options.update_hostkeys = 0; |
1462 | 0 | } |
1463 | |
|
1464 | 0 | sshkey_free(raw_key); |
1465 | 0 | free(ip); |
1466 | 0 | free(host); |
1467 | 0 | if (host_hostkeys != NULL) |
1468 | 0 | free_hostkeys(host_hostkeys); |
1469 | 0 | if (ip_hostkeys != NULL) |
1470 | 0 | free_hostkeys(ip_hostkeys); |
1471 | 0 | return 0; |
1472 | | |
1473 | 0 | fail: |
1474 | 0 | if (want_cert && host_status != HOST_REVOKED) { |
1475 | | /* |
1476 | | * No matching certificate. Downgrade cert to raw key and |
1477 | | * search normally. |
1478 | | */ |
1479 | 0 | debug("No matching CA found. Retry with plain key"); |
1480 | 0 | if ((r = sshkey_from_private(host_key, &raw_key)) != 0) |
1481 | 0 | fatal_fr(r, "decode key"); |
1482 | 0 | if ((r = sshkey_drop_cert(raw_key)) != 0) |
1483 | 0 | fatal_r(r, "Couldn't drop certificate"); |
1484 | 0 | host_key = raw_key; |
1485 | 0 | goto retry; |
1486 | 0 | } |
1487 | 0 | sshkey_free(raw_key); |
1488 | 0 | free(ip); |
1489 | 0 | free(host); |
1490 | 0 | if (host_hostkeys != NULL) |
1491 | 0 | free_hostkeys(host_hostkeys); |
1492 | 0 | if (ip_hostkeys != NULL) |
1493 | 0 | free_hostkeys(ip_hostkeys); |
1494 | 0 | return -1; |
1495 | 0 | } |
1496 | | |
1497 | | /* returns 0 if key verifies or -1 if key does NOT verify */ |
1498 | | int |
1499 | | verify_host_key(char *host, struct sockaddr *hostaddr, struct sshkey *host_key, |
1500 | | const struct ssh_conn_info *cinfo) |
1501 | 0 | { |
1502 | 0 | u_int i; |
1503 | 0 | int r = -1, flags = 0; |
1504 | 0 | char valid[64], *fp = NULL, *cafp = NULL; |
1505 | 0 | struct sshkey *plain = NULL; |
1506 | |
|
1507 | 0 | if ((fp = sshkey_fingerprint(host_key, |
1508 | 0 | options.fingerprint_hash, SSH_FP_DEFAULT)) == NULL) { |
1509 | 0 | error_fr(r, "fingerprint host key"); |
1510 | 0 | r = -1; |
1511 | 0 | goto out; |
1512 | 0 | } |
1513 | | |
1514 | 0 | if (sshkey_is_cert(host_key)) { |
1515 | 0 | if ((cafp = sshkey_fingerprint(host_key->cert->signature_key, |
1516 | 0 | options.fingerprint_hash, SSH_FP_DEFAULT)) == NULL) { |
1517 | 0 | error_fr(r, "fingerprint CA key"); |
1518 | 0 | r = -1; |
1519 | 0 | goto out; |
1520 | 0 | } |
1521 | 0 | sshkey_format_cert_validity(host_key->cert, |
1522 | 0 | valid, sizeof(valid)); |
1523 | 0 | debug("Server host certificate: %s %s, serial %llu " |
1524 | 0 | "ID \"%s\" CA %s %s valid %s", |
1525 | 0 | sshkey_ssh_name(host_key), fp, |
1526 | 0 | (unsigned long long)host_key->cert->serial, |
1527 | 0 | host_key->cert->key_id, |
1528 | 0 | sshkey_ssh_name(host_key->cert->signature_key), cafp, |
1529 | 0 | valid); |
1530 | 0 | for (i = 0; i < host_key->cert->nprincipals; i++) { |
1531 | 0 | debug2("Server host certificate hostname: %s", |
1532 | 0 | host_key->cert->principals[i]); |
1533 | 0 | } |
1534 | 0 | } else { |
1535 | 0 | debug("Server host key: %s %s", sshkey_ssh_name(host_key), fp); |
1536 | 0 | } |
1537 | | |
1538 | 0 | if (sshkey_equal(previous_host_key, host_key)) { |
1539 | 0 | debug2_f("server host key %s %s matches cached key", |
1540 | 0 | sshkey_type(host_key), fp); |
1541 | 0 | r = 0; |
1542 | 0 | goto out; |
1543 | 0 | } |
1544 | | |
1545 | | /* Check in RevokedHostKeys files if specified */ |
1546 | 0 | for (i = 0; i < options.num_revoked_host_keys; i++) { |
1547 | 0 | r = sshkey_check_revoked(host_key, |
1548 | 0 | options.revoked_host_keys[i]); |
1549 | 0 | switch (r) { |
1550 | 0 | case 0: |
1551 | 0 | break; /* not revoked */ |
1552 | 0 | case SSH_ERR_KEY_REVOKED: |
1553 | 0 | error("Host key %s %s revoked by file %s", |
1554 | 0 | sshkey_type(host_key), fp, |
1555 | 0 | options.revoked_host_keys[i]); |
1556 | 0 | r = -1; |
1557 | 0 | goto out; |
1558 | 0 | default: |
1559 | 0 | error_r(r, "Error checking host key %s %s in " |
1560 | 0 | "revoked keys file %s", sshkey_type(host_key), |
1561 | 0 | fp, options.revoked_host_keys[i]); |
1562 | 0 | r = -1; |
1563 | 0 | goto out; |
1564 | 0 | } |
1565 | 0 | } |
1566 | | |
1567 | 0 | if (options.verify_host_key_dns) { |
1568 | | /* |
1569 | | * XXX certs are not yet supported for DNS, so downgrade |
1570 | | * them and try the plain key. |
1571 | | */ |
1572 | 0 | if ((r = sshkey_from_private(host_key, &plain)) != 0) |
1573 | 0 | goto out; |
1574 | 0 | if (sshkey_is_cert(plain)) |
1575 | 0 | sshkey_drop_cert(plain); |
1576 | 0 | if (verify_host_key_dns(host, hostaddr, plain, &flags) == 0) { |
1577 | 0 | if (flags & DNS_VERIFY_FOUND) { |
1578 | 0 | if (options.verify_host_key_dns == 1 && |
1579 | 0 | flags & DNS_VERIFY_MATCH && |
1580 | 0 | flags & DNS_VERIFY_SECURE) { |
1581 | 0 | r = 0; |
1582 | 0 | goto out; |
1583 | 0 | } |
1584 | 0 | if (flags & DNS_VERIFY_MATCH) { |
1585 | 0 | matching_host_key_dns = 1; |
1586 | 0 | } else { |
1587 | 0 | warn_changed_key(plain); |
1588 | 0 | error("Update the SSHFP RR in DNS " |
1589 | 0 | "with the new host key to get rid " |
1590 | 0 | "of this message."); |
1591 | 0 | } |
1592 | 0 | } |
1593 | 0 | } |
1594 | 0 | } |
1595 | 0 | r = check_host_key(host, cinfo, hostaddr, options.port, host_key, |
1596 | 0 | RDRW, 0, options.user_hostfiles, options.num_user_hostfiles, |
1597 | 0 | options.system_hostfiles, options.num_system_hostfiles, |
1598 | 0 | options.known_hosts_command); |
1599 | |
|
1600 | 0 | out: |
1601 | 0 | sshkey_free(plain); |
1602 | 0 | free(fp); |
1603 | 0 | free(cafp); |
1604 | 0 | if (r == 0 && host_key != NULL) { |
1605 | 0 | sshkey_free(previous_host_key); |
1606 | 0 | r = sshkey_from_private(host_key, &previous_host_key); |
1607 | 0 | } |
1608 | |
|
1609 | 0 | return r; |
1610 | 0 | } |
1611 | | |
1612 | | static void |
1613 | | warn_nonpq_kex(void) |
1614 | 0 | { |
1615 | 0 | logit("** WARNING: connection is not using a post-quantum key exchange algorithm."); |
1616 | 0 | logit("** This session may be vulnerable to \"store now, decrypt later\" attacks."); |
1617 | 0 | logit("** The server may need to be upgraded. See https://openssh.com/pq.html"); |
1618 | 0 | } |
1619 | | |
1620 | | /* |
1621 | | * Starts a dialog with the server, and authenticates the current user on the |
1622 | | * server. This does not need any extra privileges. The basic connection |
1623 | | * to the server must already have been established before this is called. |
1624 | | * If login fails, this function prints an error and never returns. |
1625 | | * This function does not require super-user privileges. |
1626 | | */ |
1627 | | void |
1628 | | ssh_login(struct ssh *ssh, Sensitive *sensitive, const char *orighost, |
1629 | | struct sockaddr_storage *hostaddr, u_short port, struct passwd *pw, |
1630 | | int timeout_ms, const struct ssh_conn_info *cinfo) |
1631 | 0 | { |
1632 | 0 | char *host; |
1633 | 0 | char *server_user, *local_user; |
1634 | 0 | int r; |
1635 | |
|
1636 | 0 | local_user = xstrdup(pw->pw_name); |
1637 | 0 | server_user = options.user ? options.user : local_user; |
1638 | | |
1639 | | /* Convert the user-supplied hostname into all lowercase. */ |
1640 | 0 | host = xstrdup(orighost); |
1641 | 0 | lowercase(host); |
1642 | | |
1643 | | /* Exchange protocol version identification strings with the server. */ |
1644 | 0 | if ((r = kex_exchange_identification(ssh, timeout_ms, |
1645 | 0 | options.version_addendum)) != 0) |
1646 | 0 | sshpkt_fatal(ssh, r, "banner exchange"); |
1647 | | |
1648 | 0 | if ((ssh->compat & SSH_BUG_NOREKEY)) { |
1649 | 0 | logit("Warning: this server does not support rekeying."); |
1650 | 0 | logit("This session will eventually fail"); |
1651 | 0 | } |
1652 | | |
1653 | | /* Put the connection into non-blocking mode. */ |
1654 | 0 | ssh_packet_set_nonblocking(ssh); |
1655 | | |
1656 | | /* key exchange */ |
1657 | | /* authenticate user */ |
1658 | 0 | debug("Authenticating to %s:%d as '%s'", host, port, server_user); |
1659 | 0 | ssh_kex2(ssh, host, hostaddr, port, cinfo); |
1660 | 0 | kex_set_warn_weak_crypto(ssh, options.warn_weak_crypto && |
1661 | 0 | !options.kex_algorithms_set); |
1662 | 0 | if (!options.kex_algorithms_set && ssh->kex != NULL && |
1663 | 0 | ssh->kex->name != NULL && options.warn_weak_crypto && |
1664 | 0 | !kex_is_pq_from_name(ssh->kex->name)) { |
1665 | 0 | warn_nonpq_kex(); |
1666 | 0 | ssh->kex->non_pq_kex_warned = 1; |
1667 | 0 | } |
1668 | 0 | ssh_userauth2(ssh, local_user, server_user, host, sensitive); |
1669 | 0 | free(local_user); |
1670 | 0 | free(host); |
1671 | 0 | } |
1672 | | |
1673 | | /* print all known host keys for a given host, but skip keys of given type */ |
1674 | | static int |
1675 | | show_other_keys(struct hostkeys *hostkeys, struct sshkey *key) |
1676 | 0 | { |
1677 | 0 | int type[] = { |
1678 | 0 | KEY_RSA, |
1679 | 0 | KEY_ECDSA, |
1680 | 0 | KEY_ED25519, |
1681 | 0 | KEY_MLDSA44_ED25519, |
1682 | 0 | -1 |
1683 | 0 | }; |
1684 | 0 | int i, ret = 0; |
1685 | 0 | char *fp, *ra; |
1686 | 0 | const struct hostkey_entry *found; |
1687 | |
|
1688 | 0 | for (i = 0; type[i] != -1; i++) { |
1689 | 0 | if (type[i] == key->type) |
1690 | 0 | continue; |
1691 | 0 | if (!lookup_key_in_hostkeys_by_type(hostkeys, type[i], |
1692 | 0 | -1, &found)) |
1693 | 0 | continue; |
1694 | 0 | fp = sshkey_fingerprint(found->key, |
1695 | 0 | options.fingerprint_hash, SSH_FP_DEFAULT); |
1696 | 0 | ra = sshkey_fingerprint(found->key, |
1697 | 0 | options.fingerprint_hash, SSH_FP_RANDOMART); |
1698 | 0 | if (fp == NULL || ra == NULL) |
1699 | 0 | fatal_f("sshkey_fingerprint fail"); |
1700 | 0 | logit("WARNING: %s key found for host %s\n" |
1701 | 0 | "in %s:%lu\n" |
1702 | 0 | "%s key fingerprint %s.", |
1703 | 0 | sshkey_type(found->key), |
1704 | 0 | found->host, found->file, found->line, |
1705 | 0 | sshkey_type(found->key), fp); |
1706 | 0 | if (options.visual_host_key) |
1707 | 0 | logit("%s", ra); |
1708 | 0 | free(ra); |
1709 | 0 | free(fp); |
1710 | 0 | ret = 1; |
1711 | 0 | } |
1712 | 0 | return ret; |
1713 | 0 | } |
1714 | | |
1715 | | static void |
1716 | | warn_changed_key(struct sshkey *host_key) |
1717 | 0 | { |
1718 | 0 | char *fp; |
1719 | |
|
1720 | 0 | fp = sshkey_fingerprint(host_key, options.fingerprint_hash, |
1721 | 0 | SSH_FP_DEFAULT); |
1722 | 0 | if (fp == NULL) |
1723 | 0 | fatal_f("sshkey_fingerprint fail"); |
1724 | | |
1725 | 0 | error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); |
1726 | 0 | error("@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @"); |
1727 | 0 | error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); |
1728 | 0 | error("IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!"); |
1729 | 0 | error("Someone could be eavesdropping on you right now (man-in-the-middle attack)!"); |
1730 | 0 | error("It is also possible that a host key has just been changed."); |
1731 | 0 | error("The fingerprint for the %s key sent by the remote host is\n%s.", |
1732 | 0 | sshkey_type(host_key), fp); |
1733 | 0 | error("Please contact your system administrator."); |
1734 | |
|
1735 | 0 | free(fp); |
1736 | 0 | } |
1737 | | |
1738 | | /* |
1739 | | * Execute a local command |
1740 | | */ |
1741 | | int |
1742 | | ssh_local_cmd(const char *args) |
1743 | 0 | { |
1744 | 0 | char *shell; |
1745 | 0 | pid_t pid; |
1746 | 0 | int status; |
1747 | 0 | void (*osighand)(int); |
1748 | |
|
1749 | 0 | if (!options.permit_local_command || |
1750 | 0 | args == NULL || !*args) |
1751 | 0 | return (1); |
1752 | | |
1753 | 0 | if ((shell = getenv("SHELL")) == NULL || *shell == '\0') |
1754 | 0 | shell = _PATH_BSHELL; |
1755 | |
|
1756 | 0 | osighand = ssh_signal(SIGCHLD, SIG_DFL); |
1757 | 0 | pid = fork(); |
1758 | 0 | if (pid == 0) { |
1759 | 0 | ssh_signal(SIGPIPE, SIG_DFL); |
1760 | 0 | debug3("Executing %s -c \"%s\"", shell, args); |
1761 | 0 | execl(shell, shell, "-c", args, (char *)NULL); |
1762 | 0 | error("Couldn't execute %s -c \"%s\": %s", |
1763 | 0 | shell, args, strerror(errno)); |
1764 | 0 | _exit(1); |
1765 | 0 | } else if (pid == -1) |
1766 | 0 | fatal("fork failed: %.100s", strerror(errno)); |
1767 | 0 | while (waitpid(pid, &status, 0) == -1) |
1768 | 0 | if (errno != EINTR) |
1769 | 0 | fatal("Couldn't wait for child: %s", strerror(errno)); |
1770 | 0 | ssh_signal(SIGCHLD, osighand); |
1771 | |
|
1772 | 0 | if (!WIFEXITED(status)) |
1773 | 0 | return (1); |
1774 | | |
1775 | 0 | return (WEXITSTATUS(status)); |
1776 | 0 | } |
1777 | | |
1778 | | void |
1779 | | maybe_add_key_to_agent(const char *authfile, struct sshkey *private, |
1780 | | const char *comment, const char *passphrase) |
1781 | 0 | { |
1782 | 0 | int auth_sock = -1, r; |
1783 | 0 | const char *skprovider = NULL; |
1784 | |
|
1785 | 0 | if (options.add_keys_to_agent == 0) |
1786 | 0 | return; |
1787 | | |
1788 | 0 | if ((r = ssh_get_authentication_socket(&auth_sock)) != 0) { |
1789 | 0 | debug3("no authentication agent, not adding key"); |
1790 | 0 | return; |
1791 | 0 | } |
1792 | | |
1793 | 0 | if (options.add_keys_to_agent == 2 && |
1794 | 0 | !ask_permission("Add key %s (%s) to agent?", authfile, comment)) { |
1795 | 0 | debug3("user denied adding this key"); |
1796 | 0 | close(auth_sock); |
1797 | 0 | return; |
1798 | 0 | } |
1799 | 0 | if (sshkey_is_sk(private)) |
1800 | 0 | skprovider = options.sk_provider; |
1801 | 0 | if ((r = ssh_add_identity_constrained(auth_sock, private, |
1802 | 0 | comment == NULL ? authfile : comment, |
1803 | 0 | options.add_keys_to_agent_lifespan, |
1804 | 0 | (options.add_keys_to_agent == 3), skprovider, NULL, 0)) == 0) |
1805 | 0 | debug("identity added to agent: %s", authfile); |
1806 | 0 | else |
1807 | | debug("could not add identity to agent: %s (%d)", authfile, r); |
1808 | 0 | close(auth_sock); |
1809 | 0 | } |