1 |
|
|
/* $OpenBSD: sshconnect.c,v 1.287 2017/09/14 04:32:21 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 <sys/types.h> |
17 |
|
|
#include <sys/wait.h> |
18 |
|
|
#include <sys/stat.h> |
19 |
|
|
#include <sys/socket.h> |
20 |
|
|
#include <sys/time.h> |
21 |
|
|
|
22 |
|
|
#include <netinet/in.h> |
23 |
|
|
|
24 |
|
|
#include <ctype.h> |
25 |
|
|
#include <errno.h> |
26 |
|
|
#include <fcntl.h> |
27 |
|
|
#include <netdb.h> |
28 |
|
|
#include <paths.h> |
29 |
|
|
#include <poll.h> |
30 |
|
|
#include <signal.h> |
31 |
|
|
#include <pwd.h> |
32 |
|
|
#include <stdio.h> |
33 |
|
|
#include <stdlib.h> |
34 |
|
|
#include <string.h> |
35 |
|
|
#include <unistd.h> |
36 |
|
|
|
37 |
|
|
#include "xmalloc.h" |
38 |
|
|
#include "ssh.h" |
39 |
|
|
#include "buffer.h" |
40 |
|
|
#include "packet.h" |
41 |
|
|
#include "uidswap.h" |
42 |
|
|
#include "compat.h" |
43 |
|
|
#include "key.h" |
44 |
|
|
#include "sshconnect.h" |
45 |
|
|
#include "hostfile.h" |
46 |
|
|
#include "log.h" |
47 |
|
|
#include "misc.h" |
48 |
|
|
#include "readconf.h" |
49 |
|
|
#include "atomicio.h" |
50 |
|
|
#include "dns.h" |
51 |
|
|
#include "monitor_fdpass.h" |
52 |
|
|
#include "ssh2.h" |
53 |
|
|
#include "version.h" |
54 |
|
|
#include "authfile.h" |
55 |
|
|
#include "ssherr.h" |
56 |
|
|
#include "authfd.h" |
57 |
|
|
|
58 |
|
|
char *client_version_string = NULL; |
59 |
|
|
char *server_version_string = NULL; |
60 |
|
|
struct sshkey *previous_host_key = NULL; |
61 |
|
|
|
62 |
|
|
static int matching_host_key_dns = 0; |
63 |
|
|
|
64 |
|
|
static pid_t proxy_command_pid = 0; |
65 |
|
|
|
66 |
|
|
/* import */ |
67 |
|
|
extern Options options; |
68 |
|
|
extern char *__progname; |
69 |
|
|
extern uid_t original_real_uid; |
70 |
|
|
extern uid_t original_effective_uid; |
71 |
|
|
|
72 |
|
|
static int show_other_keys(struct hostkeys *, struct sshkey *); |
73 |
|
|
static void warn_changed_key(struct sshkey *); |
74 |
|
|
|
75 |
|
|
/* Expand a proxy command */ |
76 |
|
|
static char * |
77 |
|
|
expand_proxy_command(const char *proxy_command, const char *user, |
78 |
|
|
const char *host, int port) |
79 |
|
|
{ |
80 |
|
|
char *tmp, *ret, strport[NI_MAXSERV]; |
81 |
|
|
|
82 |
|
|
snprintf(strport, sizeof strport, "%d", port); |
83 |
|
|
xasprintf(&tmp, "exec %s", proxy_command); |
84 |
|
|
ret = percent_expand(tmp, "h", host, "p", strport, |
85 |
|
|
"r", options.user, (char *)NULL); |
86 |
|
|
free(tmp); |
87 |
|
|
return ret; |
88 |
|
|
} |
89 |
|
|
|
90 |
|
|
/* |
91 |
|
|
* Connect to the given ssh server using a proxy command that passes a |
92 |
|
|
* a connected fd back to us. |
93 |
|
|
*/ |
94 |
|
|
static int |
95 |
|
|
ssh_proxy_fdpass_connect(struct ssh *ssh, const char *host, u_short port, |
96 |
|
|
const char *proxy_command) |
97 |
|
|
{ |
98 |
|
|
char *command_string; |
99 |
|
|
int sp[2], sock; |
100 |
|
|
pid_t pid; |
101 |
|
|
char *shell; |
102 |
|
|
|
103 |
|
|
if ((shell = getenv("SHELL")) == NULL) |
104 |
|
|
shell = _PATH_BSHELL; |
105 |
|
|
|
106 |
|
|
if (socketpair(AF_UNIX, SOCK_STREAM, 0, sp) < 0) |
107 |
|
|
fatal("Could not create socketpair to communicate with " |
108 |
|
|
"proxy dialer: %.100s", strerror(errno)); |
109 |
|
|
|
110 |
|
|
command_string = expand_proxy_command(proxy_command, options.user, |
111 |
|
|
host, port); |
112 |
|
|
debug("Executing proxy dialer command: %.500s", command_string); |
113 |
|
|
|
114 |
|
|
/* Fork and execute the proxy command. */ |
115 |
|
|
if ((pid = fork()) == 0) { |
116 |
|
|
char *argv[10]; |
117 |
|
|
|
118 |
|
|
/* Child. Permanently give up superuser privileges. */ |
119 |
|
|
permanently_drop_suid(original_real_uid); |
120 |
|
|
|
121 |
|
|
close(sp[1]); |
122 |
|
|
/* Redirect stdin and stdout. */ |
123 |
|
|
if (sp[0] != 0) { |
124 |
|
|
if (dup2(sp[0], 0) < 0) |
125 |
|
|
perror("dup2 stdin"); |
126 |
|
|
} |
127 |
|
|
if (sp[0] != 1) { |
128 |
|
|
if (dup2(sp[0], 1) < 0) |
129 |
|
|
perror("dup2 stdout"); |
130 |
|
|
} |
131 |
|
|
if (sp[0] >= 2) |
132 |
|
|
close(sp[0]); |
133 |
|
|
|
134 |
|
|
/* |
135 |
|
|
* Stderr is left as it is so that error messages get |
136 |
|
|
* printed on the user's terminal. |
137 |
|
|
*/ |
138 |
|
|
argv[0] = shell; |
139 |
|
|
argv[1] = "-c"; |
140 |
|
|
argv[2] = command_string; |
141 |
|
|
argv[3] = NULL; |
142 |
|
|
|
143 |
|
|
/* |
144 |
|
|
* Execute the proxy command. |
145 |
|
|
* Note that we gave up any extra privileges above. |
146 |
|
|
*/ |
147 |
|
|
execv(argv[0], argv); |
148 |
|
|
perror(argv[0]); |
149 |
|
|
exit(1); |
150 |
|
|
} |
151 |
|
|
/* Parent. */ |
152 |
|
|
if (pid < 0) |
153 |
|
|
fatal("fork failed: %.100s", strerror(errno)); |
154 |
|
|
close(sp[0]); |
155 |
|
|
free(command_string); |
156 |
|
|
|
157 |
|
|
if ((sock = mm_receive_fd(sp[1])) == -1) |
158 |
|
|
fatal("proxy dialer did not pass back a connection"); |
159 |
|
|
close(sp[1]); |
160 |
|
|
|
161 |
|
|
while (waitpid(pid, NULL, 0) == -1) |
162 |
|
|
if (errno != EINTR) |
163 |
|
|
fatal("Couldn't wait for child: %s", strerror(errno)); |
164 |
|
|
|
165 |
|
|
/* Set the connection file descriptors. */ |
166 |
|
|
if (ssh_packet_set_connection(ssh, sock, sock) == NULL) |
167 |
|
|
return -1; /* ssh_packet_set_connection logs error */ |
168 |
|
|
|
169 |
|
|
return 0; |
170 |
|
|
} |
171 |
|
|
|
172 |
|
|
/* |
173 |
|
|
* Connect to the given ssh server using a proxy command. |
174 |
|
|
*/ |
175 |
|
|
static int |
176 |
|
|
ssh_proxy_connect(struct ssh *ssh, const char *host, u_short port, |
177 |
|
|
const char *proxy_command) |
178 |
|
|
{ |
179 |
|
|
char *command_string; |
180 |
|
|
int pin[2], pout[2]; |
181 |
|
|
pid_t pid; |
182 |
|
|
char *shell; |
183 |
|
|
|
184 |
|
|
if ((shell = getenv("SHELL")) == NULL || *shell == '\0') |
185 |
|
|
shell = _PATH_BSHELL; |
186 |
|
|
|
187 |
|
|
/* Create pipes for communicating with the proxy. */ |
188 |
|
|
if (pipe(pin) < 0 || pipe(pout) < 0) |
189 |
|
|
fatal("Could not create pipes to communicate with the proxy: %.100s", |
190 |
|
|
strerror(errno)); |
191 |
|
|
|
192 |
|
|
command_string = expand_proxy_command(proxy_command, options.user, |
193 |
|
|
host, port); |
194 |
|
|
debug("Executing proxy command: %.500s", command_string); |
195 |
|
|
|
196 |
|
|
/* Fork and execute the proxy command. */ |
197 |
|
|
if ((pid = fork()) == 0) { |
198 |
|
|
char *argv[10]; |
199 |
|
|
|
200 |
|
|
/* Child. Permanently give up superuser privileges. */ |
201 |
|
|
permanently_drop_suid(original_real_uid); |
202 |
|
|
|
203 |
|
|
/* Redirect stdin and stdout. */ |
204 |
|
|
close(pin[1]); |
205 |
|
|
if (pin[0] != 0) { |
206 |
|
|
if (dup2(pin[0], 0) < 0) |
207 |
|
|
perror("dup2 stdin"); |
208 |
|
|
close(pin[0]); |
209 |
|
|
} |
210 |
|
|
close(pout[0]); |
211 |
|
|
if (dup2(pout[1], 1) < 0) |
212 |
|
|
perror("dup2 stdout"); |
213 |
|
|
/* Cannot be 1 because pin allocated two descriptors. */ |
214 |
|
|
close(pout[1]); |
215 |
|
|
|
216 |
|
|
/* Stderr is left as it is so that error messages get |
217 |
|
|
printed on the user's terminal. */ |
218 |
|
|
argv[0] = shell; |
219 |
|
|
argv[1] = "-c"; |
220 |
|
|
argv[2] = command_string; |
221 |
|
|
argv[3] = NULL; |
222 |
|
|
|
223 |
|
|
/* Execute the proxy command. Note that we gave up any |
224 |
|
|
extra privileges above. */ |
225 |
|
|
signal(SIGPIPE, SIG_DFL); |
226 |
|
|
execv(argv[0], argv); |
227 |
|
|
perror(argv[0]); |
228 |
|
|
exit(1); |
229 |
|
|
} |
230 |
|
|
/* Parent. */ |
231 |
|
|
if (pid < 0) |
232 |
|
|
fatal("fork failed: %.100s", strerror(errno)); |
233 |
|
|
else |
234 |
|
|
proxy_command_pid = pid; /* save pid to clean up later */ |
235 |
|
|
|
236 |
|
|
/* Close child side of the descriptors. */ |
237 |
|
|
close(pin[0]); |
238 |
|
|
close(pout[1]); |
239 |
|
|
|
240 |
|
|
/* Free the command name. */ |
241 |
|
|
free(command_string); |
242 |
|
|
|
243 |
|
|
/* Set the connection file descriptors. */ |
244 |
|
|
if (ssh_packet_set_connection(ssh, pout[0], pin[1]) == NULL) |
245 |
|
|
return -1; /* ssh_packet_set_connection logs error */ |
246 |
|
|
|
247 |
|
|
return 0; |
248 |
|
|
} |
249 |
|
|
|
250 |
|
|
void |
251 |
|
|
ssh_kill_proxy_command(void) |
252 |
|
|
{ |
253 |
|
|
/* |
254 |
|
|
* Send SIGHUP to proxy command if used. We don't wait() in |
255 |
|
|
* case it hangs and instead rely on init to reap the child |
256 |
|
|
*/ |
257 |
|
|
if (proxy_command_pid > 1) |
258 |
|
|
kill(proxy_command_pid, SIGHUP); |
259 |
|
|
} |
260 |
|
|
|
261 |
|
|
/* |
262 |
|
|
* Creates a (possibly privileged) socket for use as the ssh connection. |
263 |
|
|
*/ |
264 |
|
|
static int |
265 |
|
|
ssh_create_socket(int privileged, struct addrinfo *ai) |
266 |
|
|
{ |
267 |
|
|
int sock, r, gaierr; |
268 |
|
|
struct addrinfo hints, *res = NULL; |
269 |
|
|
|
270 |
|
|
sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); |
271 |
|
|
if (sock < 0) { |
272 |
|
|
error("socket: %s", strerror(errno)); |
273 |
|
|
return -1; |
274 |
|
|
} |
275 |
|
|
fcntl(sock, F_SETFD, FD_CLOEXEC); |
276 |
|
|
|
277 |
|
|
/* Bind the socket to an alternative local IP address */ |
278 |
|
|
if (options.bind_address == NULL && !privileged) |
279 |
|
|
return sock; |
280 |
|
|
|
281 |
|
|
if (options.bind_address) { |
282 |
|
|
memset(&hints, 0, sizeof(hints)); |
283 |
|
|
hints.ai_family = ai->ai_family; |
284 |
|
|
hints.ai_socktype = ai->ai_socktype; |
285 |
|
|
hints.ai_protocol = ai->ai_protocol; |
286 |
|
|
hints.ai_flags = AI_PASSIVE; |
287 |
|
|
gaierr = getaddrinfo(options.bind_address, NULL, &hints, &res); |
288 |
|
|
if (gaierr) { |
289 |
|
|
error("getaddrinfo: %s: %s", options.bind_address, |
290 |
|
|
ssh_gai_strerror(gaierr)); |
291 |
|
|
close(sock); |
292 |
|
|
return -1; |
293 |
|
|
} |
294 |
|
|
} |
295 |
|
|
/* |
296 |
|
|
* If we are running as root and want to connect to a privileged |
297 |
|
|
* port, bind our own socket to a privileged port. |
298 |
|
|
*/ |
299 |
|
|
if (privileged) { |
300 |
|
|
PRIV_START; |
301 |
|
|
r = bindresvport_sa(sock, res ? res->ai_addr : NULL); |
302 |
|
|
PRIV_END; |
303 |
|
|
if (r < 0) { |
304 |
|
|
error("bindresvport_sa: af=%d %s", ai->ai_family, |
305 |
|
|
strerror(errno)); |
306 |
|
|
goto fail; |
307 |
|
|
} |
308 |
|
|
} else { |
309 |
|
|
if (bind(sock, res->ai_addr, res->ai_addrlen) < 0) { |
310 |
|
|
error("bind: %s: %s", options.bind_address, |
311 |
|
|
strerror(errno)); |
312 |
|
|
fail: |
313 |
|
|
close(sock); |
314 |
|
|
freeaddrinfo(res); |
315 |
|
|
return -1; |
316 |
|
|
} |
317 |
|
|
} |
318 |
|
|
if (res != NULL) |
319 |
|
|
freeaddrinfo(res); |
320 |
|
|
return sock; |
321 |
|
|
} |
322 |
|
|
|
323 |
|
|
/* |
324 |
|
|
* Wait up to *timeoutp milliseconds for fd to be readable. Updates |
325 |
|
|
* *timeoutp with time remaining. |
326 |
|
|
* Returns 0 if fd ready or -1 on timeout or error (see errno). |
327 |
|
|
*/ |
328 |
|
|
static int |
329 |
|
|
waitrfd(int fd, int *timeoutp) |
330 |
|
|
{ |
331 |
|
|
struct pollfd pfd; |
332 |
|
|
struct timeval t_start; |
333 |
|
|
int oerrno, r; |
334 |
|
|
|
335 |
|
|
gettimeofday(&t_start, NULL); |
336 |
|
|
pfd.fd = fd; |
337 |
|
|
pfd.events = POLLIN; |
338 |
|
|
for (; *timeoutp >= 0;) { |
339 |
|
|
r = poll(&pfd, 1, *timeoutp); |
340 |
|
|
oerrno = errno; |
341 |
|
|
ms_subtract_diff(&t_start, timeoutp); |
342 |
|
|
errno = oerrno; |
343 |
|
|
if (r > 0) |
344 |
|
|
return 0; |
345 |
|
|
else if (r == -1 && errno != EAGAIN) |
346 |
|
|
return -1; |
347 |
|
|
else if (r == 0) |
348 |
|
|
break; |
349 |
|
|
} |
350 |
|
|
/* timeout */ |
351 |
|
|
errno = ETIMEDOUT; |
352 |
|
|
return -1; |
353 |
|
|
} |
354 |
|
|
|
355 |
|
|
static int |
356 |
|
|
timeout_connect(int sockfd, const struct sockaddr *serv_addr, |
357 |
|
|
socklen_t addrlen, int *timeoutp) |
358 |
|
|
{ |
359 |
|
|
int optval = 0; |
360 |
|
|
socklen_t optlen = sizeof(optval); |
361 |
|
|
|
362 |
|
|
/* No timeout: just do a blocking connect() */ |
363 |
|
|
if (*timeoutp <= 0) |
364 |
|
|
return connect(sockfd, serv_addr, addrlen); |
365 |
|
|
|
366 |
|
|
set_nonblock(sockfd); |
367 |
|
|
if (connect(sockfd, serv_addr, addrlen) == 0) { |
368 |
|
|
/* Succeeded already? */ |
369 |
|
|
unset_nonblock(sockfd); |
370 |
|
|
return 0; |
371 |
|
|
} else if (errno != EINPROGRESS) |
372 |
|
|
return -1; |
373 |
|
|
|
374 |
|
|
if (waitrfd(sockfd, timeoutp) == -1) |
375 |
|
|
return -1; |
376 |
|
|
|
377 |
|
|
/* Completed or failed */ |
378 |
|
|
if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &optval, &optlen) == -1) { |
379 |
|
|
debug("getsockopt: %s", strerror(errno)); |
380 |
|
|
return -1; |
381 |
|
|
} |
382 |
|
|
if (optval != 0) { |
383 |
|
|
errno = optval; |
384 |
|
|
return -1; |
385 |
|
|
} |
386 |
|
|
unset_nonblock(sockfd); |
387 |
|
|
return 0; |
388 |
|
|
} |
389 |
|
|
|
390 |
|
|
/* |
391 |
|
|
* Opens a TCP/IP connection to the remote server on the given host. |
392 |
|
|
* The address of the remote host will be returned in hostaddr. |
393 |
|
|
* If port is 0, the default port will be used. If needpriv is true, |
394 |
|
|
* a privileged port will be allocated to make the connection. |
395 |
|
|
* This requires super-user privileges if needpriv is true. |
396 |
|
|
* Connection_attempts specifies the maximum number of tries (one per |
397 |
|
|
* second). If proxy_command is non-NULL, it specifies the command (with %h |
398 |
|
|
* and %p substituted for host and port, respectively) to use to contact |
399 |
|
|
* the daemon. |
400 |
|
|
*/ |
401 |
|
|
static int |
402 |
|
|
ssh_connect_direct(struct ssh *ssh, const char *host, struct addrinfo *aitop, |
403 |
|
|
struct sockaddr_storage *hostaddr, u_short port, int family, |
404 |
|
|
int connection_attempts, int *timeout_ms, int want_keepalive, int needpriv) |
405 |
|
|
{ |
406 |
|
|
int on = 1; |
407 |
|
|
int sock = -1, attempt; |
408 |
|
|
char ntop[NI_MAXHOST], strport[NI_MAXSERV]; |
409 |
|
|
struct addrinfo *ai; |
410 |
|
|
|
411 |
|
|
debug2("%s: needpriv %d", __func__, needpriv); |
412 |
|
|
memset(ntop, 0, sizeof(ntop)); |
413 |
|
|
memset(strport, 0, sizeof(strport)); |
414 |
|
|
|
415 |
|
|
for (attempt = 0; attempt < connection_attempts; attempt++) { |
416 |
|
|
if (attempt > 0) { |
417 |
|
|
/* Sleep a moment before retrying. */ |
418 |
|
|
sleep(1); |
419 |
|
|
debug("Trying again..."); |
420 |
|
|
} |
421 |
|
|
/* |
422 |
|
|
* Loop through addresses for this host, and try each one in |
423 |
|
|
* sequence until the connection succeeds. |
424 |
|
|
*/ |
425 |
|
|
for (ai = aitop; ai; ai = ai->ai_next) { |
426 |
|
|
if (ai->ai_family != AF_INET && |
427 |
|
|
ai->ai_family != AF_INET6) |
428 |
|
|
continue; |
429 |
|
|
if (getnameinfo(ai->ai_addr, ai->ai_addrlen, |
430 |
|
|
ntop, sizeof(ntop), strport, sizeof(strport), |
431 |
|
|
NI_NUMERICHOST|NI_NUMERICSERV) != 0) { |
432 |
|
|
error("%s: getnameinfo failed", __func__); |
433 |
|
|
continue; |
434 |
|
|
} |
435 |
|
|
debug("Connecting to %.200s [%.100s] port %s.", |
436 |
|
|
host, ntop, strport); |
437 |
|
|
|
438 |
|
|
/* Create a socket for connecting. */ |
439 |
|
|
sock = ssh_create_socket(needpriv, ai); |
440 |
|
|
if (sock < 0) |
441 |
|
|
/* Any error is already output */ |
442 |
|
|
continue; |
443 |
|
|
|
444 |
|
|
if (timeout_connect(sock, ai->ai_addr, ai->ai_addrlen, |
445 |
|
|
timeout_ms) >= 0) { |
446 |
|
|
/* Successful connection. */ |
447 |
|
|
memcpy(hostaddr, ai->ai_addr, ai->ai_addrlen); |
448 |
|
|
break; |
449 |
|
|
} else { |
450 |
|
|
debug("connect to address %s port %s: %s", |
451 |
|
|
ntop, strport, strerror(errno)); |
452 |
|
|
close(sock); |
453 |
|
|
sock = -1; |
454 |
|
|
} |
455 |
|
|
} |
456 |
|
|
if (sock != -1) |
457 |
|
|
break; /* Successful connection. */ |
458 |
|
|
} |
459 |
|
|
|
460 |
|
|
/* Return failure if we didn't get a successful connection. */ |
461 |
|
|
if (sock == -1) { |
462 |
|
|
error("ssh: connect to host %s port %s: %s", |
463 |
|
|
host, strport, strerror(errno)); |
464 |
|
|
return (-1); |
465 |
|
|
} |
466 |
|
|
|
467 |
|
|
debug("Connection established."); |
468 |
|
|
|
469 |
|
|
/* Set SO_KEEPALIVE if requested. */ |
470 |
|
|
if (want_keepalive && |
471 |
|
|
setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (void *)&on, |
472 |
|
|
sizeof(on)) < 0) |
473 |
|
|
error("setsockopt SO_KEEPALIVE: %.100s", strerror(errno)); |
474 |
|
|
|
475 |
|
|
/* Set the connection. */ |
476 |
|
|
if (ssh_packet_set_connection(ssh, sock, sock) == NULL) |
477 |
|
|
return -1; /* ssh_packet_set_connection logs error */ |
478 |
|
|
|
479 |
|
|
return 0; |
480 |
|
|
} |
481 |
|
|
|
482 |
|
|
int |
483 |
|
|
ssh_connect(struct ssh *ssh, const char *host, struct addrinfo *addrs, |
484 |
|
|
struct sockaddr_storage *hostaddr, u_short port, int family, |
485 |
|
|
int connection_attempts, int *timeout_ms, int want_keepalive, int needpriv) |
486 |
|
|
{ |
487 |
|
|
if (options.proxy_command == NULL) { |
488 |
|
|
return ssh_connect_direct(ssh, host, addrs, hostaddr, port, |
489 |
|
|
family, connection_attempts, timeout_ms, want_keepalive, |
490 |
|
|
needpriv); |
491 |
|
|
} else if (strcmp(options.proxy_command, "-") == 0) { |
492 |
|
|
if ((ssh_packet_set_connection(ssh, |
493 |
|
|
STDIN_FILENO, STDOUT_FILENO)) == NULL) |
494 |
|
|
return -1; /* ssh_packet_set_connection logs error */ |
495 |
|
|
return 0; |
496 |
|
|
} else if (options.proxy_use_fdpass) { |
497 |
|
|
return ssh_proxy_fdpass_connect(ssh, host, port, |
498 |
|
|
options.proxy_command); |
499 |
|
|
} |
500 |
|
|
return ssh_proxy_connect(ssh, host, port, options.proxy_command); |
501 |
|
|
} |
502 |
|
|
|
503 |
|
|
static void |
504 |
|
|
send_client_banner(int connection_out, int minor1) |
505 |
|
|
{ |
506 |
|
|
/* Send our own protocol version identification. */ |
507 |
|
|
xasprintf(&client_version_string, "SSH-%d.%d-%.100s\r\n", |
508 |
|
|
PROTOCOL_MAJOR_2, PROTOCOL_MINOR_2, SSH_VERSION); |
509 |
|
|
if (atomicio(vwrite, connection_out, client_version_string, |
510 |
|
|
strlen(client_version_string)) != strlen(client_version_string)) |
511 |
|
|
fatal("write: %.100s", strerror(errno)); |
512 |
|
|
chop(client_version_string); |
513 |
|
|
debug("Local version string %.100s", client_version_string); |
514 |
|
|
} |
515 |
|
|
|
516 |
|
|
/* |
517 |
|
|
* Waits for the server identification string, and sends our own |
518 |
|
|
* identification string. |
519 |
|
|
*/ |
520 |
|
|
void |
521 |
|
|
ssh_exchange_identification(int timeout_ms) |
522 |
|
|
{ |
523 |
|
|
char buf[256], remote_version[256]; /* must be same size! */ |
524 |
|
|
int remote_major, remote_minor, mismatch; |
525 |
|
|
int connection_in = packet_get_connection_in(); |
526 |
|
|
int connection_out = packet_get_connection_out(); |
527 |
|
|
u_int i, n; |
528 |
|
|
size_t len; |
529 |
|
|
int rc; |
530 |
|
|
|
531 |
|
|
send_client_banner(connection_out, 0); |
532 |
|
|
|
533 |
|
|
/* Read other side's version identification. */ |
534 |
|
|
for (n = 0;;) { |
535 |
|
|
for (i = 0; i < sizeof(buf) - 1; i++) { |
536 |
|
|
if (timeout_ms > 0) { |
537 |
|
|
rc = waitrfd(connection_in, &timeout_ms); |
538 |
|
|
if (rc == -1 && errno == ETIMEDOUT) { |
539 |
|
|
fatal("Connection timed out during " |
540 |
|
|
"banner exchange"); |
541 |
|
|
} else if (rc == -1) { |
542 |
|
|
fatal("%s: %s", |
543 |
|
|
__func__, strerror(errno)); |
544 |
|
|
} |
545 |
|
|
} |
546 |
|
|
|
547 |
|
|
len = atomicio(read, connection_in, &buf[i], 1); |
548 |
|
|
if (len != 1 && errno == EPIPE) |
549 |
|
|
fatal("ssh_exchange_identification: " |
550 |
|
|
"Connection closed by remote host"); |
551 |
|
|
else if (len != 1) |
552 |
|
|
fatal("ssh_exchange_identification: " |
553 |
|
|
"read: %.100s", strerror(errno)); |
554 |
|
|
if (buf[i] == '\r') { |
555 |
|
|
buf[i] = '\n'; |
556 |
|
|
buf[i + 1] = 0; |
557 |
|
|
continue; /**XXX wait for \n */ |
558 |
|
|
} |
559 |
|
|
if (buf[i] == '\n') { |
560 |
|
|
buf[i + 1] = 0; |
561 |
|
|
break; |
562 |
|
|
} |
563 |
|
|
if (++n > 65536) |
564 |
|
|
fatal("ssh_exchange_identification: " |
565 |
|
|
"No banner received"); |
566 |
|
|
} |
567 |
|
|
buf[sizeof(buf) - 1] = 0; |
568 |
|
|
if (strncmp(buf, "SSH-", 4) == 0) |
569 |
|
|
break; |
570 |
|
|
debug("ssh_exchange_identification: %s", buf); |
571 |
|
|
} |
572 |
|
|
server_version_string = xstrdup(buf); |
573 |
|
|
|
574 |
|
|
/* |
575 |
|
|
* Check that the versions match. In future this might accept |
576 |
|
|
* several versions and set appropriate flags to handle them. |
577 |
|
|
*/ |
578 |
|
|
if (sscanf(server_version_string, "SSH-%d.%d-%[^\n]\n", |
579 |
|
|
&remote_major, &remote_minor, remote_version) != 3) |
580 |
|
|
fatal("Bad remote protocol version identification: '%.100s'", buf); |
581 |
|
|
debug("Remote protocol version %d.%d, remote software version %.100s", |
582 |
|
|
remote_major, remote_minor, remote_version); |
583 |
|
|
|
584 |
|
|
active_state->compat = compat_datafellows(remote_version); |
585 |
|
|
mismatch = 0; |
586 |
|
|
|
587 |
|
|
switch (remote_major) { |
588 |
|
|
case 2: |
589 |
|
|
break; |
590 |
|
|
case 1: |
591 |
|
|
if (remote_minor != 99) |
592 |
|
|
mismatch = 1; |
593 |
|
|
break; |
594 |
|
|
default: |
595 |
|
|
mismatch = 1; |
596 |
|
|
break; |
597 |
|
|
} |
598 |
|
|
if (mismatch) |
599 |
|
|
fatal("Protocol major versions differ: %d vs. %d", |
600 |
|
|
PROTOCOL_MAJOR_2, remote_major); |
601 |
|
|
if ((datafellows & SSH_BUG_DERIVEKEY) != 0) |
602 |
|
|
fatal("Server version \"%.100s\" uses unsafe key agreement; " |
603 |
|
|
"refusing connection", remote_version); |
604 |
|
|
if ((datafellows & SSH_BUG_RSASIGMD5) != 0) |
605 |
|
|
logit("Server version \"%.100s\" uses unsafe RSA signature " |
606 |
|
|
"scheme; disabling use of RSA keys", remote_version); |
607 |
|
|
chop(server_version_string); |
608 |
|
|
} |
609 |
|
|
|
610 |
|
|
/* defaults to 'no' */ |
611 |
|
|
static int |
612 |
|
|
confirm(const char *prompt) |
613 |
|
|
{ |
614 |
|
|
const char *msg, *again = "Please type 'yes' or 'no': "; |
615 |
|
|
char *p; |
616 |
|
|
int ret = -1; |
617 |
|
|
|
618 |
|
|
if (options.batch_mode) |
619 |
|
|
return 0; |
620 |
|
|
for (msg = prompt;;msg = again) { |
621 |
|
|
p = read_passphrase(msg, RP_ECHO); |
622 |
|
|
if (p == NULL || |
623 |
|
|
(p[0] == '\0') || (p[0] == '\n') || |
624 |
|
|
strncasecmp(p, "no", 2) == 0) |
625 |
|
|
ret = 0; |
626 |
|
|
if (p && strncasecmp(p, "yes", 3) == 0) |
627 |
|
|
ret = 1; |
628 |
|
|
free(p); |
629 |
|
|
if (ret != -1) |
630 |
|
|
return ret; |
631 |
|
|
} |
632 |
|
|
} |
633 |
|
|
|
634 |
|
|
static int |
635 |
|
|
check_host_cert(const char *host, const struct sshkey *host_key) |
636 |
|
|
{ |
637 |
|
|
const char *reason; |
638 |
|
|
|
639 |
|
|
if (key_cert_check_authority(host_key, 1, 0, host, &reason) != 0) { |
640 |
|
|
error("%s", reason); |
641 |
|
|
return 0; |
642 |
|
|
} |
643 |
|
|
if (buffer_len(host_key->cert->critical) != 0) { |
644 |
|
|
error("Certificate for %s contains unsupported " |
645 |
|
|
"critical options(s)", host); |
646 |
|
|
return 0; |
647 |
|
|
} |
648 |
|
|
return 1; |
649 |
|
|
} |
650 |
|
|
|
651 |
|
|
static int |
652 |
|
|
sockaddr_is_local(struct sockaddr *hostaddr) |
653 |
|
|
{ |
654 |
|
|
switch (hostaddr->sa_family) { |
655 |
|
|
case AF_INET: |
656 |
|
|
return (ntohl(((struct sockaddr_in *)hostaddr)-> |
657 |
|
|
sin_addr.s_addr) >> 24) == IN_LOOPBACKNET; |
658 |
|
|
case AF_INET6: |
659 |
|
|
return IN6_IS_ADDR_LOOPBACK( |
660 |
|
|
&(((struct sockaddr_in6 *)hostaddr)->sin6_addr)); |
661 |
|
|
default: |
662 |
|
|
return 0; |
663 |
|
|
} |
664 |
|
|
} |
665 |
|
|
|
666 |
|
|
/* |
667 |
|
|
* Prepare the hostname and ip address strings that are used to lookup |
668 |
|
|
* host keys in known_hosts files. These may have a port number appended. |
669 |
|
|
*/ |
670 |
|
|
void |
671 |
|
|
get_hostfile_hostname_ipaddr(char *hostname, struct sockaddr *hostaddr, |
672 |
|
|
u_short port, char **hostfile_hostname, char **hostfile_ipaddr) |
673 |
|
|
{ |
674 |
|
|
char ntop[NI_MAXHOST]; |
675 |
|
|
|
676 |
|
|
/* |
677 |
|
|
* We don't have the remote ip-address for connections |
678 |
|
|
* using a proxy command |
679 |
|
|
*/ |
680 |
|
|
if (hostfile_ipaddr != NULL) { |
681 |
|
|
if (options.proxy_command == NULL) { |
682 |
|
|
if (getnameinfo(hostaddr, hostaddr->sa_len, |
683 |
|
|
ntop, sizeof(ntop), NULL, 0, NI_NUMERICHOST) != 0) |
684 |
|
|
fatal("%s: getnameinfo failed", __func__); |
685 |
|
|
*hostfile_ipaddr = put_host_port(ntop, port); |
686 |
|
|
} else { |
687 |
|
|
*hostfile_ipaddr = xstrdup("<no hostip for proxy " |
688 |
|
|
"command>"); |
689 |
|
|
} |
690 |
|
|
} |
691 |
|
|
|
692 |
|
|
/* |
693 |
|
|
* Allow the user to record the key under a different name or |
694 |
|
|
* differentiate a non-standard port. This is useful for ssh |
695 |
|
|
* tunneling over forwarded connections or if you run multiple |
696 |
|
|
* sshd's on different ports on the same machine. |
697 |
|
|
*/ |
698 |
|
|
if (hostfile_hostname != NULL) { |
699 |
|
|
if (options.host_key_alias != NULL) { |
700 |
|
|
*hostfile_hostname = xstrdup(options.host_key_alias); |
701 |
|
|
debug("using hostkeyalias: %s", *hostfile_hostname); |
702 |
|
|
} else { |
703 |
|
|
*hostfile_hostname = put_host_port(hostname, port); |
704 |
|
|
} |
705 |
|
|
} |
706 |
|
|
} |
707 |
|
|
|
708 |
|
|
/* |
709 |
|
|
* check whether the supplied host key is valid, return -1 if the key |
710 |
|
|
* is not valid. user_hostfile[0] will not be updated if 'readonly' is true. |
711 |
|
|
*/ |
712 |
|
|
#define RDRW 0 |
713 |
|
|
#define RDONLY 1 |
714 |
|
|
#define ROQUIET 2 |
715 |
|
|
static int |
716 |
|
|
check_host_key(char *hostname, struct sockaddr *hostaddr, u_short port, |
717 |
|
|
struct sshkey *host_key, int readonly, |
718 |
|
|
char **user_hostfiles, u_int num_user_hostfiles, |
719 |
|
|
char **system_hostfiles, u_int num_system_hostfiles) |
720 |
|
|
{ |
721 |
|
|
HostStatus host_status; |
722 |
|
|
HostStatus ip_status; |
723 |
|
|
struct sshkey *raw_key = NULL; |
724 |
|
|
char *ip = NULL, *host = NULL; |
725 |
|
|
char hostline[1000], *hostp, *fp, *ra; |
726 |
|
|
char msg[1024]; |
727 |
|
|
const char *type; |
728 |
|
|
const struct hostkey_entry *host_found, *ip_found; |
729 |
|
|
int len, cancelled_forwarding = 0; |
730 |
|
|
int local = sockaddr_is_local(hostaddr); |
731 |
|
|
int r, want_cert = sshkey_is_cert(host_key), host_ip_differ = 0; |
732 |
|
|
int hostkey_trusted = 0; /* Known or explicitly accepted by user */ |
733 |
|
|
struct hostkeys *host_hostkeys, *ip_hostkeys; |
734 |
|
|
u_int i; |
735 |
|
|
|
736 |
|
|
/* |
737 |
|
|
* Force accepting of the host key for loopback/localhost. The |
738 |
|
|
* problem is that if the home directory is NFS-mounted to multiple |
739 |
|
|
* machines, localhost will refer to a different machine in each of |
740 |
|
|
* them, and the user will get bogus HOST_CHANGED warnings. This |
741 |
|
|
* essentially disables host authentication for localhost; however, |
742 |
|
|
* this is probably not a real problem. |
743 |
|
|
*/ |
744 |
|
|
if (options.no_host_authentication_for_localhost == 1 && local && |
745 |
|
|
options.host_key_alias == NULL) { |
746 |
|
|
debug("Forcing accepting of host key for " |
747 |
|
|
"loopback/localhost."); |
748 |
|
|
return 0; |
749 |
|
|
} |
750 |
|
|
|
751 |
|
|
/* |
752 |
|
|
* Prepare the hostname and address strings used for hostkey lookup. |
753 |
|
|
* In some cases, these will have a port number appended. |
754 |
|
|
*/ |
755 |
|
|
get_hostfile_hostname_ipaddr(hostname, hostaddr, port, &host, &ip); |
756 |
|
|
|
757 |
|
|
/* |
758 |
|
|
* Turn off check_host_ip if the connection is to localhost, via proxy |
759 |
|
|
* command or if we don't have a hostname to compare with |
760 |
|
|
*/ |
761 |
|
|
if (options.check_host_ip && (local || |
762 |
|
|
strcmp(hostname, ip) == 0 || options.proxy_command != NULL)) |
763 |
|
|
options.check_host_ip = 0; |
764 |
|
|
|
765 |
|
|
host_hostkeys = init_hostkeys(); |
766 |
|
|
for (i = 0; i < num_user_hostfiles; i++) |
767 |
|
|
load_hostkeys(host_hostkeys, host, user_hostfiles[i]); |
768 |
|
|
for (i = 0; i < num_system_hostfiles; i++) |
769 |
|
|
load_hostkeys(host_hostkeys, host, system_hostfiles[i]); |
770 |
|
|
|
771 |
|
|
ip_hostkeys = NULL; |
772 |
|
|
if (!want_cert && options.check_host_ip) { |
773 |
|
|
ip_hostkeys = init_hostkeys(); |
774 |
|
|
for (i = 0; i < num_user_hostfiles; i++) |
775 |
|
|
load_hostkeys(ip_hostkeys, ip, user_hostfiles[i]); |
776 |
|
|
for (i = 0; i < num_system_hostfiles; i++) |
777 |
|
|
load_hostkeys(ip_hostkeys, ip, system_hostfiles[i]); |
778 |
|
|
} |
779 |
|
|
|
780 |
|
|
retry: |
781 |
|
|
/* Reload these as they may have changed on cert->key downgrade */ |
782 |
|
|
want_cert = sshkey_is_cert(host_key); |
783 |
|
|
type = sshkey_type(host_key); |
784 |
|
|
|
785 |
|
|
/* |
786 |
|
|
* Check if the host key is present in the user's list of known |
787 |
|
|
* hosts or in the systemwide list. |
788 |
|
|
*/ |
789 |
|
|
host_status = check_key_in_hostkeys(host_hostkeys, host_key, |
790 |
|
|
&host_found); |
791 |
|
|
|
792 |
|
|
/* |
793 |
|
|
* Also perform check for the ip address, skip the check if we are |
794 |
|
|
* localhost, looking for a certificate, or the hostname was an ip |
795 |
|
|
* address to begin with. |
796 |
|
|
*/ |
797 |
|
|
if (!want_cert && ip_hostkeys != NULL) { |
798 |
|
|
ip_status = check_key_in_hostkeys(ip_hostkeys, host_key, |
799 |
|
|
&ip_found); |
800 |
|
|
if (host_status == HOST_CHANGED && |
801 |
|
|
(ip_status != HOST_CHANGED || |
802 |
|
|
(ip_found != NULL && |
803 |
|
|
!sshkey_equal(ip_found->key, host_found->key)))) |
804 |
|
|
host_ip_differ = 1; |
805 |
|
|
} else |
806 |
|
|
ip_status = host_status; |
807 |
|
|
|
808 |
|
|
switch (host_status) { |
809 |
|
|
case HOST_OK: |
810 |
|
|
/* The host is known and the key matches. */ |
811 |
|
|
debug("Host '%.200s' is known and matches the %s host %s.", |
812 |
|
|
host, type, want_cert ? "certificate" : "key"); |
813 |
|
|
debug("Found %s in %s:%lu", want_cert ? "CA key" : "key", |
814 |
|
|
host_found->file, host_found->line); |
815 |
|
|
if (want_cert && |
816 |
|
|
!check_host_cert(options.host_key_alias == NULL ? |
817 |
|
|
hostname : options.host_key_alias, host_key)) |
818 |
|
|
goto fail; |
819 |
|
|
if (options.check_host_ip && ip_status == HOST_NEW) { |
820 |
|
|
if (readonly || want_cert) |
821 |
|
|
logit("%s host key for IP address " |
822 |
|
|
"'%.128s' not in list of known hosts.", |
823 |
|
|
type, ip); |
824 |
|
|
else if (!add_host_to_hostfile(user_hostfiles[0], ip, |
825 |
|
|
host_key, options.hash_known_hosts)) |
826 |
|
|
logit("Failed to add the %s host key for IP " |
827 |
|
|
"address '%.128s' to the list of known " |
828 |
|
|
"hosts (%.500s).", type, ip, |
829 |
|
|
user_hostfiles[0]); |
830 |
|
|
else |
831 |
|
|
logit("Warning: Permanently added the %s host " |
832 |
|
|
"key for IP address '%.128s' to the list " |
833 |
|
|
"of known hosts.", type, ip); |
834 |
|
|
} else if (options.visual_host_key) { |
835 |
|
|
fp = sshkey_fingerprint(host_key, |
836 |
|
|
options.fingerprint_hash, SSH_FP_DEFAULT); |
837 |
|
|
ra = sshkey_fingerprint(host_key, |
838 |
|
|
options.fingerprint_hash, SSH_FP_RANDOMART); |
839 |
|
|
if (fp == NULL || ra == NULL) |
840 |
|
|
fatal("%s: sshkey_fingerprint fail", __func__); |
841 |
|
|
logit("Host key fingerprint is %s\n%s", fp, ra); |
842 |
|
|
free(ra); |
843 |
|
|
free(fp); |
844 |
|
|
} |
845 |
|
|
hostkey_trusted = 1; |
846 |
|
|
break; |
847 |
|
|
case HOST_NEW: |
848 |
|
|
if (options.host_key_alias == NULL && port != 0 && |
849 |
|
|
port != SSH_DEFAULT_PORT) { |
850 |
|
|
debug("checking without port identifier"); |
851 |
|
|
if (check_host_key(hostname, hostaddr, 0, host_key, |
852 |
|
|
ROQUIET, user_hostfiles, num_user_hostfiles, |
853 |
|
|
system_hostfiles, num_system_hostfiles) == 0) { |
854 |
|
|
debug("found matching key w/out port"); |
855 |
|
|
break; |
856 |
|
|
} |
857 |
|
|
} |
858 |
|
|
if (readonly || want_cert) |
859 |
|
|
goto fail; |
860 |
|
|
/* The host is new. */ |
861 |
|
|
if (options.strict_host_key_checking == |
862 |
|
|
SSH_STRICT_HOSTKEY_YES) { |
863 |
|
|
/* |
864 |
|
|
* User has requested strict host key checking. We |
865 |
|
|
* will not add the host key automatically. The only |
866 |
|
|
* alternative left is to abort. |
867 |
|
|
*/ |
868 |
|
|
error("No %s host key is known for %.200s and you " |
869 |
|
|
"have requested strict checking.", type, host); |
870 |
|
|
goto fail; |
871 |
|
|
} else if (options.strict_host_key_checking == |
872 |
|
|
SSH_STRICT_HOSTKEY_ASK) { |
873 |
|
|
char msg1[1024], msg2[1024]; |
874 |
|
|
|
875 |
|
|
if (show_other_keys(host_hostkeys, host_key)) |
876 |
|
|
snprintf(msg1, sizeof(msg1), |
877 |
|
|
"\nbut keys of different type are already" |
878 |
|
|
" known for this host."); |
879 |
|
|
else |
880 |
|
|
snprintf(msg1, sizeof(msg1), "."); |
881 |
|
|
/* The default */ |
882 |
|
|
fp = sshkey_fingerprint(host_key, |
883 |
|
|
options.fingerprint_hash, SSH_FP_DEFAULT); |
884 |
|
|
ra = sshkey_fingerprint(host_key, |
885 |
|
|
options.fingerprint_hash, SSH_FP_RANDOMART); |
886 |
|
|
if (fp == NULL || ra == NULL) |
887 |
|
|
fatal("%s: sshkey_fingerprint fail", __func__); |
888 |
|
|
msg2[0] = '\0'; |
889 |
|
|
if (options.verify_host_key_dns) { |
890 |
|
|
if (matching_host_key_dns) |
891 |
|
|
snprintf(msg2, sizeof(msg2), |
892 |
|
|
"Matching host key fingerprint" |
893 |
|
|
" found in DNS.\n"); |
894 |
|
|
else |
895 |
|
|
snprintf(msg2, sizeof(msg2), |
896 |
|
|
"No matching host key fingerprint" |
897 |
|
|
" found in DNS.\n"); |
898 |
|
|
} |
899 |
|
|
snprintf(msg, sizeof(msg), |
900 |
|
|
"The authenticity of host '%.200s (%s)' can't be " |
901 |
|
|
"established%s\n" |
902 |
|
|
"%s key fingerprint is %s.%s%s\n%s" |
903 |
|
|
"Are you sure you want to continue connecting " |
904 |
|
|
"(yes/no)? ", |
905 |
|
|
host, ip, msg1, type, fp, |
906 |
|
|
options.visual_host_key ? "\n" : "", |
907 |
|
|
options.visual_host_key ? ra : "", |
908 |
|
|
msg2); |
909 |
|
|
free(ra); |
910 |
|
|
free(fp); |
911 |
|
|
if (!confirm(msg)) |
912 |
|
|
goto fail; |
913 |
|
|
hostkey_trusted = 1; /* user explicitly confirmed */ |
914 |
|
|
} |
915 |
|
|
/* |
916 |
|
|
* If in "new" or "off" strict mode, add the key automatically |
917 |
|
|
* to the local known_hosts file. |
918 |
|
|
*/ |
919 |
|
|
if (options.check_host_ip && ip_status == HOST_NEW) { |
920 |
|
|
snprintf(hostline, sizeof(hostline), "%s,%s", host, ip); |
921 |
|
|
hostp = hostline; |
922 |
|
|
if (options.hash_known_hosts) { |
923 |
|
|
/* Add hash of host and IP separately */ |
924 |
|
|
r = add_host_to_hostfile(user_hostfiles[0], |
925 |
|
|
host, host_key, options.hash_known_hosts) && |
926 |
|
|
add_host_to_hostfile(user_hostfiles[0], ip, |
927 |
|
|
host_key, options.hash_known_hosts); |
928 |
|
|
} else { |
929 |
|
|
/* Add unhashed "host,ip" */ |
930 |
|
|
r = add_host_to_hostfile(user_hostfiles[0], |
931 |
|
|
hostline, host_key, |
932 |
|
|
options.hash_known_hosts); |
933 |
|
|
} |
934 |
|
|
} else { |
935 |
|
|
r = add_host_to_hostfile(user_hostfiles[0], host, |
936 |
|
|
host_key, options.hash_known_hosts); |
937 |
|
|
hostp = host; |
938 |
|
|
} |
939 |
|
|
|
940 |
|
|
if (!r) |
941 |
|
|
logit("Failed to add the host to the list of known " |
942 |
|
|
"hosts (%.500s).", user_hostfiles[0]); |
943 |
|
|
else |
944 |
|
|
logit("Warning: Permanently added '%.200s' (%s) to the " |
945 |
|
|
"list of known hosts.", hostp, type); |
946 |
|
|
break; |
947 |
|
|
case HOST_REVOKED: |
948 |
|
|
error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); |
949 |
|
|
error("@ WARNING: REVOKED HOST KEY DETECTED! @"); |
950 |
|
|
error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); |
951 |
|
|
error("The %s host key for %s is marked as revoked.", type, host); |
952 |
|
|
error("This could mean that a stolen key is being used to"); |
953 |
|
|
error("impersonate this host."); |
954 |
|
|
|
955 |
|
|
/* |
956 |
|
|
* If strict host key checking is in use, the user will have |
957 |
|
|
* to edit the key manually and we can only abort. |
958 |
|
|
*/ |
959 |
|
|
if (options.strict_host_key_checking != |
960 |
|
|
SSH_STRICT_HOSTKEY_OFF) { |
961 |
|
|
error("%s host key for %.200s was revoked and you have " |
962 |
|
|
"requested strict checking.", type, host); |
963 |
|
|
goto fail; |
964 |
|
|
} |
965 |
|
|
goto continue_unsafe; |
966 |
|
|
|
967 |
|
|
case HOST_CHANGED: |
968 |
|
|
if (want_cert) { |
969 |
|
|
/* |
970 |
|
|
* This is only a debug() since it is valid to have |
971 |
|
|
* CAs with wildcard DNS matches that don't match |
972 |
|
|
* all hosts that one might visit. |
973 |
|
|
*/ |
974 |
|
|
debug("Host certificate authority does not " |
975 |
|
|
"match %s in %s:%lu", CA_MARKER, |
976 |
|
|
host_found->file, host_found->line); |
977 |
|
|
goto fail; |
978 |
|
|
} |
979 |
|
|
if (readonly == ROQUIET) |
980 |
|
|
goto fail; |
981 |
|
|
if (options.check_host_ip && host_ip_differ) { |
982 |
|
|
char *key_msg; |
983 |
|
|
if (ip_status == HOST_NEW) |
984 |
|
|
key_msg = "is unknown"; |
985 |
|
|
else if (ip_status == HOST_OK) |
986 |
|
|
key_msg = "is unchanged"; |
987 |
|
|
else |
988 |
|
|
key_msg = "has a different value"; |
989 |
|
|
error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); |
990 |
|
|
error("@ WARNING: POSSIBLE DNS SPOOFING DETECTED! @"); |
991 |
|
|
error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); |
992 |
|
|
error("The %s host key for %s has changed,", type, host); |
993 |
|
|
error("and the key for the corresponding IP address %s", ip); |
994 |
|
|
error("%s. This could either mean that", key_msg); |
995 |
|
|
error("DNS SPOOFING is happening or the IP address for the host"); |
996 |
|
|
error("and its host key have changed at the same time."); |
997 |
|
|
if (ip_status != HOST_NEW) |
998 |
|
|
error("Offending key for IP in %s:%lu", |
999 |
|
|
ip_found->file, ip_found->line); |
1000 |
|
|
} |
1001 |
|
|
/* The host key has changed. */ |
1002 |
|
|
warn_changed_key(host_key); |
1003 |
|
|
error("Add correct host key in %.100s to get rid of this message.", |
1004 |
|
|
user_hostfiles[0]); |
1005 |
|
|
error("Offending %s key in %s:%lu", |
1006 |
|
|
sshkey_type(host_found->key), |
1007 |
|
|
host_found->file, host_found->line); |
1008 |
|
|
|
1009 |
|
|
/* |
1010 |
|
|
* If strict host key checking is in use, the user will have |
1011 |
|
|
* to edit the key manually and we can only abort. |
1012 |
|
|
*/ |
1013 |
|
|
if (options.strict_host_key_checking != |
1014 |
|
|
SSH_STRICT_HOSTKEY_OFF) { |
1015 |
|
|
error("%s host key for %.200s has changed and you have " |
1016 |
|
|
"requested strict checking.", type, host); |
1017 |
|
|
goto fail; |
1018 |
|
|
} |
1019 |
|
|
|
1020 |
|
|
continue_unsafe: |
1021 |
|
|
/* |
1022 |
|
|
* If strict host key checking has not been requested, allow |
1023 |
|
|
* the connection but without MITM-able authentication or |
1024 |
|
|
* forwarding. |
1025 |
|
|
*/ |
1026 |
|
|
if (options.password_authentication) { |
1027 |
|
|
error("Password authentication is disabled to avoid " |
1028 |
|
|
"man-in-the-middle attacks."); |
1029 |
|
|
options.password_authentication = 0; |
1030 |
|
|
cancelled_forwarding = 1; |
1031 |
|
|
} |
1032 |
|
|
if (options.kbd_interactive_authentication) { |
1033 |
|
|
error("Keyboard-interactive authentication is disabled" |
1034 |
|
|
" to avoid man-in-the-middle attacks."); |
1035 |
|
|
options.kbd_interactive_authentication = 0; |
1036 |
|
|
options.challenge_response_authentication = 0; |
1037 |
|
|
cancelled_forwarding = 1; |
1038 |
|
|
} |
1039 |
|
|
if (options.challenge_response_authentication) { |
1040 |
|
|
error("Challenge/response authentication is disabled" |
1041 |
|
|
" to avoid man-in-the-middle attacks."); |
1042 |
|
|
options.challenge_response_authentication = 0; |
1043 |
|
|
cancelled_forwarding = 1; |
1044 |
|
|
} |
1045 |
|
|
if (options.forward_agent) { |
1046 |
|
|
error("Agent forwarding is disabled to avoid " |
1047 |
|
|
"man-in-the-middle attacks."); |
1048 |
|
|
options.forward_agent = 0; |
1049 |
|
|
cancelled_forwarding = 1; |
1050 |
|
|
} |
1051 |
|
|
if (options.forward_x11) { |
1052 |
|
|
error("X11 forwarding is disabled to avoid " |
1053 |
|
|
"man-in-the-middle attacks."); |
1054 |
|
|
options.forward_x11 = 0; |
1055 |
|
|
cancelled_forwarding = 1; |
1056 |
|
|
} |
1057 |
|
|
if (options.num_local_forwards > 0 || |
1058 |
|
|
options.num_remote_forwards > 0) { |
1059 |
|
|
error("Port forwarding is disabled to avoid " |
1060 |
|
|
"man-in-the-middle attacks."); |
1061 |
|
|
options.num_local_forwards = |
1062 |
|
|
options.num_remote_forwards = 0; |
1063 |
|
|
cancelled_forwarding = 1; |
1064 |
|
|
} |
1065 |
|
|
if (options.tun_open != SSH_TUNMODE_NO) { |
1066 |
|
|
error("Tunnel forwarding is disabled to avoid " |
1067 |
|
|
"man-in-the-middle attacks."); |
1068 |
|
|
options.tun_open = SSH_TUNMODE_NO; |
1069 |
|
|
cancelled_forwarding = 1; |
1070 |
|
|
} |
1071 |
|
|
if (options.exit_on_forward_failure && cancelled_forwarding) |
1072 |
|
|
fatal("Error: forwarding disabled due to host key " |
1073 |
|
|
"check failure"); |
1074 |
|
|
|
1075 |
|
|
/* |
1076 |
|
|
* XXX Should permit the user to change to use the new id. |
1077 |
|
|
* This could be done by converting the host key to an |
1078 |
|
|
* identifying sentence, tell that the host identifies itself |
1079 |
|
|
* by that sentence, and ask the user if he/she wishes to |
1080 |
|
|
* accept the authentication. |
1081 |
|
|
*/ |
1082 |
|
|
break; |
1083 |
|
|
case HOST_FOUND: |
1084 |
|
|
fatal("internal error"); |
1085 |
|
|
break; |
1086 |
|
|
} |
1087 |
|
|
|
1088 |
|
|
if (options.check_host_ip && host_status != HOST_CHANGED && |
1089 |
|
|
ip_status == HOST_CHANGED) { |
1090 |
|
|
snprintf(msg, sizeof(msg), |
1091 |
|
|
"Warning: the %s host key for '%.200s' " |
1092 |
|
|
"differs from the key for the IP address '%.128s'" |
1093 |
|
|
"\nOffending key for IP in %s:%lu", |
1094 |
|
|
type, host, ip, ip_found->file, ip_found->line); |
1095 |
|
|
if (host_status == HOST_OK) { |
1096 |
|
|
len = strlen(msg); |
1097 |
|
|
snprintf(msg + len, sizeof(msg) - len, |
1098 |
|
|
"\nMatching host key in %s:%lu", |
1099 |
|
|
host_found->file, host_found->line); |
1100 |
|
|
} |
1101 |
|
|
if (options.strict_host_key_checking == |
1102 |
|
|
SSH_STRICT_HOSTKEY_ASK) { |
1103 |
|
|
strlcat(msg, "\nAre you sure you want " |
1104 |
|
|
"to continue connecting (yes/no)? ", sizeof(msg)); |
1105 |
|
|
if (!confirm(msg)) |
1106 |
|
|
goto fail; |
1107 |
|
|
} else if (options.strict_host_key_checking != |
1108 |
|
|
SSH_STRICT_HOSTKEY_OFF) { |
1109 |
|
|
logit("%s", msg); |
1110 |
|
|
error("Exiting, you have requested strict checking."); |
1111 |
|
|
goto fail; |
1112 |
|
|
} else { |
1113 |
|
|
logit("%s", msg); |
1114 |
|
|
} |
1115 |
|
|
} |
1116 |
|
|
|
1117 |
|
|
if (!hostkey_trusted && options.update_hostkeys) { |
1118 |
|
|
debug("%s: hostkey not known or explicitly trusted: " |
1119 |
|
|
"disabling UpdateHostkeys", __func__); |
1120 |
|
|
options.update_hostkeys = 0; |
1121 |
|
|
} |
1122 |
|
|
|
1123 |
|
|
free(ip); |
1124 |
|
|
free(host); |
1125 |
|
|
if (host_hostkeys != NULL) |
1126 |
|
|
free_hostkeys(host_hostkeys); |
1127 |
|
|
if (ip_hostkeys != NULL) |
1128 |
|
|
free_hostkeys(ip_hostkeys); |
1129 |
|
|
return 0; |
1130 |
|
|
|
1131 |
|
|
fail: |
1132 |
|
|
if (want_cert && host_status != HOST_REVOKED) { |
1133 |
|
|
/* |
1134 |
|
|
* No matching certificate. Downgrade cert to raw key and |
1135 |
|
|
* search normally. |
1136 |
|
|
*/ |
1137 |
|
|
debug("No matching CA found. Retry with plain key"); |
1138 |
|
|
if ((r = sshkey_from_private(host_key, &raw_key)) != 0) |
1139 |
|
|
fatal("%s: sshkey_from_private: %s", |
1140 |
|
|
__func__, ssh_err(r)); |
1141 |
|
|
if ((r = sshkey_drop_cert(raw_key)) != 0) |
1142 |
|
|
fatal("Couldn't drop certificate: %s", ssh_err(r)); |
1143 |
|
|
host_key = raw_key; |
1144 |
|
|
goto retry; |
1145 |
|
|
} |
1146 |
|
|
if (raw_key != NULL) |
1147 |
|
|
sshkey_free(raw_key); |
1148 |
|
|
free(ip); |
1149 |
|
|
free(host); |
1150 |
|
|
if (host_hostkeys != NULL) |
1151 |
|
|
free_hostkeys(host_hostkeys); |
1152 |
|
|
if (ip_hostkeys != NULL) |
1153 |
|
|
free_hostkeys(ip_hostkeys); |
1154 |
|
|
return -1; |
1155 |
|
|
} |
1156 |
|
|
|
1157 |
|
|
/* returns 0 if key verifies or -1 if key does NOT verify */ |
1158 |
|
|
int |
1159 |
|
|
verify_host_key(char *host, struct sockaddr *hostaddr, struct sshkey *host_key) |
1160 |
|
|
{ |
1161 |
|
|
u_int i; |
1162 |
|
|
int r = -1, flags = 0; |
1163 |
|
|
char valid[64], *fp = NULL, *cafp = NULL; |
1164 |
|
|
struct sshkey *plain = NULL; |
1165 |
|
|
|
1166 |
|
|
if ((fp = sshkey_fingerprint(host_key, |
1167 |
|
|
options.fingerprint_hash, SSH_FP_DEFAULT)) == NULL) { |
1168 |
|
|
error("%s: fingerprint host key: %s", __func__, ssh_err(r)); |
1169 |
|
|
r = -1; |
1170 |
|
|
goto out; |
1171 |
|
|
} |
1172 |
|
|
|
1173 |
|
|
if (sshkey_is_cert(host_key)) { |
1174 |
|
|
if ((cafp = sshkey_fingerprint(host_key->cert->signature_key, |
1175 |
|
|
options.fingerprint_hash, SSH_FP_DEFAULT)) == NULL) { |
1176 |
|
|
error("%s: fingerprint CA key: %s", |
1177 |
|
|
__func__, ssh_err(r)); |
1178 |
|
|
r = -1; |
1179 |
|
|
goto out; |
1180 |
|
|
} |
1181 |
|
|
sshkey_format_cert_validity(host_key->cert, |
1182 |
|
|
valid, sizeof(valid)); |
1183 |
|
|
debug("Server host certificate: %s %s, serial %llu " |
1184 |
|
|
"ID \"%s\" CA %s %s valid %s", |
1185 |
|
|
sshkey_ssh_name(host_key), fp, |
1186 |
|
|
(unsigned long long)host_key->cert->serial, |
1187 |
|
|
host_key->cert->key_id, |
1188 |
|
|
sshkey_ssh_name(host_key->cert->signature_key), cafp, |
1189 |
|
|
valid); |
1190 |
|
|
for (i = 0; i < host_key->cert->nprincipals; i++) { |
1191 |
|
|
debug2("Server host certificate hostname: %s", |
1192 |
|
|
host_key->cert->principals[i]); |
1193 |
|
|
} |
1194 |
|
|
} else { |
1195 |
|
|
debug("Server host key: %s %s", sshkey_ssh_name(host_key), fp); |
1196 |
|
|
} |
1197 |
|
|
|
1198 |
|
|
if (sshkey_equal(previous_host_key, host_key)) { |
1199 |
|
|
debug2("%s: server host key %s %s matches cached key", |
1200 |
|
|
__func__, sshkey_type(host_key), fp); |
1201 |
|
|
r = 0; |
1202 |
|
|
goto out; |
1203 |
|
|
} |
1204 |
|
|
|
1205 |
|
|
/* Check in RevokedHostKeys file if specified */ |
1206 |
|
|
if (options.revoked_host_keys != NULL) { |
1207 |
|
|
r = sshkey_check_revoked(host_key, options.revoked_host_keys); |
1208 |
|
|
switch (r) { |
1209 |
|
|
case 0: |
1210 |
|
|
break; /* not revoked */ |
1211 |
|
|
case SSH_ERR_KEY_REVOKED: |
1212 |
|
|
error("Host key %s %s revoked by file %s", |
1213 |
|
|
sshkey_type(host_key), fp, |
1214 |
|
|
options.revoked_host_keys); |
1215 |
|
|
r = -1; |
1216 |
|
|
goto out; |
1217 |
|
|
default: |
1218 |
|
|
error("Error checking host key %s %s in " |
1219 |
|
|
"revoked keys file %s: %s", sshkey_type(host_key), |
1220 |
|
|
fp, options.revoked_host_keys, ssh_err(r)); |
1221 |
|
|
r = -1; |
1222 |
|
|
goto out; |
1223 |
|
|
} |
1224 |
|
|
} |
1225 |
|
|
|
1226 |
|
|
if (options.verify_host_key_dns) { |
1227 |
|
|
/* |
1228 |
|
|
* XXX certs are not yet supported for DNS, so downgrade |
1229 |
|
|
* them and try the plain key. |
1230 |
|
|
*/ |
1231 |
|
|
if ((r = sshkey_from_private(host_key, &plain)) != 0) |
1232 |
|
|
goto out; |
1233 |
|
|
if (sshkey_is_cert(plain)) |
1234 |
|
|
sshkey_drop_cert(plain); |
1235 |
|
|
if (verify_host_key_dns(host, hostaddr, plain, &flags) == 0) { |
1236 |
|
|
if (flags & DNS_VERIFY_FOUND) { |
1237 |
|
|
if (options.verify_host_key_dns == 1 && |
1238 |
|
|
flags & DNS_VERIFY_MATCH && |
1239 |
|
|
flags & DNS_VERIFY_SECURE) { |
1240 |
|
|
r = 0; |
1241 |
|
|
goto out; |
1242 |
|
|
} |
1243 |
|
|
if (flags & DNS_VERIFY_MATCH) { |
1244 |
|
|
matching_host_key_dns = 1; |
1245 |
|
|
} else { |
1246 |
|
|
warn_changed_key(plain); |
1247 |
|
|
error("Update the SSHFP RR in DNS " |
1248 |
|
|
"with the new host key to get rid " |
1249 |
|
|
"of this message."); |
1250 |
|
|
} |
1251 |
|
|
} |
1252 |
|
|
} |
1253 |
|
|
} |
1254 |
|
|
r = check_host_key(host, hostaddr, options.port, host_key, RDRW, |
1255 |
|
|
options.user_hostfiles, options.num_user_hostfiles, |
1256 |
|
|
options.system_hostfiles, options.num_system_hostfiles); |
1257 |
|
|
|
1258 |
|
|
out: |
1259 |
|
|
sshkey_free(plain); |
1260 |
|
|
free(fp); |
1261 |
|
|
free(cafp); |
1262 |
|
|
if (r == 0 && host_key != NULL) { |
1263 |
|
|
sshkey_free(previous_host_key); |
1264 |
|
|
r = sshkey_from_private(host_key, &previous_host_key); |
1265 |
|
|
} |
1266 |
|
|
|
1267 |
|
|
return r; |
1268 |
|
|
} |
1269 |
|
|
|
1270 |
|
|
/* |
1271 |
|
|
* Starts a dialog with the server, and authenticates the current user on the |
1272 |
|
|
* server. This does not need any extra privileges. The basic connection |
1273 |
|
|
* to the server must already have been established before this is called. |
1274 |
|
|
* If login fails, this function prints an error and never returns. |
1275 |
|
|
* This function does not require super-user privileges. |
1276 |
|
|
*/ |
1277 |
|
|
void |
1278 |
|
|
ssh_login(Sensitive *sensitive, const char *orighost, |
1279 |
|
|
struct sockaddr *hostaddr, u_short port, struct passwd *pw, int timeout_ms) |
1280 |
|
|
{ |
1281 |
|
|
char *host; |
1282 |
|
|
char *server_user, *local_user; |
1283 |
|
|
|
1284 |
|
|
local_user = xstrdup(pw->pw_name); |
1285 |
|
|
server_user = options.user ? options.user : local_user; |
1286 |
|
|
|
1287 |
|
|
/* Convert the user-supplied hostname into all lowercase. */ |
1288 |
|
|
host = xstrdup(orighost); |
1289 |
|
|
lowercase(host); |
1290 |
|
|
|
1291 |
|
|
/* Exchange protocol version identification strings with the server. */ |
1292 |
|
|
ssh_exchange_identification(timeout_ms); |
1293 |
|
|
|
1294 |
|
|
/* Put the connection into non-blocking mode. */ |
1295 |
|
|
packet_set_nonblocking(); |
1296 |
|
|
|
1297 |
|
|
/* key exchange */ |
1298 |
|
|
/* authenticate user */ |
1299 |
|
|
debug("Authenticating to %s:%d as '%s'", host, port, server_user); |
1300 |
|
|
ssh_kex2(host, hostaddr, port); |
1301 |
|
|
ssh_userauth2(local_user, server_user, host, sensitive); |
1302 |
|
|
free(local_user); |
1303 |
|
|
} |
1304 |
|
|
|
1305 |
|
|
void |
1306 |
|
|
ssh_put_password(char *password) |
1307 |
|
|
{ |
1308 |
|
|
int size; |
1309 |
|
|
char *padded; |
1310 |
|
|
|
1311 |
|
|
if (datafellows & SSH_BUG_PASSWORDPAD) { |
1312 |
|
|
packet_put_cstring(password); |
1313 |
|
|
return; |
1314 |
|
|
} |
1315 |
|
|
size = ROUNDUP(strlen(password) + 1, 32); |
1316 |
|
|
padded = xcalloc(1, size); |
1317 |
|
|
strlcpy(padded, password, size); |
1318 |
|
|
packet_put_string(padded, size); |
1319 |
|
|
explicit_bzero(padded, size); |
1320 |
|
|
free(padded); |
1321 |
|
|
} |
1322 |
|
|
|
1323 |
|
|
/* print all known host keys for a given host, but skip keys of given type */ |
1324 |
|
|
static int |
1325 |
|
|
show_other_keys(struct hostkeys *hostkeys, struct sshkey *key) |
1326 |
|
|
{ |
1327 |
|
|
int type[] = { |
1328 |
|
|
KEY_RSA, |
1329 |
|
|
KEY_DSA, |
1330 |
|
|
KEY_ECDSA, |
1331 |
|
|
KEY_ED25519, |
1332 |
|
|
-1 |
1333 |
|
|
}; |
1334 |
|
|
int i, ret = 0; |
1335 |
|
|
char *fp, *ra; |
1336 |
|
|
const struct hostkey_entry *found; |
1337 |
|
|
|
1338 |
|
|
for (i = 0; type[i] != -1; i++) { |
1339 |
|
|
if (type[i] == key->type) |
1340 |
|
|
continue; |
1341 |
|
|
if (!lookup_key_in_hostkeys_by_type(hostkeys, type[i], &found)) |
1342 |
|
|
continue; |
1343 |
|
|
fp = sshkey_fingerprint(found->key, |
1344 |
|
|
options.fingerprint_hash, SSH_FP_DEFAULT); |
1345 |
|
|
ra = sshkey_fingerprint(found->key, |
1346 |
|
|
options.fingerprint_hash, SSH_FP_RANDOMART); |
1347 |
|
|
if (fp == NULL || ra == NULL) |
1348 |
|
|
fatal("%s: sshkey_fingerprint fail", __func__); |
1349 |
|
|
logit("WARNING: %s key found for host %s\n" |
1350 |
|
|
"in %s:%lu\n" |
1351 |
|
|
"%s key fingerprint %s.", |
1352 |
|
|
key_type(found->key), |
1353 |
|
|
found->host, found->file, found->line, |
1354 |
|
|
key_type(found->key), fp); |
1355 |
|
|
if (options.visual_host_key) |
1356 |
|
|
logit("%s", ra); |
1357 |
|
|
free(ra); |
1358 |
|
|
free(fp); |
1359 |
|
|
ret = 1; |
1360 |
|
|
} |
1361 |
|
|
return ret; |
1362 |
|
|
} |
1363 |
|
|
|
1364 |
|
|
static void |
1365 |
|
|
warn_changed_key(struct sshkey *host_key) |
1366 |
|
|
{ |
1367 |
|
|
char *fp; |
1368 |
|
|
|
1369 |
|
|
fp = sshkey_fingerprint(host_key, options.fingerprint_hash, |
1370 |
|
|
SSH_FP_DEFAULT); |
1371 |
|
|
if (fp == NULL) |
1372 |
|
|
fatal("%s: sshkey_fingerprint fail", __func__); |
1373 |
|
|
|
1374 |
|
|
error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); |
1375 |
|
|
error("@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @"); |
1376 |
|
|
error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); |
1377 |
|
|
error("IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!"); |
1378 |
|
|
error("Someone could be eavesdropping on you right now (man-in-the-middle attack)!"); |
1379 |
|
|
error("It is also possible that a host key has just been changed."); |
1380 |
|
|
error("The fingerprint for the %s key sent by the remote host is\n%s.", |
1381 |
|
|
key_type(host_key), fp); |
1382 |
|
|
error("Please contact your system administrator."); |
1383 |
|
|
|
1384 |
|
|
free(fp); |
1385 |
|
|
} |
1386 |
|
|
|
1387 |
|
|
/* |
1388 |
|
|
* Execute a local command |
1389 |
|
|
*/ |
1390 |
|
|
int |
1391 |
|
|
ssh_local_cmd(const char *args) |
1392 |
|
|
{ |
1393 |
|
|
char *shell; |
1394 |
|
|
pid_t pid; |
1395 |
|
|
int status; |
1396 |
|
|
void (*osighand)(int); |
1397 |
|
|
|
1398 |
|
|
if (!options.permit_local_command || |
1399 |
|
|
args == NULL || !*args) |
1400 |
|
|
return (1); |
1401 |
|
|
|
1402 |
|
|
if ((shell = getenv("SHELL")) == NULL || *shell == '\0') |
1403 |
|
|
shell = _PATH_BSHELL; |
1404 |
|
|
|
1405 |
|
|
osighand = signal(SIGCHLD, SIG_DFL); |
1406 |
|
|
pid = fork(); |
1407 |
|
|
if (pid == 0) { |
1408 |
|
|
signal(SIGPIPE, SIG_DFL); |
1409 |
|
|
debug3("Executing %s -c \"%s\"", shell, args); |
1410 |
|
|
execl(shell, shell, "-c", args, (char *)NULL); |
1411 |
|
|
error("Couldn't execute %s -c \"%s\": %s", |
1412 |
|
|
shell, args, strerror(errno)); |
1413 |
|
|
_exit(1); |
1414 |
|
|
} else if (pid == -1) |
1415 |
|
|
fatal("fork failed: %.100s", strerror(errno)); |
1416 |
|
|
while (waitpid(pid, &status, 0) == -1) |
1417 |
|
|
if (errno != EINTR) |
1418 |
|
|
fatal("Couldn't wait for child: %s", strerror(errno)); |
1419 |
|
|
signal(SIGCHLD, osighand); |
1420 |
|
|
|
1421 |
|
|
if (!WIFEXITED(status)) |
1422 |
|
|
return (1); |
1423 |
|
|
|
1424 |
|
|
return (WEXITSTATUS(status)); |
1425 |
|
|
} |
1426 |
|
|
|
1427 |
|
|
void |
1428 |
|
|
maybe_add_key_to_agent(char *authfile, struct sshkey *private, char *comment, |
1429 |
|
|
char *passphrase) |
1430 |
|
|
{ |
1431 |
|
|
int auth_sock = -1, r; |
1432 |
|
|
|
1433 |
|
|
if (options.add_keys_to_agent == 0) |
1434 |
|
|
return; |
1435 |
|
|
|
1436 |
|
|
if ((r = ssh_get_authentication_socket(&auth_sock)) != 0) { |
1437 |
|
|
debug3("no authentication agent, not adding key"); |
1438 |
|
|
return; |
1439 |
|
|
} |
1440 |
|
|
|
1441 |
|
|
if (options.add_keys_to_agent == 2 && |
1442 |
|
|
!ask_permission("Add key %s (%s) to agent?", authfile, comment)) { |
1443 |
|
|
debug3("user denied adding this key"); |
1444 |
|
|
close(auth_sock); |
1445 |
|
|
return; |
1446 |
|
|
} |
1447 |
|
|
|
1448 |
|
|
if ((r = ssh_add_identity_constrained(auth_sock, private, comment, 0, |
1449 |
|
|
(options.add_keys_to_agent == 3))) == 0) |
1450 |
|
|
debug("identity added to agent: %s", authfile); |
1451 |
|
|
else |
1452 |
|
|
debug("could not add identity to agent: %s (%d)", authfile, r); |
1453 |
|
|
close(auth_sock); |
1454 |
|
|
} |