1296781Sdes/* $OpenBSD: sshconnect.c,v 1.271 2016/01/14 22:56:56 markus Exp $ */
257429Smarkm/*
357429Smarkm * Author: Tatu Ylonen <ylo@cs.hut.fi>
457429Smarkm * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
557429Smarkm *                    All rights reserved
657429Smarkm * Code to connect to a remote host, and to perform the client side of the
757429Smarkm * login (authentication) dialog.
865674Skris *
965674Skris * As far as I am concerned, the code I have written for this software
1065674Skris * can be used freely for any purpose.  Any derived versions of this
1165674Skris * software must be clearly marked as such, and if the derived work is
1265674Skris * incompatible with the protocol description in the RFC file, it must be
1365674Skris * called by a name other than "ssh" or "Secure Shell".
1457429Smarkm */
1557429Smarkm
1657429Smarkm#include "includes.h"
17294666Sdes__RCSID("$FreeBSD$");
1857429Smarkm
19295367Sdes#include <sys/param.h>	/* roundup */
20162856Sdes#include <sys/types.h>
21162856Sdes#include <sys/wait.h>
22162856Sdes#include <sys/stat.h>
23162856Sdes#include <sys/socket.h>
24162856Sdes#ifdef HAVE_SYS_TIME_H
25162856Sdes# include <sys/time.h>
26162856Sdes#endif
2760576Skris
28162856Sdes#include <netinet/in.h>
29162856Sdes#include <arpa/inet.h>
30262566Sdes#include <rpc/rpc.h>
31162856Sdes
32162856Sdes#include <ctype.h>
33162856Sdes#include <errno.h>
34204917Sdes#include <fcntl.h>
35162856Sdes#include <netdb.h>
36162856Sdes#ifdef HAVE_PATHS_H
37162856Sdes#include <paths.h>
38162856Sdes#endif
39162856Sdes#include <pwd.h>
40221420Sdes#include <signal.h>
41162856Sdes#include <stdarg.h>
42162856Sdes#include <stdio.h>
43162856Sdes#include <stdlib.h>
44162856Sdes#include <string.h>
45162856Sdes#include <unistd.h>
46162856Sdes
47162856Sdes#include "xmalloc.h"
48162856Sdes#include "key.h"
49162856Sdes#include "hostfile.h"
5076262Sgreen#include "ssh.h"
5157429Smarkm#include "rsa.h"
5260576Skris#include "buffer.h"
5357429Smarkm#include "packet.h"
5457429Smarkm#include "uidswap.h"
5557429Smarkm#include "compat.h"
5658585Skris#include "key.h"
5760576Skris#include "sshconnect.h"
5858585Skris#include "hostfile.h"
5976262Sgreen#include "log.h"
60295367Sdes#include "misc.h"
6176262Sgreen#include "readconf.h"
6276262Sgreen#include "atomicio.h"
63124211Sdes#include "dns.h"
64262566Sdes#include "monitor_fdpass.h"
65204917Sdes#include "ssh2.h"
66162856Sdes#include "version.h"
67295367Sdes#include "authfile.h"
68295367Sdes#include "ssherr.h"
69296781Sdes#include "authfd.h"
70124211Sdes
7160576Skrischar *client_version_string = NULL;
7260576Skrischar *server_version_string = NULL;
73295367SdesKey *previous_host_key = NULL;
7457429Smarkm
75157019Sdesstatic int matching_host_key_dns = 0;
76124211Sdes
77221420Sdesstatic pid_t proxy_command_pid = 0;
78221420Sdes
7998684Sdes/* import */
8057429Smarkmextern Options options;
8157429Smarkmextern char *__progname;
8298684Sdesextern uid_t original_real_uid;
8398684Sdesextern uid_t original_effective_uid;
8457429Smarkm
85221420Sdesstatic int show_other_keys(struct hostkeys *, Key *);
86126277Sdesstatic void warn_changed_key(Key *);
8776262Sgreen
88262566Sdes/* Expand a proxy command */
89262566Sdesstatic char *
90262566Sdesexpand_proxy_command(const char *proxy_command, const char *user,
91262566Sdes    const char *host, int port)
92262566Sdes{
93262566Sdes	char *tmp, *ret, strport[NI_MAXSERV];
94262566Sdes
95262566Sdes	snprintf(strport, sizeof strport, "%d", port);
96262566Sdes	xasprintf(&tmp, "exec %s", proxy_command);
97262566Sdes	ret = percent_expand(tmp, "h", host, "p", strport,
98262566Sdes	    "r", options.user, (char *)NULL);
99262566Sdes	free(tmp);
100262566Sdes	return ret;
101262566Sdes}
102262566Sdes
10357429Smarkm/*
104262566Sdes * Connect to the given ssh server using a proxy command that passes a
105262566Sdes * a connected fd back to us.
106262566Sdes */
107262566Sdesstatic int
108262566Sdesssh_proxy_fdpass_connect(const char *host, u_short port,
109262566Sdes    const char *proxy_command)
110262566Sdes{
111262566Sdes	char *command_string;
112262566Sdes	int sp[2], sock;
113262566Sdes	pid_t pid;
114262566Sdes	char *shell;
115262566Sdes
116262566Sdes	if ((shell = getenv("SHELL")) == NULL)
117262566Sdes		shell = _PATH_BSHELL;
118262566Sdes
119262566Sdes	if (socketpair(AF_UNIX, SOCK_STREAM, 0, sp) < 0)
120262566Sdes		fatal("Could not create socketpair to communicate with "
121262566Sdes		    "proxy dialer: %.100s", strerror(errno));
122262566Sdes
123262566Sdes	command_string = expand_proxy_command(proxy_command, options.user,
124262566Sdes	    host, port);
125262566Sdes	debug("Executing proxy dialer command: %.500s", command_string);
126262566Sdes
127262566Sdes	/* Fork and execute the proxy command. */
128262566Sdes	if ((pid = fork()) == 0) {
129262566Sdes		char *argv[10];
130262566Sdes
131262566Sdes		/* Child.  Permanently give up superuser privileges. */
132262566Sdes		permanently_drop_suid(original_real_uid);
133262566Sdes
134262566Sdes		close(sp[1]);
135262566Sdes		/* Redirect stdin and stdout. */
136262566Sdes		if (sp[0] != 0) {
137262566Sdes			if (dup2(sp[0], 0) < 0)
138262566Sdes				perror("dup2 stdin");
139262566Sdes		}
140262566Sdes		if (sp[0] != 1) {
141262566Sdes			if (dup2(sp[0], 1) < 0)
142262566Sdes				perror("dup2 stdout");
143262566Sdes		}
144262566Sdes		if (sp[0] >= 2)
145262566Sdes			close(sp[0]);
146262566Sdes
147262566Sdes		/*
148262566Sdes		 * Stderr is left as it is so that error messages get
149262566Sdes		 * printed on the user's terminal.
150262566Sdes		 */
151262566Sdes		argv[0] = shell;
152262566Sdes		argv[1] = "-c";
153262566Sdes		argv[2] = command_string;
154262566Sdes		argv[3] = NULL;
155262566Sdes
156262566Sdes		/*
157262566Sdes		 * Execute the proxy command.
158262566Sdes		 * Note that we gave up any extra privileges above.
159262566Sdes		 */
160262566Sdes		execv(argv[0], argv);
161262566Sdes		perror(argv[0]);
162262566Sdes		exit(1);
163262566Sdes	}
164262566Sdes	/* Parent. */
165262566Sdes	if (pid < 0)
166262566Sdes		fatal("fork failed: %.100s", strerror(errno));
167262566Sdes	close(sp[0]);
168262566Sdes	free(command_string);
169262566Sdes
170262566Sdes	if ((sock = mm_receive_fd(sp[1])) == -1)
171262566Sdes		fatal("proxy dialer did not pass back a connection");
172296781Sdes	close(sp[1]);
173262566Sdes
174262566Sdes	while (waitpid(pid, NULL, 0) == -1)
175262566Sdes		if (errno != EINTR)
176262566Sdes			fatal("Couldn't wait for child: %s", strerror(errno));
177262566Sdes
178262566Sdes	/* Set the connection file descriptors. */
179262566Sdes	packet_set_connection(sock, sock);
180262566Sdes
181262566Sdes	return 0;
182262566Sdes}
183262566Sdes
184262566Sdes/*
18557429Smarkm * Connect to the given ssh server using a proxy command.
18657429Smarkm */
18792559Sdesstatic int
18898684Sdesssh_proxy_connect(const char *host, u_short port, const char *proxy_command)
18957429Smarkm{
190262566Sdes	char *command_string;
19157429Smarkm	int pin[2], pout[2];
19260576Skris	pid_t pid;
193262566Sdes	char *shell;
19457429Smarkm
195221420Sdes	if ((shell = getenv("SHELL")) == NULL || *shell == '\0')
196181111Sdes		shell = _PATH_BSHELL;
197181111Sdes
19857429Smarkm	/* Create pipes for communicating with the proxy. */
19957429Smarkm	if (pipe(pin) < 0 || pipe(pout) < 0)
20057429Smarkm		fatal("Could not create pipes to communicate with the proxy: %.100s",
20192559Sdes		    strerror(errno));
20257429Smarkm
203262566Sdes	command_string = expand_proxy_command(proxy_command, options.user,
204262566Sdes	    host, port);
20557429Smarkm	debug("Executing proxy command: %.500s", command_string);
20657429Smarkm
20757429Smarkm	/* Fork and execute the proxy command. */
20857429Smarkm	if ((pid = fork()) == 0) {
20957429Smarkm		char *argv[10];
21057429Smarkm
21157429Smarkm		/* Child.  Permanently give up superuser privileges. */
212162856Sdes		permanently_drop_suid(original_real_uid);
21357429Smarkm
21457429Smarkm		/* Redirect stdin and stdout. */
21557429Smarkm		close(pin[1]);
21657429Smarkm		if (pin[0] != 0) {
21757429Smarkm			if (dup2(pin[0], 0) < 0)
21857429Smarkm				perror("dup2 stdin");
21957429Smarkm			close(pin[0]);
22057429Smarkm		}
22157429Smarkm		close(pout[0]);
22257429Smarkm		if (dup2(pout[1], 1) < 0)
22357429Smarkm			perror("dup2 stdout");
22457429Smarkm		/* Cannot be 1 because pin allocated two descriptors. */
22557429Smarkm		close(pout[1]);
22657429Smarkm
22757429Smarkm		/* Stderr is left as it is so that error messages get
22857429Smarkm		   printed on the user's terminal. */
229181111Sdes		argv[0] = shell;
23057429Smarkm		argv[1] = "-c";
23157429Smarkm		argv[2] = command_string;
23257429Smarkm		argv[3] = NULL;
23357429Smarkm
23457429Smarkm		/* Execute the proxy command.  Note that we gave up any
23557429Smarkm		   extra privileges above. */
236221420Sdes		signal(SIGPIPE, SIG_DFL);
23776262Sgreen		execv(argv[0], argv);
23876262Sgreen		perror(argv[0]);
23957429Smarkm		exit(1);
24057429Smarkm	}
24157429Smarkm	/* Parent. */
24257429Smarkm	if (pid < 0)
24357429Smarkm		fatal("fork failed: %.100s", strerror(errno));
244106130Sdes	else
245106130Sdes		proxy_command_pid = pid; /* save pid to clean up later */
24657429Smarkm
24757429Smarkm	/* Close child side of the descriptors. */
24857429Smarkm	close(pin[0]);
24957429Smarkm	close(pout[1]);
25057429Smarkm
25157429Smarkm	/* Free the command name. */
252255767Sdes	free(command_string);
25357429Smarkm
25457429Smarkm	/* Set the connection file descriptors. */
25557429Smarkm	packet_set_connection(pout[0], pin[1]);
25657429Smarkm
25792559Sdes	/* Indicate OK return */
25892559Sdes	return 0;
25957429Smarkm}
26057429Smarkm
261221420Sdesvoid
262221420Sdesssh_kill_proxy_command(void)
263221420Sdes{
264221420Sdes	/*
265221420Sdes	 * Send SIGHUP to proxy command if used. We don't wait() in
266221420Sdes	 * case it hangs and instead rely on init to reap the child
267221420Sdes	 */
268221420Sdes	if (proxy_command_pid > 1)
269221420Sdes		kill(proxy_command_pid, SIGHUP);
270221420Sdes}
271221420Sdes
27257429Smarkm/*
27357429Smarkm * Creates a (possibly privileged) socket for use as the ssh connection.
27457429Smarkm */
27592559Sdesstatic int
276124211Sdesssh_create_socket(int privileged, struct addrinfo *ai)
27757429Smarkm{
278262566Sdes	int sock, r, gaierr;
279264377Sdes	struct addrinfo hints, *res = NULL;
28057429Smarkm
281124211Sdes	sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
282204917Sdes	if (sock < 0) {
283262566Sdes		error("socket: %s", strerror(errno));
284204917Sdes		return -1;
285204917Sdes	}
286204917Sdes	fcntl(sock, F_SETFD, FD_CLOEXEC);
28792559Sdes
28892559Sdes	/* Bind the socket to an alternative local IP address */
289262566Sdes	if (options.bind_address == NULL && !privileged)
29092559Sdes		return sock;
29192559Sdes
292264377Sdes	if (options.bind_address) {
293264377Sdes		memset(&hints, 0, sizeof(hints));
294264377Sdes		hints.ai_family = ai->ai_family;
295264377Sdes		hints.ai_socktype = ai->ai_socktype;
296264377Sdes		hints.ai_protocol = ai->ai_protocol;
297264377Sdes		hints.ai_flags = AI_PASSIVE;
298264377Sdes		gaierr = getaddrinfo(options.bind_address, NULL, &hints, &res);
299264377Sdes		if (gaierr) {
300264377Sdes			error("getaddrinfo: %s: %s", options.bind_address,
301264377Sdes			    ssh_gai_strerror(gaierr));
302264377Sdes			close(sock);
303264377Sdes			return -1;
304264377Sdes		}
30592559Sdes	}
306262566Sdes	/*
307262566Sdes	 * If we are running as root and want to connect to a privileged
308262566Sdes	 * port, bind our own socket to a privileged port.
309262566Sdes	 */
310262566Sdes	if (privileged) {
311262566Sdes		PRIV_START;
312264377Sdes		r = bindresvport_sa(sock, res ? res->ai_addr : NULL);
313262566Sdes		PRIV_END;
314262566Sdes		if (r < 0) {
315262566Sdes			error("bindresvport_sa: af=%d %s", ai->ai_family,
316262566Sdes			    strerror(errno));
317262566Sdes			goto fail;
318262566Sdes		}
319262566Sdes	} else {
320262566Sdes		if (bind(sock, res->ai_addr, res->ai_addrlen) < 0) {
321262566Sdes			error("bind: %s: %s", options.bind_address,
322262566Sdes			    strerror(errno));
323262566Sdes fail:
324262566Sdes			close(sock);
325262566Sdes			freeaddrinfo(res);
326262566Sdes			return -1;
327262566Sdes		}
32892559Sdes	}
329264377Sdes	if (res != NULL)
330264377Sdes		freeaddrinfo(res);
33157429Smarkm	return sock;
33257429Smarkm}
33357429Smarkm
334124211Sdesstatic int
335124211Sdestimeout_connect(int sockfd, const struct sockaddr *serv_addr,
336181111Sdes    socklen_t addrlen, int *timeoutp)
337124211Sdes{
338124211Sdes	fd_set *fdset;
339181111Sdes	struct timeval tv, t_start;
340124211Sdes	socklen_t optlen;
341162856Sdes	int optval, rc, result = -1;
342124211Sdes
343181111Sdes	gettimeofday(&t_start, NULL);
344124211Sdes
345181111Sdes	if (*timeoutp <= 0) {
346181111Sdes		result = connect(sockfd, serv_addr, addrlen);
347181111Sdes		goto done;
348181111Sdes	}
349181111Sdes
350126277Sdes	set_nonblock(sockfd);
351124211Sdes	rc = connect(sockfd, serv_addr, addrlen);
352126277Sdes	if (rc == 0) {
353126277Sdes		unset_nonblock(sockfd);
354181111Sdes		result = 0;
355181111Sdes		goto done;
356126277Sdes	}
357181111Sdes	if (errno != EINPROGRESS) {
358181111Sdes		result = -1;
359181111Sdes		goto done;
360181111Sdes	}
361124211Sdes
362295367Sdes	fdset = xcalloc(howmany(sockfd + 1, NFDBITS),
363162856Sdes	    sizeof(fd_mask));
364124211Sdes	FD_SET(sockfd, fdset);
365181111Sdes	ms_to_timeval(&tv, *timeoutp);
366124211Sdes
367147005Sdes	for (;;) {
368124211Sdes		rc = select(sockfd + 1, NULL, fdset, NULL, &tv);
369124211Sdes		if (rc != -1 || errno != EINTR)
370124211Sdes			break;
371124211Sdes	}
372124211Sdes
373147005Sdes	switch (rc) {
374124211Sdes	case 0:
375124211Sdes		/* Timed out */
376124211Sdes		errno = ETIMEDOUT;
377124211Sdes		break;
378124211Sdes	case -1:
379124211Sdes		/* Select error */
380126277Sdes		debug("select: %s", strerror(errno));
381124211Sdes		break;
382124211Sdes	case 1:
383124211Sdes		/* Completed or failed */
384124211Sdes		optval = 0;
385124211Sdes		optlen = sizeof(optval);
386126277Sdes		if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &optval,
387124211Sdes		    &optlen) == -1) {
388126277Sdes			debug("getsockopt: %s", strerror(errno));
389124211Sdes			break;
390124211Sdes		}
391124211Sdes		if (optval != 0) {
392124211Sdes			errno = optval;
393124211Sdes			break;
394124211Sdes		}
395124211Sdes		result = 0;
396126277Sdes		unset_nonblock(sockfd);
397124211Sdes		break;
398124211Sdes	default:
399124211Sdes		/* Should not occur */
400124211Sdes		fatal("Bogus return (%d) from select()", rc);
401124211Sdes	}
402124211Sdes
403255767Sdes	free(fdset);
404181111Sdes
405181111Sdes done:
406181111Sdes 	if (result == 0 && *timeoutp > 0) {
407181111Sdes		ms_subtract_diff(&t_start, timeoutp);
408181111Sdes		if (*timeoutp <= 0) {
409181111Sdes			errno = ETIMEDOUT;
410181111Sdes			result = -1;
411181111Sdes		}
412181111Sdes	}
413181111Sdes
414124211Sdes	return (result);
415124211Sdes}
416124211Sdes
41757429Smarkm/*
41857429Smarkm * Opens a TCP/IP connection to the remote server on the given host.
41957429Smarkm * The address of the remote host will be returned in hostaddr.
42098684Sdes * If port is 0, the default port will be used.  If needpriv is true,
42157429Smarkm * a privileged port will be allocated to make the connection.
42298684Sdes * This requires super-user privileges if needpriv is true.
42357429Smarkm * Connection_attempts specifies the maximum number of tries (one per
42457429Smarkm * second).  If proxy_command is non-NULL, it specifies the command (with %h
42557429Smarkm * and %p substituted for host and port, respectively) to use to contact
42657429Smarkm * the daemon.
42757429Smarkm */
428262566Sdesstatic int
429262566Sdesssh_connect_direct(const char *host, struct addrinfo *aitop,
430262566Sdes    struct sockaddr_storage *hostaddr, u_short port, int family,
431262566Sdes    int connection_attempts, int *timeout_ms, int want_keepalive, int needpriv)
43257429Smarkm{
43376262Sgreen	int on = 1;
43457429Smarkm	int sock = -1, attempt;
43576262Sgreen	char ntop[NI_MAXHOST], strport[NI_MAXSERV];
436262566Sdes	struct addrinfo *ai;
43757429Smarkm
438296781Sdes	debug2("%s: needpriv %d", __func__, needpriv);
439296781Sdes	memset(ntop, 0, sizeof(ntop));
440296781Sdes	memset(strport, 0, sizeof(strport));
44157429Smarkm
442162856Sdes	for (attempt = 0; attempt < connection_attempts; attempt++) {
443164149Sdes		if (attempt > 0) {
444164149Sdes			/* Sleep a moment before retrying. */
445164149Sdes			sleep(1);
44657429Smarkm			debug("Trying again...");
447164149Sdes		}
448162856Sdes		/*
449162856Sdes		 * Loop through addresses for this host, and try each one in
450162856Sdes		 * sequence until the connection succeeds.
451162856Sdes		 */
45257429Smarkm		for (ai = aitop; ai; ai = ai->ai_next) {
453262566Sdes			if (ai->ai_family != AF_INET &&
454262566Sdes			    ai->ai_family != AF_INET6)
45557429Smarkm				continue;
45657429Smarkm			if (getnameinfo(ai->ai_addr, ai->ai_addrlen,
45757429Smarkm			    ntop, sizeof(ntop), strport, sizeof(strport),
45857429Smarkm			    NI_NUMERICHOST|NI_NUMERICSERV) != 0) {
459296781Sdes				error("%s: getnameinfo failed", __func__);
46057429Smarkm				continue;
46157429Smarkm			}
46257429Smarkm			debug("Connecting to %.200s [%.100s] port %s.",
46376226Sgreen				host, ntop, strport);
46457429Smarkm
46557429Smarkm			/* Create a socket for connecting. */
466124211Sdes			sock = ssh_create_socket(needpriv, ai);
46757429Smarkm			if (sock < 0)
46892559Sdes				/* Any error is already output */
46957429Smarkm				continue;
47057429Smarkm
471124211Sdes			if (timeout_connect(sock, ai->ai_addr, ai->ai_addrlen,
472181111Sdes			    timeout_ms) >= 0) {
47357429Smarkm				/* Successful connection. */
47476262Sgreen				memcpy(hostaddr, ai->ai_addr, ai->ai_addrlen);
47557429Smarkm				break;
47657429Smarkm			} else {
477106130Sdes				debug("connect to address %s port %s: %s",
478106130Sdes				    ntop, strport, strerror(errno));
47957429Smarkm				close(sock);
480162856Sdes				sock = -1;
48157429Smarkm			}
48257429Smarkm		}
483162856Sdes		if (sock != -1)
48457429Smarkm			break;	/* Successful connection. */
48557429Smarkm	}
48657429Smarkm
48757429Smarkm	/* Return failure if we didn't get a successful connection. */
488162856Sdes	if (sock == -1) {
489147005Sdes		error("ssh: connect to host %s port %s: %s",
490106130Sdes		    host, strport, strerror(errno));
491147005Sdes		return (-1);
492106130Sdes	}
49357429Smarkm
49457429Smarkm	debug("Connection established.");
49557429Smarkm
496126277Sdes	/* Set SO_KEEPALIVE if requested. */
497181111Sdes	if (want_keepalive &&
49876262Sgreen	    setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (void *)&on,
49976262Sgreen	    sizeof(on)) < 0)
50076262Sgreen		error("setsockopt SO_KEEPALIVE: %.100s", strerror(errno));
50176262Sgreen
50257429Smarkm	/* Set the connection. */
50357429Smarkm	packet_set_connection(sock, sock);
50457429Smarkm
50592559Sdes	return 0;
50657429Smarkm}
50757429Smarkm
508262566Sdesint
509262566Sdesssh_connect(const char *host, struct addrinfo *addrs,
510262566Sdes    struct sockaddr_storage *hostaddr, u_short port, int family,
511262566Sdes    int connection_attempts, int *timeout_ms, int want_keepalive, int needpriv)
512262566Sdes{
513262566Sdes	if (options.proxy_command == NULL) {
514262566Sdes		return ssh_connect_direct(host, addrs, hostaddr, port, family,
515262566Sdes		    connection_attempts, timeout_ms, want_keepalive, needpriv);
516262566Sdes	} else if (strcmp(options.proxy_command, "-") == 0) {
517262566Sdes		packet_set_connection(STDIN_FILENO, STDOUT_FILENO);
518262566Sdes		return 0; /* Always succeeds */
519262566Sdes	} else if (options.proxy_use_fdpass) {
520262566Sdes		return ssh_proxy_fdpass_connect(host, port,
521262566Sdes		    options.proxy_command);
522262566Sdes	}
523262566Sdes	return ssh_proxy_connect(host, port, options.proxy_command);
524262566Sdes}
525262566Sdes
526248619Sdesstatic void
527248619Sdessend_client_banner(int connection_out, int minor1)
528248619Sdes{
529248619Sdes	/* Send our own protocol version identification. */
530294693Sdes	xasprintf(&client_version_string, "SSH-%d.%d-%.100s%s%s%s",
531248619Sdes	    compat20 ? PROTOCOL_MAJOR_2 : PROTOCOL_MAJOR_1,
532248619Sdes	    compat20 ? PROTOCOL_MINOR_2 : minor1,
533294693Sdes	    SSH_VERSION,
534248619Sdes	    *options.version_addendum == '\0' ? "" : " ",
535248619Sdes	    options.version_addendum, compat20 ? "\r\n" : "\n");
536296781Sdes	if (atomicio(vwrite, connection_out, client_version_string,
537248619Sdes	    strlen(client_version_string)) != strlen(client_version_string))
538248619Sdes		fatal("write: %.100s", strerror(errno));
539248619Sdes	chop(client_version_string);
540248619Sdes	debug("Local version string %.100s", client_version_string);
541248619Sdes}
542248619Sdes
54357429Smarkm/*
54460576Skris * Waits for the server identification string, and sends our own
54560576Skris * identification string.
54657429Smarkm */
547197679Sdesvoid
548181111Sdesssh_exchange_identification(int timeout_ms)
54957429Smarkm{
55060576Skris	char buf[256], remote_version[256];	/* must be same size! */
551149753Sdes	int remote_major, remote_minor, mismatch;
55260576Skris	int connection_in = packet_get_connection_in();
55360576Skris	int connection_out = packet_get_connection_out();
554248619Sdes	int minor1 = PROTOCOL_MINOR_1, client_banner_sent = 0;
555162856Sdes	u_int i, n;
556181111Sdes	size_t len;
557181111Sdes	int fdsetsz, remaining, rc;
558181111Sdes	struct timeval t_start, t_remaining;
559181111Sdes	fd_set *fdset;
56057429Smarkm
561181111Sdes	fdsetsz = howmany(connection_in + 1, NFDBITS) * sizeof(fd_mask);
562181111Sdes	fdset = xcalloc(1, fdsetsz);
563181111Sdes
564248619Sdes	/*
565248619Sdes	 * If we are SSH2-only then we can send the banner immediately and
566248619Sdes	 * save a round-trip.
567248619Sdes	 */
568248619Sdes	if (options.protocol == SSH_PROTO_2) {
569248619Sdes		enable_compat20();
570248619Sdes		send_client_banner(connection_out, 0);
571248619Sdes		client_banner_sent = 1;
572248619Sdes	}
573248619Sdes
574149753Sdes	/* Read other side's version identification. */
575181111Sdes	remaining = timeout_ms;
576162856Sdes	for (n = 0;;) {
57765674Skris		for (i = 0; i < sizeof(buf) - 1; i++) {
578181111Sdes			if (timeout_ms > 0) {
579181111Sdes				gettimeofday(&t_start, NULL);
580181111Sdes				ms_to_timeval(&t_remaining, remaining);
581181111Sdes				FD_SET(connection_in, fdset);
582181111Sdes				rc = select(connection_in + 1, fdset, NULL,
583181111Sdes				    fdset, &t_remaining);
584181111Sdes				ms_subtract_diff(&t_start, &remaining);
585181111Sdes				if (rc == 0 || remaining <= 0)
586181111Sdes					fatal("Connection timed out during "
587181111Sdes					    "banner exchange");
588181111Sdes				if (rc == -1) {
589181111Sdes					if (errno == EINTR)
590181111Sdes						continue;
591181111Sdes					fatal("ssh_exchange_identification: "
592181111Sdes					    "select: %s", strerror(errno));
593181111Sdes				}
594181111Sdes			}
595149753Sdes
596296781Sdes			len = atomicio(read, connection_in, &buf[i], 1);
597181111Sdes
598149753Sdes			if (len != 1 && errno == EPIPE)
599181111Sdes				fatal("ssh_exchange_identification: "
600181111Sdes				    "Connection closed by remote host");
601149753Sdes			else if (len != 1)
602181111Sdes				fatal("ssh_exchange_identification: "
603181111Sdes				    "read: %.100s", strerror(errno));
60465674Skris			if (buf[i] == '\r') {
60565674Skris				buf[i] = '\n';
60665674Skris				buf[i + 1] = 0;
60765674Skris				continue;		/**XXX wait for \n */
60865674Skris			}
60965674Skris			if (buf[i] == '\n') {
61065674Skris				buf[i + 1] = 0;
61165674Skris				break;
61265674Skris			}
613162856Sdes			if (++n > 65536)
614181111Sdes				fatal("ssh_exchange_identification: "
615181111Sdes				    "No banner received");
61660576Skris		}
61765674Skris		buf[sizeof(buf) - 1] = 0;
61865674Skris		if (strncmp(buf, "SSH-", 4) == 0)
61960576Skris			break;
62065674Skris		debug("ssh_exchange_identification: %s", buf);
62160576Skris	}
62260576Skris	server_version_string = xstrdup(buf);
623255767Sdes	free(fdset);
62457429Smarkm
62560576Skris	/*
62660576Skris	 * Check that the versions match.  In future this might accept
62760576Skris	 * several versions and set appropriate flags to handle them.
62860576Skris	 */
62960576Skris	if (sscanf(server_version_string, "SSH-%d.%d-%[^\n]\n",
63060576Skris	    &remote_major, &remote_minor, remote_version) != 3)
63160576Skris		fatal("Bad remote protocol version identification: '%.100s'", buf);
63260576Skris	debug("Remote protocol version %d.%d, remote software version %.100s",
63392559Sdes	    remote_major, remote_minor, remote_version);
63457429Smarkm
635295367Sdes	active_state->compat = compat_datafellows(remote_version);
63660576Skris	mismatch = 0;
63757429Smarkm
63892559Sdes	switch (remote_major) {
63960576Skris	case 1:
64060576Skris		if (remote_minor == 99 &&
64160576Skris		    (options.protocol & SSH_PROTO_2) &&
64260576Skris		    !(options.protocol & SSH_PROTO_1_PREFERRED)) {
64360576Skris			enable_compat20();
64460576Skris			break;
64560576Skris		}
64660576Skris		if (!(options.protocol & SSH_PROTO_1)) {
64760576Skris			mismatch = 1;
64860576Skris			break;
64960576Skris		}
65060576Skris		if (remote_minor < 3) {
65160576Skris			fatal("Remote machine has too old SSH software version.");
65276262Sgreen		} else if (remote_minor == 3 || remote_minor == 4) {
65360576Skris			/* We speak 1.3, too. */
65460576Skris			enable_compat13();
65576262Sgreen			minor1 = 3;
65660576Skris			if (options.forward_agent) {
657124211Sdes				logit("Agent forwarding disabled for protocol 1.3");
65860576Skris				options.forward_agent = 0;
65960576Skris			}
66060576Skris		}
66160576Skris		break;
66260576Skris	case 2:
66360576Skris		if (options.protocol & SSH_PROTO_2) {
66460576Skris			enable_compat20();
66560576Skris			break;
66660576Skris		}
66760576Skris		/* FALLTHROUGH */
66860576Skris	default:
66960576Skris		mismatch = 1;
67060576Skris		break;
67160576Skris	}
67260576Skris	if (mismatch)
67360576Skris		fatal("Protocol major versions differ: %d vs. %d",
67460576Skris		    (options.protocol & SSH_PROTO_2) ? PROTOCOL_MAJOR_2 : PROTOCOL_MAJOR_1,
67560576Skris		    remote_major);
676262566Sdes	if ((datafellows & SSH_BUG_DERIVEKEY) != 0)
677262566Sdes		fatal("Server version \"%.100s\" uses unsafe key agreement; "
678262566Sdes		    "refusing connection", remote_version);
679262566Sdes	if ((datafellows & SSH_BUG_RSASIGMD5) != 0)
680262566Sdes		logit("Server version \"%.100s\" uses unsafe RSA signature "
681262566Sdes		    "scheme; disabling use of RSA keys", remote_version);
682248619Sdes	if (!client_banner_sent)
683248619Sdes		send_client_banner(connection_out, minor1);
68460576Skris	chop(server_version_string);
68557429Smarkm}
68657429Smarkm
68776262Sgreen/* defaults to 'no' */
68892559Sdesstatic int
68992559Sdesconfirm(const char *prompt)
69057429Smarkm{
69192559Sdes	const char *msg, *again = "Please type 'yes' or 'no': ";
69292559Sdes	char *p;
69392559Sdes	int ret = -1;
69457429Smarkm
69576262Sgreen	if (options.batch_mode)
69676262Sgreen		return 0;
69792559Sdes	for (msg = prompt;;msg = again) {
69892559Sdes		p = read_passphrase(msg, RP_ECHO);
69992559Sdes		if (p == NULL ||
70092559Sdes		    (p[0] == '\0') || (p[0] == '\n') ||
70192559Sdes		    strncasecmp(p, "no", 2) == 0)
70292559Sdes			ret = 0;
703106130Sdes		if (p && strncasecmp(p, "yes", 3) == 0)
70492559Sdes			ret = 1;
705255767Sdes		free(p);
70692559Sdes		if (ret != -1)
70792559Sdes			return ret;
70857429Smarkm	}
70957429Smarkm}
71057429Smarkm
711204917Sdesstatic int
712204917Sdescheck_host_cert(const char *host, const Key *host_key)
713204917Sdes{
714204917Sdes	const char *reason;
715204917Sdes
716204917Sdes	if (key_cert_check_authority(host_key, 1, 0, host, &reason) != 0) {
717204917Sdes		error("%s", reason);
718204917Sdes		return 0;
719204917Sdes	}
720295367Sdes	if (buffer_len(host_key->cert->critical) != 0) {
721215116Sdes		error("Certificate for %s contains unsupported "
722215116Sdes		    "critical options(s)", host);
723204917Sdes		return 0;
724204917Sdes	}
725204917Sdes	return 1;
726204917Sdes}
727204917Sdes
728221420Sdesstatic int
729221420Sdessockaddr_is_local(struct sockaddr *hostaddr)
730221420Sdes{
731221420Sdes	switch (hostaddr->sa_family) {
732221420Sdes	case AF_INET:
733221420Sdes		return (ntohl(((struct sockaddr_in *)hostaddr)->
734221420Sdes		    sin_addr.s_addr) >> 24) == IN_LOOPBACKNET;
735221420Sdes	case AF_INET6:
736221420Sdes		return IN6_IS_ADDR_LOOPBACK(
737221420Sdes		    &(((struct sockaddr_in6 *)hostaddr)->sin6_addr));
738221420Sdes	default:
739221420Sdes		return 0;
740221420Sdes	}
741221420Sdes}
742221420Sdes
74357429Smarkm/*
744221420Sdes * Prepare the hostname and ip address strings that are used to lookup
745221420Sdes * host keys in known_hosts files. These may have a port number appended.
746221420Sdes */
747221420Sdesvoid
748221420Sdesget_hostfile_hostname_ipaddr(char *hostname, struct sockaddr *hostaddr,
749221420Sdes    u_short port, char **hostfile_hostname, char **hostfile_ipaddr)
750221420Sdes{
751221420Sdes	char ntop[NI_MAXHOST];
752221420Sdes	socklen_t addrlen;
753221420Sdes
754221420Sdes	switch (hostaddr == NULL ? -1 : hostaddr->sa_family) {
755221420Sdes	case -1:
756221420Sdes		addrlen = 0;
757221420Sdes		break;
758221420Sdes	case AF_INET:
759221420Sdes		addrlen = sizeof(struct sockaddr_in);
760221420Sdes		break;
761221420Sdes	case AF_INET6:
762221420Sdes		addrlen = sizeof(struct sockaddr_in6);
763221420Sdes		break;
764221420Sdes	default:
765221420Sdes		addrlen = sizeof(struct sockaddr);
766221420Sdes		break;
767221420Sdes	}
768221420Sdes
769221420Sdes	/*
770221420Sdes	 * We don't have the remote ip-address for connections
771221420Sdes	 * using a proxy command
772221420Sdes	 */
773221420Sdes	if (hostfile_ipaddr != NULL) {
774221420Sdes		if (options.proxy_command == NULL) {
775221420Sdes			if (getnameinfo(hostaddr, addrlen,
776221420Sdes			    ntop, sizeof(ntop), NULL, 0, NI_NUMERICHOST) != 0)
777295367Sdes			fatal("%s: getnameinfo failed", __func__);
778221420Sdes			*hostfile_ipaddr = put_host_port(ntop, port);
779221420Sdes		} else {
780221420Sdes			*hostfile_ipaddr = xstrdup("<no hostip for proxy "
781221420Sdes			    "command>");
782221420Sdes		}
783221420Sdes	}
784221420Sdes
785221420Sdes	/*
786221420Sdes	 * Allow the user to record the key under a different name or
787221420Sdes	 * differentiate a non-standard port.  This is useful for ssh
788221420Sdes	 * tunneling over forwarded connections or if you run multiple
789221420Sdes	 * sshd's on different ports on the same machine.
790221420Sdes	 */
791221420Sdes	if (hostfile_hostname != NULL) {
792221420Sdes		if (options.host_key_alias != NULL) {
793221420Sdes			*hostfile_hostname = xstrdup(options.host_key_alias);
794221420Sdes			debug("using hostkeyalias: %s", *hostfile_hostname);
795221420Sdes		} else {
796221420Sdes			*hostfile_hostname = put_host_port(hostname, port);
797221420Sdes		}
798221420Sdes	}
799221420Sdes}
800221420Sdes
801221420Sdes/*
80292559Sdes * check whether the supplied host key is valid, return -1 if the key
803226046Sdes * is not valid. user_hostfile[0] will not be updated if 'readonly' is true.
80457429Smarkm */
805162856Sdes#define RDRW	0
806162856Sdes#define RDONLY	1
807162856Sdes#define ROQUIET	2
80892559Sdesstatic int
809162856Sdescheck_host_key(char *hostname, struct sockaddr *hostaddr, u_short port,
810226046Sdes    Key *host_key, int readonly,
811226046Sdes    char **user_hostfiles, u_int num_user_hostfiles,
812226046Sdes    char **system_hostfiles, u_int num_system_hostfiles)
81357429Smarkm{
814226046Sdes	HostStatus host_status;
815226046Sdes	HostStatus ip_status;
816221420Sdes	Key *raw_key = NULL;
817162856Sdes	char *ip = NULL, *host = NULL;
818181111Sdes	char hostline[1000], *hostp, *fp, *ra;
81992559Sdes	char msg[1024];
820226046Sdes	const char *type;
821226046Sdes	const struct hostkey_entry *host_found, *ip_found;
822221420Sdes	int len, cancelled_forwarding = 0;
823226046Sdes	int local = sockaddr_is_local(hostaddr);
824226046Sdes	int r, want_cert = key_is_cert(host_key), host_ip_differ = 0;
825295367Sdes	int hostkey_trusted = 0; /* Known or explicitly accepted by user */
826221420Sdes	struct hostkeys *host_hostkeys, *ip_hostkeys;
827226046Sdes	u_int i;
82857429Smarkm
82960576Skris	/*
83060576Skris	 * Force accepting of the host key for loopback/localhost. The
83160576Skris	 * problem is that if the home directory is NFS-mounted to multiple
83260576Skris	 * machines, localhost will refer to a different machine in each of
83360576Skris	 * them, and the user will get bogus HOST_CHANGED warnings.  This
83460576Skris	 * essentially disables host authentication for localhost; however,
83560576Skris	 * this is probably not a real problem.
83660576Skris	 */
83792559Sdes	if (options.no_host_authentication_for_localhost == 1 && local &&
83892559Sdes	    options.host_key_alias == NULL) {
83976262Sgreen		debug("Forcing accepting of host key for "
84076262Sgreen		    "loopback/localhost.");
84192559Sdes		return 0;
84260576Skris	}
84357429Smarkm
84460576Skris	/*
845221420Sdes	 * Prepare the hostname and address strings used for hostkey lookup.
846221420Sdes	 * In some cases, these will have a port number appended.
84760576Skris	 */
848221420Sdes	get_hostfile_hostname_ipaddr(hostname, hostaddr, port, &host, &ip);
849181111Sdes
85076262Sgreen	/*
85176262Sgreen	 * Turn off check_host_ip if the connection is to localhost, via proxy
85276262Sgreen	 * command or if we don't have a hostname to compare with
85376262Sgreen	 */
854162856Sdes	if (options.check_host_ip && (local ||
855162856Sdes	    strcmp(hostname, ip) == 0 || options.proxy_command != NULL))
85676262Sgreen		options.check_host_ip = 0;
85757429Smarkm
858221420Sdes	host_hostkeys = init_hostkeys();
859226046Sdes	for (i = 0; i < num_user_hostfiles; i++)
860226046Sdes		load_hostkeys(host_hostkeys, host, user_hostfiles[i]);
861226046Sdes	for (i = 0; i < num_system_hostfiles; i++)
862226046Sdes		load_hostkeys(host_hostkeys, host, system_hostfiles[i]);
863221420Sdes
864221420Sdes	ip_hostkeys = NULL;
865221420Sdes	if (!want_cert && options.check_host_ip) {
866221420Sdes		ip_hostkeys = init_hostkeys();
867226046Sdes		for (i = 0; i < num_user_hostfiles; i++)
868226046Sdes			load_hostkeys(ip_hostkeys, ip, user_hostfiles[i]);
869226046Sdes		for (i = 0; i < num_system_hostfiles; i++)
870226046Sdes			load_hostkeys(ip_hostkeys, ip, system_hostfiles[i]);
87176262Sgreen	}
87276262Sgreen
873204917Sdes retry:
874221420Sdes	/* Reload these as they may have changed on cert->key downgrade */
875204917Sdes	want_cert = key_is_cert(host_key);
876204917Sdes	type = key_type(host_key);
877204917Sdes
87876262Sgreen	/*
879157019Sdes	 * Check if the host key is present in the user's list of known
88060576Skris	 * hosts or in the systemwide list.
88160576Skris	 */
882221420Sdes	host_status = check_key_in_hostkeys(host_hostkeys, host_key,
883221420Sdes	    &host_found);
884221420Sdes
88560576Skris	/*
88660576Skris	 * Also perform check for the ip address, skip the check if we are
887204917Sdes	 * localhost, looking for a certificate, or the hostname was an ip
888204917Sdes	 * address to begin with.
88960576Skris	 */
890221420Sdes	if (!want_cert && ip_hostkeys != NULL) {
891221420Sdes		ip_status = check_key_in_hostkeys(ip_hostkeys, host_key,
892221420Sdes		    &ip_found);
89360576Skris		if (host_status == HOST_CHANGED &&
894221420Sdes		    (ip_status != HOST_CHANGED ||
895221420Sdes		    (ip_found != NULL &&
896221420Sdes		    !key_equal(ip_found->key, host_found->key))))
89760576Skris			host_ip_differ = 1;
89860576Skris	} else
89960576Skris		ip_status = host_status;
90057429Smarkm
90160576Skris	switch (host_status) {
90260576Skris	case HOST_OK:
90360576Skris		/* The host is known and the key matches. */
904204917Sdes		debug("Host '%.200s' is known and matches the %s host %s.",
905204917Sdes		    host, type, want_cert ? "certificate" : "key");
906221420Sdes		debug("Found %s in %s:%lu", want_cert ? "CA key" : "key",
907221420Sdes		    host_found->file, host_found->line);
908204917Sdes		if (want_cert && !check_host_cert(hostname, host_key))
909204917Sdes			goto fail;
91076262Sgreen		if (options.check_host_ip && ip_status == HOST_NEW) {
911204917Sdes			if (readonly || want_cert)
912124211Sdes				logit("%s host key for IP address "
91392559Sdes				    "'%.128s' not in list of known hosts.",
91492559Sdes				    type, ip);
915226046Sdes			else if (!add_host_to_hostfile(user_hostfiles[0], ip,
916147005Sdes			    host_key, options.hash_known_hosts))
917124211Sdes				logit("Failed to add the %s host key for IP "
91892559Sdes				    "address '%.128s' to the list of known "
919295367Sdes				    "hosts (%.500s).", type, ip,
920226046Sdes				    user_hostfiles[0]);
92176262Sgreen			else
922124211Sdes				logit("Warning: Permanently added the %s host "
92392559Sdes				    "key for IP address '%.128s' to the list "
92492559Sdes				    "of known hosts.", type, ip);
925181111Sdes		} else if (options.visual_host_key) {
926295367Sdes			fp = sshkey_fingerprint(host_key,
927295367Sdes			    options.fingerprint_hash, SSH_FP_DEFAULT);
928295367Sdes			ra = sshkey_fingerprint(host_key,
929295367Sdes			    options.fingerprint_hash, SSH_FP_RANDOMART);
930295367Sdes			if (fp == NULL || ra == NULL)
931295367Sdes				fatal("%s: sshkey_fingerprint fail", __func__);
932296781Sdes			logit("Host key fingerprint is %s\n%s", fp, ra);
933255767Sdes			free(ra);
934255767Sdes			free(fp);
93560576Skris		}
936295367Sdes		hostkey_trusted = 1;
93760576Skris		break;
93860576Skris	case HOST_NEW:
939162856Sdes		if (options.host_key_alias == NULL && port != 0 &&
940162856Sdes		    port != SSH_DEFAULT_PORT) {
941162856Sdes			debug("checking without port identifier");
942192595Sdes			if (check_host_key(hostname, hostaddr, 0, host_key,
943226046Sdes			    ROQUIET, user_hostfiles, num_user_hostfiles,
944226046Sdes			    system_hostfiles, num_system_hostfiles) == 0) {
945162856Sdes				debug("found matching key w/out port");
946162856Sdes				break;
947162856Sdes			}
948162856Sdes		}
949204917Sdes		if (readonly || want_cert)
95092559Sdes			goto fail;
95160576Skris		/* The host is new. */
95260576Skris		if (options.strict_host_key_checking == 1) {
95392559Sdes			/*
95492559Sdes			 * User has requested strict host key checking.  We
95592559Sdes			 * will not add the host key automatically.  The only
95692559Sdes			 * alternative left is to abort.
95792559Sdes			 */
95892559Sdes			error("No %s host key is known for %.200s and you "
95992559Sdes			    "have requested strict checking.", type, host);
96092559Sdes			goto fail;
96160576Skris		} else if (options.strict_host_key_checking == 2) {
962124211Sdes			char msg1[1024], msg2[1024];
963124211Sdes
964221420Sdes			if (show_other_keys(host_hostkeys, host_key))
965124211Sdes				snprintf(msg1, sizeof(msg1),
966149753Sdes				    "\nbut keys of different type are already"
967149753Sdes				    " known for this host.");
968124211Sdes			else
969124211Sdes				snprintf(msg1, sizeof(msg1), ".");
97060576Skris			/* The default */
971295367Sdes			fp = sshkey_fingerprint(host_key,
972295367Sdes			    options.fingerprint_hash, SSH_FP_DEFAULT);
973295367Sdes			ra = sshkey_fingerprint(host_key,
974295367Sdes			    options.fingerprint_hash, SSH_FP_RANDOMART);
975295367Sdes			if (fp == NULL || ra == NULL)
976295367Sdes				fatal("%s: sshkey_fingerprint fail", __func__);
977124211Sdes			msg2[0] = '\0';
978124211Sdes			if (options.verify_host_key_dns) {
979126277Sdes				if (matching_host_key_dns)
980124211Sdes					snprintf(msg2, sizeof(msg2),
981124211Sdes					    "Matching host key fingerprint"
982124211Sdes					    " found in DNS.\n");
983124211Sdes				else
984124211Sdes					snprintf(msg2, sizeof(msg2),
985124211Sdes					    "No matching host key fingerprint"
986124211Sdes					    " found in DNS.\n");
987124211Sdes			}
98892559Sdes			snprintf(msg, sizeof(msg),
98992559Sdes			    "The authenticity of host '%.200s (%s)' can't be "
990106130Sdes			    "established%s\n"
991181111Sdes			    "%s key fingerprint is %s.%s%s\n%s"
99292559Sdes			    "Are you sure you want to continue connecting "
993106130Sdes			    "(yes/no)? ",
994181111Sdes			    host, ip, msg1, type, fp,
995181111Sdes			    options.visual_host_key ? "\n" : "",
996181111Sdes			    options.visual_host_key ? ra : "",
997181111Sdes			    msg2);
998255767Sdes			free(ra);
999255767Sdes			free(fp);
100092559Sdes			if (!confirm(msg))
100192559Sdes				goto fail;
1002295367Sdes			hostkey_trusted = 1; /* user explicitly confirmed */
100360576Skris		}
1004147005Sdes		/*
1005147005Sdes		 * If not in strict mode, add the key automatically to the
1006147005Sdes		 * local known_hosts file.
1007147005Sdes		 */
100876262Sgreen		if (options.check_host_ip && ip_status == HOST_NEW) {
1009221420Sdes			snprintf(hostline, sizeof(hostline), "%s,%s", host, ip);
101060576Skris			hostp = hostline;
1011147005Sdes			if (options.hash_known_hosts) {
1012147005Sdes				/* Add hash of host and IP separately */
1013226046Sdes				r = add_host_to_hostfile(user_hostfiles[0],
1014226046Sdes				    host, host_key, options.hash_known_hosts) &&
1015226046Sdes				    add_host_to_hostfile(user_hostfiles[0], ip,
1016147005Sdes				    host_key, options.hash_known_hosts);
1017147005Sdes			} else {
1018147005Sdes				/* Add unhashed "host,ip" */
1019226046Sdes				r = add_host_to_hostfile(user_hostfiles[0],
1020147005Sdes				    hostline, host_key,
1021147005Sdes				    options.hash_known_hosts);
1022147005Sdes			}
1023147005Sdes		} else {
1024226046Sdes			r = add_host_to_hostfile(user_hostfiles[0], host,
1025226046Sdes			    host_key, options.hash_known_hosts);
102660576Skris			hostp = host;
1027147005Sdes		}
102857429Smarkm
1029147005Sdes		if (!r)
1030124211Sdes			logit("Failed to add the host to the list of known "
1031226046Sdes			    "hosts (%.500s).", user_hostfiles[0]);
103260576Skris		else
1033124211Sdes			logit("Warning: Permanently added '%.200s' (%s) to the "
103492559Sdes			    "list of known hosts.", hostp, type);
103557429Smarkm		break;
1036204917Sdes	case HOST_REVOKED:
1037204917Sdes		error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
1038204917Sdes		error("@       WARNING: REVOKED HOST KEY DETECTED!               @");
1039204917Sdes		error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
1040204917Sdes		error("The %s host key for %s is marked as revoked.", type, host);
1041204917Sdes		error("This could mean that a stolen key is being used to");
1042204917Sdes		error("impersonate this host.");
1043204917Sdes
1044204917Sdes		/*
1045204917Sdes		 * If strict host key checking is in use, the user will have
1046204917Sdes		 * to edit the key manually and we can only abort.
1047204917Sdes		 */
1048204917Sdes		if (options.strict_host_key_checking) {
1049204917Sdes			error("%s host key for %.200s was revoked and you have "
1050204917Sdes			    "requested strict checking.", type, host);
1051204917Sdes			goto fail;
1052204917Sdes		}
1053204917Sdes		goto continue_unsafe;
1054204917Sdes
105560576Skris	case HOST_CHANGED:
1056204917Sdes		if (want_cert) {
1057204917Sdes			/*
1058204917Sdes			 * This is only a debug() since it is valid to have
1059204917Sdes			 * CAs with wildcard DNS matches that don't match
1060204917Sdes			 * all hosts that one might visit.
1061204917Sdes			 */
1062204917Sdes			debug("Host certificate authority does not "
1063221420Sdes			    "match %s in %s:%lu", CA_MARKER,
1064221420Sdes			    host_found->file, host_found->line);
1065204917Sdes			goto fail;
1066204917Sdes		}
1067162856Sdes		if (readonly == ROQUIET)
1068162856Sdes			goto fail;
106960576Skris		if (options.check_host_ip && host_ip_differ) {
1070137019Sdes			char *key_msg;
107160576Skris			if (ip_status == HOST_NEW)
1072137019Sdes				key_msg = "is unknown";
107360576Skris			else if (ip_status == HOST_OK)
1074137019Sdes				key_msg = "is unchanged";
107560576Skris			else
1076137019Sdes				key_msg = "has a different value";
107760576Skris			error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
107860576Skris			error("@       WARNING: POSSIBLE DNS SPOOFING DETECTED!          @");
107960576Skris			error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
108060576Skris			error("The %s host key for %s has changed,", type, host);
1081181111Sdes			error("and the key for the corresponding IP address %s", ip);
1082137019Sdes			error("%s. This could either mean that", key_msg);
108360576Skris			error("DNS SPOOFING is happening or the IP address for the host");
108476262Sgreen			error("and its host key have changed at the same time.");
108576262Sgreen			if (ip_status != HOST_NEW)
1086221420Sdes				error("Offending key for IP in %s:%lu",
1087221420Sdes				    ip_found->file, ip_found->line);
108860576Skris		}
108960576Skris		/* The host key has changed. */
1090126277Sdes		warn_changed_key(host_key);
109160576Skris		error("Add correct host key in %.100s to get rid of this message.",
1092226046Sdes		    user_hostfiles[0]);
1093221420Sdes		error("Offending %s key in %s:%lu", key_type(host_found->key),
1094221420Sdes		    host_found->file, host_found->line);
109557429Smarkm
109660576Skris		/*
109760576Skris		 * If strict host key checking is in use, the user will have
109860576Skris		 * to edit the key manually and we can only abort.
109960576Skris		 */
110092559Sdes		if (options.strict_host_key_checking) {
110192559Sdes			error("%s host key for %.200s has changed and you have "
110292559Sdes			    "requested strict checking.", type, host);
110392559Sdes			goto fail;
110492559Sdes		}
110557429Smarkm
1106204917Sdes continue_unsafe:
110757429Smarkm		/*
110860576Skris		 * If strict host key checking has not been requested, allow
1109124211Sdes		 * the connection but without MITM-able authentication or
1110162856Sdes		 * forwarding.
111157429Smarkm		 */
111260576Skris		if (options.password_authentication) {
111392559Sdes			error("Password authentication is disabled to avoid "
111492559Sdes			    "man-in-the-middle attacks.");
111560576Skris			options.password_authentication = 0;
1116181111Sdes			cancelled_forwarding = 1;
111757429Smarkm		}
1118124211Sdes		if (options.kbd_interactive_authentication) {
1119124211Sdes			error("Keyboard-interactive authentication is disabled"
1120124211Sdes			    " to avoid man-in-the-middle attacks.");
1121124211Sdes			options.kbd_interactive_authentication = 0;
1122124211Sdes			options.challenge_response_authentication = 0;
1123181111Sdes			cancelled_forwarding = 1;
1124124211Sdes		}
1125124211Sdes		if (options.challenge_response_authentication) {
1126124211Sdes			error("Challenge/response authentication is disabled"
1127124211Sdes			    " to avoid man-in-the-middle attacks.");
1128124211Sdes			options.challenge_response_authentication = 0;
1129181111Sdes			cancelled_forwarding = 1;
1130124211Sdes		}
113160576Skris		if (options.forward_agent) {
113292559Sdes			error("Agent forwarding is disabled to avoid "
113392559Sdes			    "man-in-the-middle attacks.");
113460576Skris			options.forward_agent = 0;
1135181111Sdes			cancelled_forwarding = 1;
113660576Skris		}
113776262Sgreen		if (options.forward_x11) {
113892559Sdes			error("X11 forwarding is disabled to avoid "
113992559Sdes			    "man-in-the-middle attacks.");
114076262Sgreen			options.forward_x11 = 0;
1141181111Sdes			cancelled_forwarding = 1;
114276262Sgreen		}
114392559Sdes		if (options.num_local_forwards > 0 ||
114492559Sdes		    options.num_remote_forwards > 0) {
114592559Sdes			error("Port forwarding is disabled to avoid "
114692559Sdes			    "man-in-the-middle attacks.");
114792559Sdes			options.num_local_forwards =
114892559Sdes			    options.num_remote_forwards = 0;
1149181111Sdes			cancelled_forwarding = 1;
115076262Sgreen		}
1151162856Sdes		if (options.tun_open != SSH_TUNMODE_NO) {
1152162856Sdes			error("Tunnel forwarding is disabled to avoid "
1153162856Sdes			    "man-in-the-middle attacks.");
1154162856Sdes			options.tun_open = SSH_TUNMODE_NO;
1155181111Sdes			cancelled_forwarding = 1;
1156162856Sdes		}
1157181111Sdes		if (options.exit_on_forward_failure && cancelled_forwarding)
1158181111Sdes			fatal("Error: forwarding disabled due to host key "
1159181111Sdes			    "check failure");
1160181111Sdes
116160576Skris		/*
116260576Skris		 * XXX Should permit the user to change to use the new id.
116360576Skris		 * This could be done by converting the host key to an
116460576Skris		 * identifying sentence, tell that the host identifies itself
1165204917Sdes		 * by that sentence, and ask the user if he/she wishes to
116660576Skris		 * accept the authentication.
116760576Skris		 */
116857429Smarkm		break;
1169106130Sdes	case HOST_FOUND:
1170106130Sdes		fatal("internal error");
1171106130Sdes		break;
117257429Smarkm	}
117376262Sgreen
117476262Sgreen	if (options.check_host_ip && host_status != HOST_CHANGED &&
117576262Sgreen	    ip_status == HOST_CHANGED) {
117692559Sdes		snprintf(msg, sizeof(msg),
117792559Sdes		    "Warning: the %s host key for '%.200s' "
117892559Sdes		    "differs from the key for the IP address '%.128s'"
1179221420Sdes		    "\nOffending key for IP in %s:%lu",
1180221420Sdes		    type, host, ip, ip_found->file, ip_found->line);
118192559Sdes		if (host_status == HOST_OK) {
118292559Sdes			len = strlen(msg);
118392559Sdes			snprintf(msg + len, sizeof(msg) - len,
1184221420Sdes			    "\nMatching host key in %s:%lu",
1185221420Sdes			    host_found->file, host_found->line);
118692559Sdes		}
118776262Sgreen		if (options.strict_host_key_checking == 1) {
1188124211Sdes			logit("%s", msg);
118992559Sdes			error("Exiting, you have requested strict checking.");
119092559Sdes			goto fail;
119176262Sgreen		} else if (options.strict_host_key_checking == 2) {
119292559Sdes			strlcat(msg, "\nAre you sure you want "
119392559Sdes			    "to continue connecting (yes/no)? ", sizeof(msg));
119492559Sdes			if (!confirm(msg))
119592559Sdes				goto fail;
119692559Sdes		} else {
1197124211Sdes			logit("%s", msg);
119876262Sgreen		}
119976262Sgreen	}
120076262Sgreen
1201295367Sdes	if (!hostkey_trusted && options.update_hostkeys) {
1202295367Sdes		debug("%s: hostkey not known or explicitly trusted: "
1203295367Sdes		    "disabling UpdateHostkeys", __func__);
1204295367Sdes		options.update_hostkeys = 0;
1205295367Sdes	}
1206295367Sdes
1207255767Sdes	free(ip);
1208255767Sdes	free(host);
1209221420Sdes	if (host_hostkeys != NULL)
1210221420Sdes		free_hostkeys(host_hostkeys);
1211221420Sdes	if (ip_hostkeys != NULL)
1212221420Sdes		free_hostkeys(ip_hostkeys);
121392559Sdes	return 0;
121492559Sdes
121592559Sdesfail:
1216204917Sdes	if (want_cert && host_status != HOST_REVOKED) {
1217204917Sdes		/*
1218204917Sdes		 * No matching certificate. Downgrade cert to raw key and
1219204917Sdes		 * search normally.
1220204917Sdes		 */
1221204917Sdes		debug("No matching CA found. Retry with plain key");
1222204917Sdes		raw_key = key_from_private(host_key);
1223204917Sdes		if (key_drop_cert(raw_key) != 0)
1224204917Sdes			fatal("Couldn't drop certificate");
1225204917Sdes		host_key = raw_key;
1226204917Sdes		goto retry;
1227204917Sdes	}
1228204917Sdes	if (raw_key != NULL)
1229204917Sdes		key_free(raw_key);
1230255767Sdes	free(ip);
1231255767Sdes	free(host);
1232221420Sdes	if (host_hostkeys != NULL)
1233221420Sdes		free_hostkeys(host_hostkeys);
1234221420Sdes	if (ip_hostkeys != NULL)
1235221420Sdes		free_hostkeys(ip_hostkeys);
123692559Sdes	return -1;
123757429Smarkm}
123857429Smarkm
1239124211Sdes/* returns 0 if key verifies or -1 if key does NOT verify */
124057565Smarkmint
124192559Sdesverify_host_key(char *host, struct sockaddr *hostaddr, Key *host_key)
124257565Smarkm{
1243296781Sdes	u_int i;
1244295367Sdes	int r = -1, flags = 0;
1245296781Sdes	char valid[64], *fp = NULL, *cafp = NULL;
1246295367Sdes	struct sshkey *plain = NULL;
124757565Smarkm
1248295367Sdes	if ((fp = sshkey_fingerprint(host_key,
1249295367Sdes	    options.fingerprint_hash, SSH_FP_DEFAULT)) == NULL) {
1250295367Sdes		error("%s: fingerprint host key: %s", __func__, ssh_err(r));
1251295367Sdes		r = -1;
1252295367Sdes		goto out;
1253295367Sdes	}
1254221420Sdes
1255296781Sdes	if (sshkey_is_cert(host_key)) {
1256296781Sdes		if ((cafp = sshkey_fingerprint(host_key->cert->signature_key,
1257296781Sdes		    options.fingerprint_hash, SSH_FP_DEFAULT)) == NULL) {
1258296781Sdes			error("%s: fingerprint CA key: %s",
1259296781Sdes			    __func__, ssh_err(r));
1260296781Sdes			r = -1;
1261296781Sdes			goto out;
1262296781Sdes		}
1263296781Sdes		sshkey_format_cert_validity(host_key->cert,
1264296781Sdes		    valid, sizeof(valid));
1265296781Sdes		debug("Server host certificate: %s %s, serial %llu "
1266296781Sdes		    "ID \"%s\" CA %s %s valid %s",
1267296781Sdes		    sshkey_ssh_name(host_key), fp,
1268296781Sdes		    (unsigned long long)host_key->cert->serial,
1269296781Sdes		    host_key->cert->key_id,
1270296781Sdes		    sshkey_ssh_name(host_key->cert->signature_key), cafp,
1271296781Sdes		    valid);
1272296781Sdes		for (i = 0; i < host_key->cert->nprincipals; i++) {
1273296781Sdes			debug2("Server host certificate hostname: %s",
1274296781Sdes			    host_key->cert->principals[i]);
1275296781Sdes		}
1276296781Sdes	} else {
1277296781Sdes		debug("Server host key: %s %s", compat20 ?
1278296781Sdes		    sshkey_ssh_name(host_key) : sshkey_type(host_key), fp);
1279296781Sdes	}
1280295367Sdes
1281295367Sdes	if (sshkey_equal(previous_host_key, host_key)) {
1282295367Sdes		debug2("%s: server host key %s %s matches cached key",
1283295367Sdes		    __func__, sshkey_type(host_key), fp);
1284295367Sdes		r = 0;
1285295367Sdes		goto out;
1286295367Sdes	}
1287295367Sdes
1288295367Sdes	/* Check in RevokedHostKeys file if specified */
1289295367Sdes	if (options.revoked_host_keys != NULL) {
1290295367Sdes		r = sshkey_check_revoked(host_key, options.revoked_host_keys);
1291295367Sdes		switch (r) {
1292295367Sdes		case 0:
1293295367Sdes			break; /* not revoked */
1294295367Sdes		case SSH_ERR_KEY_REVOKED:
1295295367Sdes			error("Host key %s %s revoked by file %s",
1296295367Sdes			    sshkey_type(host_key), fp,
1297295367Sdes			    options.revoked_host_keys);
1298295367Sdes			r = -1;
1299295367Sdes			goto out;
1300295367Sdes		default:
1301295367Sdes			error("Error checking host key %s %s in "
1302295367Sdes			    "revoked keys file %s: %s", sshkey_type(host_key),
1303295367Sdes			    fp, options.revoked_host_keys, ssh_err(r));
1304295367Sdes			r = -1;
1305295367Sdes			goto out;
1306295367Sdes		}
1307295367Sdes	}
1308295367Sdes
1309285976Sdelphij	if (options.verify_host_key_dns) {
1310285976Sdelphij		/*
1311285976Sdelphij		 * XXX certs are not yet supported for DNS, so downgrade
1312285976Sdelphij		 * them and try the plain key.
1313285976Sdelphij		 */
1314295367Sdes		if ((r = sshkey_from_private(host_key, &plain)) != 0)
1315295367Sdes			goto out;
1316295367Sdes		if (sshkey_is_cert(plain))
1317295367Sdes			sshkey_drop_cert(plain);
1318285976Sdelphij		if (verify_host_key_dns(host, hostaddr, plain, &flags) == 0) {
1319285976Sdelphij			if (flags & DNS_VERIFY_FOUND) {
1320285976Sdelphij				if (options.verify_host_key_dns == 1 &&
1321285976Sdelphij				    flags & DNS_VERIFY_MATCH &&
1322285976Sdelphij				    flags & DNS_VERIFY_SECURE) {
1323295367Sdes					r = 0;
1324295367Sdes					goto out;
1325285976Sdelphij				}
1326285976Sdelphij				if (flags & DNS_VERIFY_MATCH) {
1327285976Sdelphij					matching_host_key_dns = 1;
1328285976Sdelphij				} else {
1329285976Sdelphij					warn_changed_key(plain);
1330285976Sdelphij					error("Update the SSHFP RR in DNS "
1331285976Sdelphij					    "with the new host key to get rid "
1332285976Sdelphij					    "of this message.");
1333285976Sdelphij				}
1334126277Sdes			}
1335124211Sdes		}
1336124211Sdes	}
1337295367Sdes	r = check_host_key(host, hostaddr, options.port, host_key, RDRW,
1338226046Sdes	    options.user_hostfiles, options.num_user_hostfiles,
1339226046Sdes	    options.system_hostfiles, options.num_system_hostfiles);
1340295367Sdes
1341295367Sdesout:
1342295367Sdes	sshkey_free(plain);
1343295367Sdes	free(fp);
1344296781Sdes	free(cafp);
1345295367Sdes	if (r == 0 && host_key != NULL) {
1346295367Sdes		key_free(previous_host_key);
1347295367Sdes		previous_host_key = key_from_private(host_key);
1348295367Sdes	}
1349295367Sdes
1350295367Sdes	return r;
135157565Smarkm}
135257565Smarkm
135357429Smarkm/*
135460576Skris * Starts a dialog with the server, and authenticates the current user on the
135560576Skris * server.  This does not need any extra privileges.  The basic connection
135660576Skris * to the server must already have been established before this is called.
135760576Skris * If login fails, this function prints an error and never returns.
135860576Skris * This function does not require super-user privileges.
135957429Smarkm */
136057429Smarkmvoid
136198684Sdesssh_login(Sensitive *sensitive, const char *orighost,
1362221420Sdes    struct sockaddr *hostaddr, u_short port, struct passwd *pw, int timeout_ms)
136357429Smarkm{
1364262566Sdes	char *host;
136560576Skris	char *server_user, *local_user;
136657429Smarkm
136757429Smarkm	local_user = xstrdup(pw->pw_name);
136857429Smarkm	server_user = options.user ? options.user : local_user;
136957429Smarkm
137057429Smarkm	/* Convert the user-supplied hostname into all lowercase. */
137157429Smarkm	host = xstrdup(orighost);
1372262566Sdes	lowercase(host);
137357429Smarkm
137457429Smarkm	/* Exchange protocol version identification strings with the server. */
1375181111Sdes	ssh_exchange_identification(timeout_ms);
137657429Smarkm
137757429Smarkm	/* Put the connection into non-blocking mode. */
137857429Smarkm	packet_set_nonblocking();
137957429Smarkm
138057429Smarkm	/* key exchange */
138157429Smarkm	/* authenticate user */
1382295367Sdes	debug("Authenticating to %s:%d as '%s'", host, port, server_user);
138360576Skris	if (compat20) {
1384221420Sdes		ssh_kex2(host, hostaddr, port);
138598684Sdes		ssh_userauth2(local_user, server_user, host, sensitive);
138660576Skris	} else {
1387295367Sdes#ifdef WITH_SSH1
138860576Skris		ssh_kex(host, hostaddr);
138998684Sdes		ssh_userauth1(local_user, server_user, host, sensitive);
1390295367Sdes#else
1391295367Sdes		fatal("ssh1 is not supported");
1392295367Sdes#endif
139360576Skris	}
1394255767Sdes	free(local_user);
139557429Smarkm}
139674500Sgreen
139774500Sgreenvoid
139874500Sgreenssh_put_password(char *password)
139974500Sgreen{
140074500Sgreen	int size;
140174500Sgreen	char *padded;
140274500Sgreen
140376262Sgreen	if (datafellows & SSH_BUG_PASSWORDPAD) {
140492559Sdes		packet_put_cstring(password);
140576262Sgreen		return;
140676262Sgreen	}
140774500Sgreen	size = roundup(strlen(password) + 1, 32);
1408162856Sdes	padded = xcalloc(1, size);
140974500Sgreen	strlcpy(padded, password, size);
141074500Sgreen	packet_put_string(padded, size);
1411264377Sdes	explicit_bzero(padded, size);
1412255767Sdes	free(padded);
141374500Sgreen}
1414106130Sdes
1415221420Sdes/* print all known host keys for a given host, but skip keys of given type */
1416106130Sdesstatic int
1417221420Sdesshow_other_keys(struct hostkeys *hostkeys, Key *key)
1418106130Sdes{
1419262566Sdes	int type[] = {
1420262566Sdes		KEY_RSA1,
1421262566Sdes		KEY_RSA,
1422262566Sdes		KEY_DSA,
1423262566Sdes		KEY_ECDSA,
1424262566Sdes		KEY_ED25519,
1425262566Sdes		-1
1426262566Sdes	};
1427221420Sdes	int i, ret = 0;
1428181111Sdes	char *fp, *ra;
1429221420Sdes	const struct hostkey_entry *found;
1430106130Sdes
1431106130Sdes	for (i = 0; type[i] != -1; i++) {
1432106130Sdes		if (type[i] == key->type)
1433106130Sdes			continue;
1434221420Sdes		if (!lookup_key_in_hostkeys_by_type(hostkeys, type[i], &found))
1435106130Sdes			continue;
1436295367Sdes		fp = sshkey_fingerprint(found->key,
1437295367Sdes		    options.fingerprint_hash, SSH_FP_DEFAULT);
1438295367Sdes		ra = sshkey_fingerprint(found->key,
1439295367Sdes		    options.fingerprint_hash, SSH_FP_RANDOMART);
1440295367Sdes		if (fp == NULL || ra == NULL)
1441295367Sdes			fatal("%s: sshkey_fingerprint fail", __func__);
1442221420Sdes		logit("WARNING: %s key found for host %s\n"
1443221420Sdes		    "in %s:%lu\n"
1444221420Sdes		    "%s key fingerprint %s.",
1445221420Sdes		    key_type(found->key),
1446221420Sdes		    found->host, found->file, found->line,
1447221420Sdes		    key_type(found->key), fp);
1448221420Sdes		if (options.visual_host_key)
1449221420Sdes			logit("%s", ra);
1450255767Sdes		free(ra);
1451255767Sdes		free(fp);
1452221420Sdes		ret = 1;
1453106130Sdes	}
1454221420Sdes	return ret;
1455106130Sdes}
1456126277Sdes
1457126277Sdesstatic void
1458126277Sdeswarn_changed_key(Key *host_key)
1459126277Sdes{
1460126277Sdes	char *fp;
1461126277Sdes
1462295367Sdes	fp = sshkey_fingerprint(host_key, options.fingerprint_hash,
1463295367Sdes	    SSH_FP_DEFAULT);
1464295367Sdes	if (fp == NULL)
1465295367Sdes		fatal("%s: sshkey_fingerprint fail", __func__);
1466126277Sdes
1467126277Sdes	error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
1468126277Sdes	error("@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @");
1469126277Sdes	error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
1470126277Sdes	error("IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!");
1471126277Sdes	error("Someone could be eavesdropping on you right now (man-in-the-middle attack)!");
1472221420Sdes	error("It is also possible that a host key has just been changed.");
1473126277Sdes	error("The fingerprint for the %s key sent by the remote host is\n%s.",
1474221420Sdes	    key_type(host_key), fp);
1475126277Sdes	error("Please contact your system administrator.");
1476126277Sdes
1477255767Sdes	free(fp);
1478126277Sdes}
1479157019Sdes
1480157019Sdes/*
1481157019Sdes * Execute a local command
1482157019Sdes */
1483157019Sdesint
1484157019Sdesssh_local_cmd(const char *args)
1485157019Sdes{
1486157019Sdes	char *shell;
1487157019Sdes	pid_t pid;
1488157019Sdes	int status;
1489221420Sdes	void (*osighand)(int);
1490157019Sdes
1491157019Sdes	if (!options.permit_local_command ||
1492157019Sdes	    args == NULL || !*args)
1493157019Sdes		return (1);
1494157019Sdes
1495221420Sdes	if ((shell = getenv("SHELL")) == NULL || *shell == '\0')
1496157019Sdes		shell = _PATH_BSHELL;
1497157019Sdes
1498221420Sdes	osighand = signal(SIGCHLD, SIG_DFL);
1499157019Sdes	pid = fork();
1500157019Sdes	if (pid == 0) {
1501221420Sdes		signal(SIGPIPE, SIG_DFL);
1502157019Sdes		debug3("Executing %s -c \"%s\"", shell, args);
1503157019Sdes		execl(shell, shell, "-c", args, (char *)NULL);
1504157019Sdes		error("Couldn't execute %s -c \"%s\": %s",
1505157019Sdes		    shell, args, strerror(errno));
1506157019Sdes		_exit(1);
1507157019Sdes	} else if (pid == -1)
1508157019Sdes		fatal("fork failed: %.100s", strerror(errno));
1509157019Sdes	while (waitpid(pid, &status, 0) == -1)
1510157019Sdes		if (errno != EINTR)
1511157019Sdes			fatal("Couldn't wait for child: %s", strerror(errno));
1512221420Sdes	signal(SIGCHLD, osighand);
1513157019Sdes
1514157019Sdes	if (!WIFEXITED(status))
1515157019Sdes		return (1);
1516157019Sdes
1517157019Sdes	return (WEXITSTATUS(status));
1518157019Sdes}
1519296781Sdes
1520296781Sdesvoid
1521296781Sdesmaybe_add_key_to_agent(char *authfile, Key *private, char *comment,
1522296781Sdes    char *passphrase)
1523296781Sdes{
1524296781Sdes	int auth_sock = -1, r;
1525296781Sdes
1526296781Sdes	if (options.add_keys_to_agent == 0)
1527296781Sdes		return;
1528296781Sdes
1529296781Sdes	if ((r = ssh_get_authentication_socket(&auth_sock)) != 0) {
1530296781Sdes		debug3("no authentication agent, not adding key");
1531296781Sdes		return;
1532296781Sdes	}
1533296781Sdes
1534296781Sdes	if (options.add_keys_to_agent == 2 &&
1535296781Sdes	    !ask_permission("Add key %s (%s) to agent?", authfile, comment)) {
1536296781Sdes		debug3("user denied adding this key");
1537296781Sdes		return;
1538296781Sdes	}
1539296781Sdes
1540296781Sdes	if ((r = ssh_add_identity_constrained(auth_sock, private, comment, 0,
1541296781Sdes	    (options.add_keys_to_agent == 3))) == 0)
1542296781Sdes		debug("identity added to agent: %s", authfile);
1543296781Sdes	else
1544296781Sdes		debug("could not add identity to agent: %s (%d)", authfile, r);
1545296781Sdes}
1546