[gsi-openssh/el6] Based on openssh-5.3p1-104.el6

Mattias Ellert ellert at fedoraproject.org
Wed Oct 22 16:58:54 UTC 2014


commit 8c3cdb5ce9167b55dc947a13bbe6a2da52293501
Author: Mattias Ellert <mattias.ellert at fysast.uu.se>
Date:   Wed Oct 22 18:58:16 2014 +0200

    Based on openssh-5.3p1-104.el6

 gsi-openssh.spec                                   |   60 +-
 gsisshd.init                                       |    2 +-
 openssh-5.3p1-CVE-2014-2653.patch                  |   76 +
 ...ersist-avoid-race-between-bind-and-listen.patch |  108 +
 openssh-5.3p1-ControlPersist.patch                 | 3338 ++++++++++++++++
 openssh-5.3p1-FIPS-mode-SP800-131A.patch           |  236 ++
 openssh-5.3p1-audit.patch                          |   41 +-
 openssh-5.3p1-ecdsa-ecdh.patch                     | 4105 ++++++++++++++++++++
 openssh-5.3p1-fips-dont-load-rsa1-keys.patch       |   16 +
 openssh-5.3p1-fips-syslog.patch                    |   24 +
 openssh-5.3p1-fips.patch                           |   45 +-
 openssh-5.3p1-fix-several-coverity-issues.patch    |   93 +
 openssh-5.3p1-gsissh.patch                         |    2 +-
 openssh-5.3p1-gsskex-fips.patch                    |   59 +
 openssh-5.3p1-ignore-SIGXFSZ.patch                 |   14 +
 openssh-5.3p1-ignore-bad-env-var.patch             |  136 +
 openssh-5.3p1-log-sftp-only-connections.patch      |   12 +
 openssh-5.3p1-restore-oom-after-restart.patch      |   58 +
 openssh-5.3p1-sigpipe.patch                        |   13 +
 openssh-5.3p1-skip-pin-for-ssh-add-e.patch         |   55 +
 openssh-5.3p1-ssh-certificates.patch               |    8 +-
 openssh-5.3p1-ssh-keygen-V-fix.patch               |   32 +
 openssh-5.3p1-x11-for-non-linux-platforms.patch    |   17 +
 openssh-5.3p1-x11-getaddrinfo.patch                |   22 +
 24 files changed, 8526 insertions(+), 46 deletions(-)
---
diff --git a/gsi-openssh.spec b/gsi-openssh.spec
index d312333..4fa8f78 100644
--- a/gsi-openssh.spec
+++ b/gsi-openssh.spec
@@ -35,7 +35,7 @@
 %global nologin 1
 
 %global openssh_ver 5.3p1
-%global openssh_rel 10
+%global openssh_rel 11
 
 Summary: An implementation of the SSH protocol with GSI authentication
 Name: gsi-openssh
@@ -136,6 +136,43 @@ Patch109: openssh-5.3p1-drop-internal-sftp-connections.patch
 Patch110: openssh-5.3p1-gssapi-with-poly-tmp.patch
 # Change default of MaxStartups to 10:30:100 (#908707)
 Patch111: openssh-5.3p1-change-max-startups.patch
+# FIPS mode - adjust the key echange DH groups and ssh-keygen according to SP800-131A (#993580)
+Patch120: openssh-5.3p1-FIPS-mode-SP800-131A.patch
+# ECDSA and ECDH support (#1028335)
+Patch121: openssh-5.3p1-ecdsa-ecdh.patch
+# fix segfault in GSSAPI key exchange in FIPS mode
+Patch122: openssh-5.3p1-gsskex-fips.patch
+# log fipscheck verification message into syslog authpriv (#1020803)
+Patch123: openssh-5.3p1-fips-syslog.patch
+# Prevents a server from skipping SSHFP lookup and forcing a new-hostkey
+# dialog by offering only certificate keys. (#1081338)
+Patch124: openssh-5.3p1-CVE-2014-2653.patch
+# ignore environment variables with embedded '=' or '\0' characters (#1077843)
+Patch125: openssh-5.3p1-ignore-bad-env-var.patch
+# backport ControlPersist option (#953088)
+Patch126: openssh-5.3p1-ControlPersist.patch
+# log when a client requests an interactive session and only sftp is allowed (#997377)
+Patch127: openssh-5.3p1-log-sftp-only-connections.patch
+# don't try to load RSA1 host key in FIPS mode (#1009959)
+Patch128: openssh-5.3p1-fips-dont-load-rsa1-keys.patch
+# restore Linux oom_adj setting when handling SIGHUP to maintain behaviour over restart (#1010429)
+Patch129: openssh-5.3p1-restore-oom-after-restart.patch
+# ssh-keygen -V - relative-specified certificate expiry time should be relative to current time (#1022459)
+Patch130: openssh-5.3p1-ssh-keygen-V-fix.patch
+# look for x11 forward sockets with AI_ADDRCONFIG flag getaddrinfo (#1027197)
+Patch131: openssh-5.3p1-x11-getaddrinfo.patch
+# fix openssh-5.3p1-x11.patch for non-linux platforms (#1100913)
+Patch132: openssh-5.3p1-x11-for-non-linux-platforms.patch
+# fix several coverity issue (#876544)
+Patch133: openssh-5.3p1-fix-several-coverity-issues.patch
+# skip requesting smartcard PIN when removing keys from agent (#1042519)
+Patch134: openssh-5.3p1-skip-pin-for-ssh-add-e.patch
+# fix race in backported ControlPersist patch (#953088)
+Patch135: openssh-5.3p1-ControlPersist-avoid-race-between-bind-and-listen.patch
+# ignore SIGPIPE in ssh-keyscan (#1108836)
+Patch136: openssh-5.3p1-sigpipe.patch
+# Ignore SIGXFSZ in postauth monitor child (#1133906)
+Patch137: openssh-5.3p1-ignore-SIGXFSZ.patch
 
 # This is the patch that adds GSI support
 # Based on http://grid.ncsa.illinois.edu/ssh/dl/patch/openssh-5.3p1.patch
@@ -307,6 +344,24 @@ This version of OpenSSH has been modified to support GSI authentication.
 %patch109 -p1 -b .drop-internal-sftp
 %patch110 -p1 -b .gssapi-poly-tmp
 %patch111 -p1 -b .max-startups
+%patch120 -p1 -b .SP800-131A
+%patch121 -p1 -b .ecdsa-ecdh
+%patch122 -p1 -b .gsskex-fips
+%patch123 -p1 -b .fips-syslog
+%patch124 -p1 -b .CVE-2014-2653
+%patch125 -p1 -b .bad-env-var
+%patch126 -p1 -b .ControlPersist
+%patch127 -p1 -b .997377
+%patch128 -p1 -b .1009959
+%patch129 -p1 -b .1010429
+%patch130 -p1 -b .1022459
+%patch131 -p1 -b .1027197
+%patch132 -p1 -b .1100913
+%patch133 -p1 -b .876544
+%patch134 -p1 -b .1042519
+%patch135 -p1 -b .ControlPersist-race
+%patch136 -p1 -b .sigpipe
+%patch137 -p1 -b .SIGXFSZ
 
 %patch200 -p1 -b .gsi
 
@@ -512,6 +567,9 @@ fi
 %attr(0640,root,root) %config(noreplace) /etc/sysconfig/gsisshd
 
 %changelog
+* Wed Oct 22 2014 Mattias Ellert <mattias.ellert at fysast.uu.se> - 5.3p1-11
+- Based on openssh-5.3p1-104.el6
+
 * Tue Nov 26 2013 Mattias Ellert <mattias.ellert at fysast.uu.se> - 5.3p1-10
 - Based on openssh-5.3p1-94.el6
 
diff --git a/gsisshd.init b/gsisshd.init
index c85cd84..ee6cfa2 100755
--- a/gsisshd.init
+++ b/gsisshd.init
@@ -94,7 +94,7 @@ do_rsa_keygen() {
 }
 
 do_dsa_keygen() {
-	if [ ! -s $DSA_KEY ]; then
+	if [ ! -s $DSA_KEY -a `fips_enabled` -eq 0 ]; then
 		echo -n $"Generating SSH2 DSA host key: "
 		rm -f $DSA_KEY
 		if test ! -f $DSA_KEY && $KEYGEN -q -t dsa -f $DSA_KEY -C '' -N '' >&/dev/null; then
diff --git a/openssh-5.3p1-CVE-2014-2653.patch b/openssh-5.3p1-CVE-2014-2653.patch
new file mode 100644
index 0000000..0c554b6
--- /dev/null
+++ b/openssh-5.3p1-CVE-2014-2653.patch
@@ -0,0 +1,76 @@
+diff --git a/ChangeLog b/ChangeLog
+index e1ab9ca..b7a4563 100644
+--- a/ChangeLog
++++ b/ChangeLog
+@@ -1,3 +1,14 @@
++20140420
++   - djm at cvs.openbsd.org 2014/04/01 03:34:10
++     [sshconnect.c]
++     When using VerifyHostKeyDNS with a DNSSEC resolver, down-convert any
++     certificate keys to plain keys and attempt SSHFP resolution.
++     
++     Prevents a server from skipping SSHFP lookup and forcing a new-hostkey
++     dialog by offering only certificate keys.
++     
++     Reported by mcv21 AT cam.ac.uk
++
+ 20131109
+  - (dtucker) [configure.ac kex.c key.c myproposal.h] Test for the presence of
+    NID_X9_62_prime256v1, NID_secp384r1 and NID_secp521r1 and test that the
+diff --git a/sshconnect.c b/sshconnect.c
+index 8b601fd..0e04a1b 100644
+--- a/sshconnect.c
++++ b/sshconnect.c
+@@ -1056,26 +1056,35 @@ verify_host_key(char *host, struct sockaddr *hostaddr, Key *host_key)
+ {
+ 	struct stat st;
+ 	int flags = 0;
++	Key *plain = NULL;
+ 
+-	/* XXX certs are not yet supported for DNS */
+-	if (!key_is_cert(host_key) && options.verify_host_key_dns &&
+-	    verify_host_key_dns(host, hostaddr, host_key, &flags) == 0) {
+-
+-		if (flags & DNS_VERIFY_FOUND) {
+-
+-			if (options.verify_host_key_dns == 1 &&
+-			    flags & DNS_VERIFY_MATCH &&
+-			    flags & DNS_VERIFY_SECURE)
+-				return 0;
+-
+-			if (flags & DNS_VERIFY_MATCH) {
+-				matching_host_key_dns = 1;
+-			} else {
+-				warn_changed_key(host_key);
+-				error("Update the SSHFP RR in DNS with the new "
+-				    "host key to get rid of this message.");
++	if (options.verify_host_key_dns) {
++		/*
++		 * XXX certs are not yet supported for DNS, so downgrade
++		 * them and try the plain key.
++		 */
++		plain = key_from_private(host_key);
++		if (key_is_cert(plain))
++			key_drop_cert(plain);
++		if (verify_host_key_dns(host, hostaddr, plain, &flags) == 0) {
++			if (flags & DNS_VERIFY_FOUND) {
++				if (options.verify_host_key_dns == 1 &&
++				    flags & DNS_VERIFY_MATCH &&
++				    flags & DNS_VERIFY_SECURE) {
++					key_free(plain);
++					return 0;
++				}
++				if (flags & DNS_VERIFY_MATCH) {
++					matching_host_key_dns = 1;
++				} else {
++					warn_changed_key(plain);
++					error("Update the SSHFP RR in DNS "
++					    "with the new host key to get rid "
++					    "of this message.");
++				}
+ 			}
+ 		}
++		key_free(plain);
+ 	}
+ 
+ 	/* return ok if the key can be found in an old keyfile */
diff --git a/openssh-5.3p1-ControlPersist-avoid-race-between-bind-and-listen.patch b/openssh-5.3p1-ControlPersist-avoid-race-between-bind-and-listen.patch
new file mode 100644
index 0000000..ca1b093
--- /dev/null
+++ b/openssh-5.3p1-ControlPersist-avoid-race-between-bind-and-listen.patch
@@ -0,0 +1,108 @@
+diff --git a/mux.c b/mux.c
+index 87d28ae..0c17d5e 100644
+--- a/mux.c
++++ b/mux.c
+@@ -940,6 +940,9 @@ muxserver_listen(void)
+ 	struct sockaddr_un addr;
+ 	socklen_t sun_len;
+ 	mode_t old_umask;
++	char *orig_control_path = options.control_path;
++	char rbuf[16+1];
++	u_int i, r;
+ 
+ 	if (options.control_path == NULL ||
+ 	    options.control_master == SSHCTL_MASTER_NO)
+@@ -947,27 +950,50 @@ muxserver_listen(void)
+ 
+ 	debug("setting up multiplex master socket");
+ 
++	/*
++	 * Use a temporary path before listen so we can pseudo-atomically
++	 * establish the listening socket in its final location to avoid
++	 * other processes racing in between bind() and listen() and hitting
++	 * an unready socket.
++	 */
++	for (i = 0; i < sizeof(rbuf) - 1; i++) {
++		r = arc4random_uniform(26+26+10);
++		rbuf[i] = (r < 26) ? 'a' + r :
++		    (r < 26*2) ? 'A' + r - 26 :
++		    '0' + r - 26 - 26;
++	}
++	rbuf[sizeof(rbuf) - 1] = '\0';
++	options.control_path = NULL;
++	xasprintf(&options.control_path, "%s.%s", orig_control_path, rbuf);
++	debug3("%s: temporary control path %s", __func__, options.control_path);
++
+ 	memset(&addr, '\0', sizeof(addr));
+ 	addr.sun_family = AF_UNIX;
+ 	sun_len = offsetof(struct sockaddr_un, sun_path) +
+ 	    strlen(options.control_path) + 1;
+ 
+ 	if (strlcpy(addr.sun_path, options.control_path,
+-	    sizeof(addr.sun_path)) >= sizeof(addr.sun_path))
+-		fatal("ControlPath too long");
++	    sizeof(addr.sun_path)) >= sizeof(addr.sun_path)) {
++		error("ControlPath \"%s\" too long for Unix domain socket",
++		    options.control_path);
++		goto disable_mux_master;
++	}
+ 
+ 	if ((muxserver_sock = socket(PF_UNIX, SOCK_STREAM, 0)) < 0)
+ 		fatal("%s socket(): %s", __func__, strerror(errno));
+ 
+ 	old_umask = umask(0177);
+ 	if (bind(muxserver_sock, (struct sockaddr *)&addr, sun_len) == -1) {
+-		muxserver_sock = -1;
+ 		if (errno == EINVAL || errno == EADDRINUSE) {
+ 			error("ControlSocket %s already exists, "
+ 			    "disabling multiplexing", options.control_path);
+-			close(muxserver_sock);
+-			muxserver_sock = -1;
+-			xfree(options.control_path);
++ disable_mux_master:
++			if (muxserver_sock != -1) {
++				close(muxserver_sock);
++				muxserver_sock = -1;
++			}
++			free(orig_control_path);
++			free(options.control_path);
+ 			options.control_path = NULL;
+ 			options.control_master = SSHCTL_MASTER_NO;
+ 			return;
+@@ -979,6 +1005,22 @@ muxserver_listen(void)
+ 	if (listen(muxserver_sock, 64) == -1)
+ 		fatal("%s listen(): %s", __func__, strerror(errno));
+ 
++	/* Now atomically "move" the mux socket into position */
++	if (link(options.control_path, orig_control_path) != 0) {
++		if (errno != EEXIST) {
++			fatal("%s: link mux listener %s => %s: %s", __func__, 
++			    options.control_path, orig_control_path,
++			    strerror(errno));
++		}
++		error("ControlSocket %s already exists, disabling multiplexing",
++		    orig_control_path);
++		unlink(options.control_path);
++		goto disable_mux_master;
++	}
++	unlink(options.control_path);
++	free(options.control_path);
++	options.control_path = orig_control_path;
++
+ 	set_nonblock(muxserver_sock);
+ 
+ 	mux_listener_channel = channel_new("mux listener",
+diff --git a/ssh.c b/ssh.c
+index acc522a..94465e4 100644
+--- a/ssh.c
++++ b/ssh.c
+@@ -1422,9 +1422,6 @@ ssh_session2(void)
+ 	    options.permit_local_command)
+ 		ssh_local_cmd(options.local_command);
+ 
+-	/* Start listening for multiplex clients */
+-	muxserver_listen();
+-
+ 	/* If requested, let ssh continue in the background. */
+ 	if (fork_after_authentication_flag) {
+ 		fork_after_authentication_flag = 0;
diff --git a/openssh-5.3p1-ControlPersist.patch b/openssh-5.3p1-ControlPersist.patch
new file mode 100644
index 0000000..0005a02
--- /dev/null
+++ b/openssh-5.3p1-ControlPersist.patch
@@ -0,0 +1,3338 @@
+diff --git a/ChangeLog b/ChangeLog
+index c25fb32..3304206 100644
+--- a/ChangeLog
++++ b/ChangeLog
+@@ -22,6 +22,14 @@
+    latter actually works before using it.  Fedora (at least) has NID_secp521r1
+    that doesn't work (see https://bugzilla.redhat.com/show_bug.cgi?id=1021897).
+ 
++20111104
++ - (dtucker) OpenBSD CVS Sync
++   - djm at cvs.openbsd.org 2011/10/24 02:10:46
++     [ssh.c]
++     bz#1943: unbreak stdio forwarding when ControlPersist is in user - ssh
++     was incorrectly requesting the forward in both the control master and
++     slave. skip requesting it in the master to fix. ok markus@
++
+ 20101021
+    - djm at cvs.openbsd.org 2010/08/31 12:24:09
+      [regress/cert-hostkey.sh regress/cert-userkey.sh]
+@@ -74,6 +82,9 @@
+    platforms that don't have the requisite OpenSSL support. ok dtucker@
+  - (dtucker) [kex.h key.c packet.h ssh-agent.c ssh.c] A few more ECC ifdefs
+    for missing headers and compiler warnings.
++ - markus at cvs.openbsd.org 2010/09/02 16:08:39
++     [ssh.c]
++     unbreak ControlPersist=yes for ControlMaster=yes; ok djm@
+ 
+ 20100831
+    - djm at cvs.openbsd.org 2010/08/31 11:54:45
+@@ -103,6 +114,32 @@
+  - (djm) [bufec.c kexecdh.c kexecdhc.c kexecdhs.c ssh-ecdsa.c] include
+    includes.h
+ 
++20100816
++ - OpenBSD CVS Sync
++   - djm at cvs.openbsd.org 2010/08/12 21:49:44
++     [ssh.c]
++     close any extra file descriptors inherited from parent at start and
++     reopen stdin/stdout to /dev/null when forking for ControlPersist.
++     
++     prevents tools that fork and run a captive ssh for communication from
++     failing to exit when the ssh completes while they wait for these fds to
++     close. The inherited fds may persist arbitrarily long if a background
++     mux master has been started by ControlPersist. cvs and scp were effected
++     by this.
++     
++     "please commit" markus@
++
++20100803
++ - OpenBSD CVS Sync
++   - djm at cvs.openbsd.org 2010/07/19 09:15:12
++     [clientloop.c readconf.c readconf.h ssh.c ssh_config.5]
++     add a "ControlPersist" option that automatically starts a background
++     ssh(1) multiplex master when connecting. This connection can stay alive
++     indefinitely, or can be set to automatically close after a user-specified
++     duration of inactivity. bz#1330 - patch by dwmw2 AT infradead.org, but
++     further hacked on by wmertens AT cisco.com, apb AT cequrux.com,
++     martin-mindrot-bugzilla AT earth.li and myself; "looks ok" markus@
++
+ 20100521
+  - (djm) OpenBSD CVS Sync
+    - djm at cvs.openbsd.org 2010/05/07 11:31:26
+@@ -295,6 +332,32 @@
+     [Makefile regress/cert-hostkey.sh regress/cert-userkey.sh]
+     regression tests for certified keys
+ 
++20100126
++ - (djm) OpenBSD CVS Sync
++   - djm at cvs.openbsd.org 2010/01/26 01:28:35
++     [channels.c channels.h clientloop.c clientloop.h mux.c nchan.c ssh.c]
++     rewrite ssh(1) multiplexing code to a more sensible protocol.
++     
++     The new multiplexing code uses channels for the listener and
++     accepted control sockets to make the mux master non-blocking, so
++     no stalls when processing messages from a slave.
++     
++     avoid use of fatal() in mux master protocol parsing so an errant slave
++     process cannot take down a running master.
++     
++     implement requesting of port-forwards over multiplexed sessions. Any
++     port forwards requested by the slave are added to those the master has
++     established.
++     
++     add support for stdio forwarding ("ssh -W host:port ...") in mux slaves.
++     
++     document master/slave mux protocol so that other tools can use it to
++     control a running ssh(1). Note: there are no guarantees that this
++     protocol won't be incompatibly changed (though it is versioned).
++     
++     feedback Salvador Fandino, dtucker@
++     channel changes ok markus@
++
+ 20100211
+   - markus at cvs.openbsd.org 2010/02/08 10:50:20
+     [pathnames.h readconf.c readconf.h scp.1 sftp.1 ssh-add.1 ssh-add.c]
+diff --git a/channels.c b/channels.c
+index 1da940a..f60bf84 100644
+--- a/channels.c
++++ b/channels.c
+@@ -246,7 +246,6 @@ channel_register_fds(Channel *c, int rfd, int wfd, int efd,
+ 	c->rfd = rfd;
+ 	c->wfd = wfd;
+ 	c->sock = (rfd == wfd) ? rfd : -1;
+-	c->ctl_fd = -1; /* XXX: set elsewhere */
+ 	c->efd = efd;
+ 	c->extended_usage = extusage;
+ 
+@@ -335,6 +334,9 @@ channel_new(char *ctype, int type, int rfd, int wfd, int efd,
+ 	c->output_filter = NULL;
+ 	c->filter_ctx = NULL;
+ 	c->filter_cleanup = NULL;
++	c->ctl_chan = -1;
++	c->mux_rcb = NULL;
++	c->mux_ctx = NULL;
+ 	TAILQ_INIT(&c->status_confirms);
+ 	debug("channel %d: new [%s]", found, remote_name);
+ 	return c;
+@@ -376,11 +378,10 @@ channel_close_fd(int *fdp)
+ static void
+ channel_close_fds(Channel *c)
+ {
+-	debug3("channel %d: close_fds r %d w %d e %d c %d",
+-	    c->self, c->rfd, c->wfd, c->efd, c->ctl_fd);
++	debug3("channel %d: close_fds r %d w %d e %d",
++	    c->self, c->rfd, c->wfd, c->efd);
+ 
+ 	channel_close_fd(&c->sock);
+-	channel_close_fd(&c->ctl_fd);
+ 	channel_close_fd(&c->rfd);
+ 	channel_close_fd(&c->wfd);
+ 	channel_close_fd(&c->efd);
+@@ -406,8 +407,6 @@ channel_free(Channel *c)
+ 
+ 	if (c->sock != -1)
+ 		shutdown(c->sock, SHUT_RDWR);
+-	if (c->ctl_fd != -1)
+-		shutdown(c->ctl_fd, SHUT_RDWR);
+ 	channel_close_fds(c);
+ 	buffer_free(&c->input);
+ 	buffer_free(&c->output);
+@@ -529,6 +528,7 @@ channel_still_open(void)
+ 		case SSH_CHANNEL_X11_LISTENER:
+ 		case SSH_CHANNEL_PORT_LISTENER:
+ 		case SSH_CHANNEL_RPORT_LISTENER:
++		case SSH_CHANNEL_MUX_LISTENER:
+ 		case SSH_CHANNEL_CLOSED:
+ 		case SSH_CHANNEL_AUTH_SOCKET:
+ 		case SSH_CHANNEL_DYNAMIC:
+@@ -542,6 +542,7 @@ channel_still_open(void)
+ 		case SSH_CHANNEL_OPENING:
+ 		case SSH_CHANNEL_OPEN:
+ 		case SSH_CHANNEL_X11_OPEN:
++		case SSH_CHANNEL_MUX_CLIENT:
+ 			return 1;
+ 		case SSH_CHANNEL_INPUT_DRAINING:
+ 		case SSH_CHANNEL_OUTPUT_DRAINING:
+@@ -573,6 +574,8 @@ channel_find_open(void)
+ 		case SSH_CHANNEL_X11_LISTENER:
+ 		case SSH_CHANNEL_PORT_LISTENER:
+ 		case SSH_CHANNEL_RPORT_LISTENER:
++		case SSH_CHANNEL_MUX_LISTENER:
++		case SSH_CHANNEL_MUX_CLIENT:
+ 		case SSH_CHANNEL_OPENING:
+ 		case SSH_CHANNEL_CONNECTING:
+ 		case SSH_CHANNEL_ZOMBIE:
+@@ -623,6 +626,8 @@ channel_open_message(void)
+ 		case SSH_CHANNEL_CLOSED:
+ 		case SSH_CHANNEL_AUTH_SOCKET:
+ 		case SSH_CHANNEL_ZOMBIE:
++		case SSH_CHANNEL_MUX_CLIENT:
++		case SSH_CHANNEL_MUX_LISTENER:
+ 			continue;
+ 		case SSH_CHANNEL_LARVAL:
+ 		case SSH_CHANNEL_OPENING:
+@@ -633,12 +638,12 @@ channel_open_message(void)
+ 		case SSH_CHANNEL_INPUT_DRAINING:
+ 		case SSH_CHANNEL_OUTPUT_DRAINING:
+ 			snprintf(buf, sizeof buf,
+-			    "  #%d %.300s (t%d r%d i%d/%d o%d/%d fd %d/%d cfd %d)\r\n",
++			    "  #%d %.300s (t%d r%d i%d/%d o%d/%d fd %d/%d cc %d)\r\n",
+ 			    c->self, c->remote_name,
+ 			    c->type, c->remote_id,
+ 			    c->istate, buffer_len(&c->input),
+ 			    c->ostate, buffer_len(&c->output),
+-			    c->rfd, c->wfd, c->ctl_fd);
++			    c->rfd, c->wfd, c->ctl_chan);
+ 			buffer_append(&buffer, buf, strlen(buf));
+ 			continue;
+ 		default:
+@@ -846,9 +851,6 @@ channel_pre_open(Channel *c, fd_set *readset, fd_set *writeset)
+ 			FD_SET(c->efd, readset);
+ 	}
+ 	/* XXX: What about efd? races? */
+-	if (compat20 && c->ctl_fd != -1 &&
+-	    c->istate == CHAN_INPUT_OPEN && c->ostate == CHAN_OUTPUT_OPEN)
+-		FD_SET(c->ctl_fd, readset);
+ }
+ 
+ /* ARGSUSED */
+@@ -993,6 +995,28 @@ channel_pre_x11_open(Channel *c, fd_set *readset, fd_set *writeset)
+ 	}
+ }
+ 
++static void
++channel_pre_mux_client(Channel *c, fd_set *readset, fd_set *writeset)
++{
++	if (c->istate == CHAN_INPUT_OPEN &&
++	    buffer_check_alloc(&c->input, CHAN_RBUF))
++		FD_SET(c->rfd, readset);
++	if (c->istate == CHAN_INPUT_WAIT_DRAIN) {
++		/* clear buffer immediately (discard any partial packet) */
++		buffer_clear(&c->input);
++		chan_ibuf_empty(c);
++		/* Start output drain. XXX just kill chan? */
++		chan_rcvd_oclose(c);
++	}
++	if (c->ostate == CHAN_OUTPUT_OPEN ||
++	    c->ostate == CHAN_OUTPUT_WAIT_DRAIN) {
++		if (buffer_len(&c->output) > 0)
++			FD_SET(c->wfd, writeset);
++		else if (c->ostate == CHAN_OUTPUT_WAIT_DRAIN)
++			chan_obuf_empty(c);
++	}
++}
++
+ /* try to decode a socks4 header */
+ /* ARGSUSED */
+ static int
+@@ -1225,19 +1249,14 @@ channel_decode_socks5(Channel *c, fd_set *readset, fd_set *writeset)
+ }
+ 
+ Channel *
+-channel_connect_stdio_fwd(const char *host_to_connect, u_short port_to_connect)
++channel_connect_stdio_fwd(const char *host_to_connect, u_short port_to_connect,
++    int in, int out)
+ {
+ 	Channel *c;
+-	int in, out;
+ 
+ 	debug("channel_connect_stdio_fwd %s:%d", host_to_connect,
+ 	    port_to_connect);
+ 
+-	in = dup(STDIN_FILENO);
+-	out = dup(STDOUT_FILENO);
+-	if (in < 0 || out < 0)
+-		fatal("channel_connect_stdio_fwd: dup() in/out failed");
+-
+ 	c = channel_new("stdio-forward", SSH_CHANNEL_OPENING, in, out,
+ 	    -1, CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT,
+ 	    0, "stdio-forward", /*nonblock*/0);
+@@ -1775,36 +1794,6 @@ channel_handle_efd(Channel *c, fd_set *readset, fd_set *writeset)
+ 	return 1;
+ }
+ 
+-/* ARGSUSED */
+-static int
+-channel_handle_ctl(Channel *c, fd_set *readset, fd_set *writeset)
+-{
+-	char buf[16];
+-	int len;
+-
+-	/* Monitor control fd to detect if the slave client exits */
+-	if (c->ctl_fd != -1 && FD_ISSET(c->ctl_fd, readset)) {
+-		len = read(c->ctl_fd, buf, sizeof(buf));
+-		if (len < 0 &&
+-		    (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK))
+-			return 1;
+-		if (len <= 0) {
+-			debug2("channel %d: ctl read<=0", c->self);
+-			if (c->type != SSH_CHANNEL_OPEN) {
+-				debug2("channel %d: not open", c->self);
+-				chan_mark_dead(c);
+-				return -1;
+-			} else {
+-				chan_read_failed(c);
+-				chan_write_failed(c);
+-			}
+-			return -1;
+-		} else
+-			fatal("%s: unexpected data on ctl fd", __func__);
+-	}
+-	return 1;
+-}
+-
+ static int
+ channel_check_window(Channel *c)
+ {
+@@ -1837,10 +1826,131 @@ channel_post_open(Channel *c, fd_set *readset, fd_set *writeset)
+ 	if (!compat20)
+ 		return;
+ 	channel_handle_efd(c, readset, writeset);
+-	channel_handle_ctl(c, readset, writeset);
+ 	channel_check_window(c);
+ }
+ 
++static u_int
++read_mux(Channel *c, u_int need)
++{
++	char buf[CHAN_RBUF];
++	int len;
++	u_int rlen;
++
++	if (buffer_len(&c->input) < need) {
++		rlen = need - buffer_len(&c->input);
++		len = read(c->rfd, buf, MIN(rlen, CHAN_RBUF));
++		if (len <= 0) {
++			if (errno != EINTR && errno != EAGAIN) {
++				debug2("channel %d: ctl read<=0 rfd %d len %d",
++				    c->self, c->rfd, len);
++				chan_read_failed(c);
++				return 0;
++			}
++		} else
++			buffer_append(&c->input, buf, len);
++	}
++	return buffer_len(&c->input);
++}
++
++static void
++channel_post_mux_client(Channel *c, fd_set *readset, fd_set *writeset)
++{
++	u_int need;
++	ssize_t len;
++
++	if (!compat20)
++		fatal("%s: entered with !compat20", __func__);
++
++	if (c->rfd != -1 && FD_ISSET(c->rfd, readset) &&
++	    (c->istate == CHAN_INPUT_OPEN ||
++	    c->istate == CHAN_INPUT_WAIT_DRAIN)) {
++		/*
++		 * Don't not read past the precise end of packets to
++		 * avoid disrupting fd passing.
++		 */
++		if (read_mux(c, 4) < 4) /* read header */
++			return;
++		need = get_u32(buffer_ptr(&c->input));
++#define CHANNEL_MUX_MAX_PACKET	(256 * 1024)
++		if (need > CHANNEL_MUX_MAX_PACKET) {
++			debug2("channel %d: packet too big %u > %u",
++			    c->self, CHANNEL_MUX_MAX_PACKET, need);
++			chan_rcvd_oclose(c);
++			return;
++		}
++		if (read_mux(c, need + 4) < need + 4) /* read body */
++			return;
++		if (c->mux_rcb(c) != 0) {
++			debug("channel %d: mux_rcb failed", c->self);
++			chan_mark_dead(c);
++			return;
++		}
++	}
++
++	if (c->wfd != -1 && FD_ISSET(c->wfd, writeset) &&
++	    buffer_len(&c->output) > 0) {
++		len = write(c->wfd, buffer_ptr(&c->output),
++		    buffer_len(&c->output));
++		if (len < 0 && (errno == EINTR || errno == EAGAIN))
++			return;
++		if (len <= 0) {
++			chan_mark_dead(c);
++			return;
++		}
++		buffer_consume(&c->output, len);
++	}
++}
++
++static void
++channel_post_mux_listener(Channel *c, fd_set *readset, fd_set *writeset)
++{
++	Channel *nc;
++	struct sockaddr_storage addr;
++	socklen_t addrlen;
++	int newsock;
++	uid_t euid;
++	gid_t egid;
++
++	if (!FD_ISSET(c->sock, readset))
++		return;
++
++	debug("multiplexing control connection");
++
++	/*
++	 * Accept connection on control socket
++	 */
++	memset(&addr, 0, sizeof(addr));
++	addrlen = sizeof(addr);
++	if ((newsock = accept(c->sock, (struct sockaddr*)&addr,
++	    &addrlen)) == -1) {
++		error("%s accept: %s", __func__, strerror(errno));
++		return;
++	}
++
++	if (getpeereid(newsock, &euid, &egid) < 0) {
++		error("%s getpeereid failed: %s", __func__,
++		    strerror(errno));
++		close(newsock);
++		return;
++	}
++	if ((euid != 0) && (getuid() != euid)) {
++		error("multiplex uid mismatch: peer euid %u != uid %u",
++		    (u_int)euid, (u_int)getuid());
++		close(newsock);
++		return;
++	}
++	nc = channel_new("multiplex client", SSH_CHANNEL_MUX_CLIENT,
++	    newsock, newsock, -1, c->local_window_max,
++	    c->local_maxpacket, 0, "mux-control", 1);
++	nc->mux_rcb = c->mux_rcb;
++	debug3("%s: new mux channel %d fd %d", __func__,
++	    nc->self, nc->sock);
++	/* establish state */
++	nc->mux_rcb(nc);
++	/* mux state transitions must not elicit protocol messages */
++	nc->flags |= CHAN_LOCAL;
++}
++
+ /* ARGSUSED */
+ static void
+ channel_post_output_drain_13(Channel *c, fd_set *readset, fd_set *writeset)
+@@ -1869,6 +1979,8 @@ channel_handler_init_20(void)
+ 	channel_pre[SSH_CHANNEL_AUTH_SOCKET] =		&channel_pre_listener;
+ 	channel_pre[SSH_CHANNEL_CONNECTING] =		&channel_pre_connecting;
+ 	channel_pre[SSH_CHANNEL_DYNAMIC] =		&channel_pre_dynamic;
++	channel_pre[SSH_CHANNEL_MUX_LISTENER] =		&channel_pre_listener;
++	channel_pre[SSH_CHANNEL_MUX_CLIENT] =		&channel_pre_mux_client;
+ 
+ 	channel_post[SSH_CHANNEL_OPEN] =		&channel_post_open;
+ 	channel_post[SSH_CHANNEL_PORT_LISTENER] =	&channel_post_port_listener;
+@@ -1877,6 +1989,8 @@ channel_handler_init_20(void)
+ 	channel_post[SSH_CHANNEL_AUTH_SOCKET] =		&channel_post_auth_listener;
+ 	channel_post[SSH_CHANNEL_CONNECTING] =		&channel_post_connecting;
+ 	channel_post[SSH_CHANNEL_DYNAMIC] =		&channel_post_open;
++	channel_post[SSH_CHANNEL_MUX_LISTENER] =	&channel_post_mux_listener;
++	channel_post[SSH_CHANNEL_MUX_CLIENT] =		&channel_post_mux_client;
+ }
+ 
+ static void
+diff --git a/channels.h b/channels.h
+index 427842a..69ed23b 100644
+--- a/channels.h
++++ b/channels.h
+@@ -53,7 +53,9 @@
+ #define SSH_CHANNEL_CONNECTING		12
+ #define SSH_CHANNEL_DYNAMIC		13
+ #define SSH_CHANNEL_ZOMBIE		14	/* Almost dead. */
+-#define SSH_CHANNEL_MAX_TYPE		15
++#define SSH_CHANNEL_MUX_LISTENER	15	/* Listener for mux conn. */
++#define SSH_CHANNEL_MUX_CLIENT		16	/* Conn. to mux slave */
++#define SSH_CHANNEL_MAX_TYPE		17
+ 
+ struct Channel;
+ typedef struct Channel Channel;
+@@ -81,6 +83,9 @@ struct channel_connect {
+ 	struct addrinfo *ai, *aitop;
+ };
+ 
++/* Callbacks for mux channels back into client-specific code */
++typedef int mux_callback_fn(struct Channel *);
++
+ struct Channel {
+ 	int     type;		/* channel type/state */
+ 	int     self;		/* my own channel identifier */
+@@ -92,7 +97,7 @@ struct Channel {
+ 	int     wfd;		/* write fd */
+ 	int     efd;		/* extended fd */
+ 	int     sock;		/* sock fd */
+-	int     ctl_fd;		/* control fd (client sharing) */
++	int     ctl_chan;	/* control channel (multiplexed connections) */
+ 	int     isatty;		/* rfd is a tty */
+ 	int     wfd_isatty;	/* wfd is a tty */
+ 	int	client_tty;	/* (client) TTY has been requested */
+@@ -138,6 +143,10 @@ struct Channel {
+ 
+ 	/* non-blocking connect */
+ 	struct channel_connect	connect_ctx;
++
++	/* multiplexing protocol hook, called for each packet received */
++	mux_callback_fn		*mux_rcb;
++	void			*mux_ctx;
+ };
+ 
+ #define CHAN_EXTENDED_IGNORE		0
+@@ -168,6 +177,7 @@ struct Channel {
+ #define CHAN_CLOSE_RCVD			0x02
+ #define CHAN_EOF_SENT			0x04
+ #define CHAN_EOF_RCVD			0x08
++#define CHAN_LOCAL			0x10
+ 
+ #define CHAN_RBUF	16*1024
+ 
+@@ -239,7 +249,7 @@ void	 channel_clear_adm_permitted_opens(void);
+ void 	 channel_print_adm_permitted_opens(void);
+ int      channel_input_port_forward_request(int, int);
+ Channel	*channel_connect_to(const char *, u_short, char *, char *);
+-Channel	*channel_connect_stdio_fwd(const char*, u_short);
++Channel	*channel_connect_stdio_fwd(const char*, u_short, int, int);
+ Channel	*channel_connect_by_listen_address(u_short, char *, char *);
+ int	 channel_request_remote_forwarding(const char *, u_short,
+ 	     const char *, u_short);
+diff --git a/clientloop.c b/clientloop.c
+index 636b176..a4f35cc 100644
+--- a/clientloop.c
++++ b/clientloop.c
+@@ -125,7 +125,7 @@ extern int stdin_null_flag;
+ extern int no_shell_flag;
+ 
+ /* Control socket */
+-extern int muxserver_sock;
++extern int muxserver_sock; /* XXX use mux_client_cleanup() instead */
+ 
+ /*
+  * Name of the host we are connecting to.  This is the name given on the
+@@ -146,8 +146,11 @@ static volatile sig_atomic_t received_signal = 0;
+ /* Flag indicating whether the user's terminal is in non-blocking mode. */
+ static int in_non_blocking_mode = 0;
+ 
++/* Time when backgrounded control master using ControlPersist should exit */
++static time_t control_persist_exit_time = 0;
++
+ /* Common data for the client loop code. */
+-static volatile sig_atomic_t quit_pending; /* Set non-zero to quit the loop. */
++volatile sig_atomic_t quit_pending; /* Set non-zero to quit the loop. */
+ static int escape_char1;	/* Escape character. (proto1 only) */
+ static int escape_pending1;	/* Last character was an escape (proto1 only) */
+ static int last_was_cr;		/* Last character was a newline. */
+@@ -250,6 +253,34 @@ get_current_time(void)
+ 	return (double) tv.tv_sec + (double) tv.tv_usec / 1000000.0;
+ }
+ 
++/*
++ * Sets control_persist_exit_time to the absolute time when the
++ * backgrounded control master should exit due to expiry of the
++ * ControlPersist timeout.  Sets it to 0 if we are not a backgrounded
++ * control master process, or if there is no ControlPersist timeout.
++ */
++static void
++set_control_persist_exit_time(void)
++{
++	if (muxserver_sock == -1 || !options.control_persist
++	    || options.control_persist_timeout == 0)
++		/* not using a ControlPersist timeout */
++		control_persist_exit_time = 0;
++	else if (channel_still_open()) {
++		/* some client connections are still open */
++		if (control_persist_exit_time > 0)
++			debug2("%s: cancel scheduled exit", __func__);
++		control_persist_exit_time = 0;
++	} else if (control_persist_exit_time <= 0) {
++		/* a client connection has recently closed */
++		control_persist_exit_time = time(NULL) +
++			(time_t)options.control_persist_timeout;
++		debug2("%s: schedule exit in %d seconds", __func__,
++		    options.control_persist_timeout);
++	}
++	/* else we are already counting down to the timeout */
++}
++
+ #define SSH_X11_PROTO "MIT-MAGIC-COOKIE-1"
+ void
+ client_x11_get_proto(const char *display, const char *xauth_path,
+@@ -523,6 +554,7 @@ client_wait_until_can_do_something(fd_set **readsetp, fd_set **writesetp,
+     int *maxfdp, u_int *nallocp, int rekeying)
+ {
+ 	struct timeval tv, *tvp;
++	int timeout_secs;
+ 	int ret;
+ 
+ 	/* Add any selections by the channel mechanism. */
+@@ -563,22 +595,30 @@ client_wait_until_can_do_something(fd_set **readsetp, fd_set **writesetp,
+ 	if (packet_have_data_to_write())
+ 		FD_SET(connection_out, *writesetp);
+ 
+-	if (muxserver_sock != -1)
+-		FD_SET(muxserver_sock, *readsetp);
+-
+ 	/*
+ 	 * Wait for something to happen.  This will suspend the process until
+ 	 * some selected descriptor can be read, written, or has some other
+-	 * event pending.
++	 * event pending, or a timeout expires.
+ 	 */
+ 
+-	if (options.server_alive_interval == 0 || !compat20)
++	timeout_secs = INT_MAX; /* we use INT_MAX to mean no timeout */
++	if (options.server_alive_interval > 0 && compat20)
++		timeout_secs = options.server_alive_interval;
++	set_control_persist_exit_time();
++	if (control_persist_exit_time > 0) {
++		timeout_secs = MIN(timeout_secs,
++			control_persist_exit_time - time(NULL));
++		if (timeout_secs < 0)
++			timeout_secs = 0;
++	}
++	if (timeout_secs == INT_MAX)
+ 		tvp = NULL;
+ 	else {
+-		tv.tv_sec = options.server_alive_interval;
++		tv.tv_sec = timeout_secs;
+ 		tv.tv_usec = 0;
+ 		tvp = &tv;
+ 	}
++
+ 	ret = select((*maxfdp)+1, *readsetp, *writesetp, NULL, tvp);
+ 	if (ret < 0) {
+ 		char buf[100];
+@@ -694,7 +734,7 @@ client_status_confirm(int type, Channel *c, void *ctx)
+ 
+ 	/* XXX supress on mux _client_ quietmode */
+ 	tochan = options.log_level >= SYSLOG_LEVEL_ERROR &&
+-	    c->ctl_fd != -1 && c->extended_usage == CHAN_EXTENDED_WRITE;
++	    c->ctl_chan != -1 && c->extended_usage == CHAN_EXTENDED_WRITE;
+ 
+ 	if (type == SSH2_MSG_CHANNEL_SUCCESS) {
+ 		debug2("%s request accepted on channel %d",
+@@ -838,6 +878,7 @@ process_cmdline(void)
+ 	while (isspace(*++s))
+ 		;
+ 
++	/* XXX update list of forwards in options */
+ 	if (delete) {
+ 		cancel_port = 0;
+ 		cancel_host = hpdelim(&s);	/* may be NULL */
+@@ -935,7 +976,7 @@ process_escapes(Channel *c, Buffer *bin, Buffer *bout, Buffer *berr,
+ 				    escape_char);
+ 				buffer_append(berr, string, strlen(string));
+ 
+-				if (c && c->ctl_fd != -1) {
++				if (c && c->ctl_chan != -1) {
+ 					chan_read_failed(c);
+ 					chan_write_failed(c);
+ 					return 0;
+@@ -945,7 +986,7 @@ process_escapes(Channel *c, Buffer *bin, Buffer *bout, Buffer *berr,
+ 
+ 			case 'Z' - 64:
+ 				/* XXX support this for mux clients */
+-				if (c && c->ctl_fd != -1) {
++				if (c && c->ctl_chan != -1) {
+  noescape:
+ 					snprintf(string, sizeof string,
+ 					    "%c%c escape not available to "
+@@ -990,7 +1031,7 @@ process_escapes(Channel *c, Buffer *bin, Buffer *bout, Buffer *berr,
+ 				continue;
+ 
+ 			case '&':
+-				if (c && c->ctl_fd != -1)
++				if (c && c->ctl_chan != -1)
+ 					goto noescape;
+ 				/*
+ 				 * Detach the program (continue to serve
+@@ -1041,7 +1082,7 @@ process_escapes(Channel *c, Buffer *bin, Buffer *bout, Buffer *berr,
+ 				continue;
+ 
+ 			case '?':
+-				if (c && c->ctl_fd != -1) {
++				if (c && c->ctl_chan != -1) {
+ 					snprintf(string, sizeof string,
+ "%c?\r\n\
+ Supported escape sequences:\r\n\
+@@ -1090,7 +1131,7 @@ Supported escape sequences:\r\n\
+ 				continue;
+ 
+ 			case 'C':
+-				if (c && c->ctl_fd != -1)
++				if (c && c->ctl_chan != -1)
+ 					goto noescape;
+ 				process_cmdline();
+ 				continue;
+@@ -1326,8 +1367,6 @@ client_loop(int have_pty, int escape_char_arg, int ssh2_chan_id)
+ 	connection_in = packet_get_connection_in();
+ 	connection_out = packet_get_connection_out();
+ 	max_fd = MAX(connection_in, connection_out);
+-	if (muxserver_sock != -1)
+-		max_fd = MAX(max_fd, muxserver_sock);
+ 
+ 	if (!compat20) {
+ 		/* enable nonblocking unless tty */
+@@ -1452,12 +1491,6 @@ client_loop(int have_pty, int escape_char_arg, int ssh2_chan_id)
+ 		/* Buffer input from the connection.  */
+ 		client_process_net_input(readset);
+ 
+-		/* Accept control connections.  */
+-		if (muxserver_sock != -1 &&FD_ISSET(muxserver_sock, readset)) {
+-			if (muxserver_accept_control())
+-				quit_pending = 1;
+-		}
+-
+ 		if (quit_pending)
+ 			break;
+ 
+@@ -1477,6 +1510,18 @@ client_loop(int have_pty, int escape_char_arg, int ssh2_chan_id)
+ 		 */
+ 		if (FD_ISSET(connection_out, writeset))
+ 			packet_write_poll();
++
++		/*
++		 * If we are a backgrounded control master, and the
++		 * timeout has expired without any active client
++		 * connections, then quit.
++		 */
++		if (control_persist_exit_time > 0) {
++			if (time(NULL) >= control_persist_exit_time) {
++				debug("ControlPersist timeout expired");
++				break;
++			}
++		}
+ 	}
+ 	if (readset)
+ 		xfree(readset);
+@@ -1857,15 +1902,16 @@ client_input_channel_req(int type, u_int32_t seq, void *ctxt)
+ 		chan_rcvd_eow(c);
+ 	} else if (strcmp(rtype, "exit-status") == 0) {
+ 		exitval = packet_get_int();
+-		if (id == session_ident) {
++		if (c->ctl_chan != -1) {
++			mux_exit_message(c, exitval);
++			success = 1;
++		} else if (id == session_ident) {
++			/* Record exit value of local session */
+ 			success = 1;
+ 			exit_status = exitval;
+-		} else if (c->ctl_fd == -1) {
++		} else {
+ 			error("client_input_channel_req: unexpected channel %d",
+ 			    session_ident);
+-		} else {
+-			atomicio(vwrite, c->ctl_fd, &exitval, sizeof(exitval));
+-			success = 1;
+ 		}
+ 		packet_check_eom();
+ 	}
+diff --git a/clientloop.h b/clientloop.h
+index 8bb874b..0b8257b 100644
+--- a/clientloop.h
++++ b/clientloop.h
+@@ -1,4 +1,4 @@
+-/* $OpenBSD: clientloop.h,v 1.22 2008/06/12 15:19:17 djm Exp $ */
++/* $OpenBSD: clientloop.h,v 1.23 2010/01/26 01:28:35 djm Exp $ */
+ 
+ /*
+  * Author: Tatu Ylonen <ylo at cs.hut.fi>
+@@ -56,18 +56,14 @@ typedef void global_confirm_cb(int, u_int32_t seq, void *);
+ void	 client_register_global_confirm(global_confirm_cb *, void *);
+ 
+ /* Multiplexing protocol version */
+-#define SSHMUX_VER			2
++#define SSHMUX_VER			4
+ 
+ /* Multiplexing control protocol flags */
+ #define SSHMUX_COMMAND_OPEN		1	/* Open new connection */
+ #define SSHMUX_COMMAND_ALIVE_CHECK	2	/* Check master is alive */
+ #define SSHMUX_COMMAND_TERMINATE	3	/* Ask master to exit */
+-
+-#define SSHMUX_FLAG_TTY			(1)	/* Request tty on open */
+-#define SSHMUX_FLAG_SUBSYS		(1<<1)	/* Subsystem request on open */
+-#define SSHMUX_FLAG_X11_FWD		(1<<2)	/* Request X11 forwarding */
+-#define SSHMUX_FLAG_AGENT_FWD		(1<<3)	/* Request agent forwarding */
++#define SSHMUX_COMMAND_STDIO_FWD	4	/* Open stdio fwd (ssh -W) */
+ 
+ void	muxserver_listen(void);
+-int	muxserver_accept_control(void);
+ void	muxclient(const char *);
++void	mux_exit_message(Channel *, int);
+diff --git a/mux.c b/mux.c
+index 79f8376..b1d282b 100644
+--- a/mux.c
++++ b/mux.c
+@@ -17,25 +17,21 @@
+ 
+ /* ssh session multiplexing support */
+ 
+-#include "includes.h"
+-
+ /*
+  * TODO:
+- *   1. partial reads in muxserver_accept_control (maybe make channels
+- *      from accepted connections)
+- *   2. Better signalling from master to slave, especially passing of
++ *   - Better signalling from master to slave, especially passing of
+  *      error messages
+- *   3. Better fall-back from mux slave error to new connection.
+- *   3. Add/delete forwardings via slave
+- *   4. ExitOnForwardingFailure (after #3 obviously)
+- *   5. Maybe extension mechanisms for multi-X11/multi-agent forwarding
+- *   6. Document the mux mini-protocol somewhere.
+- *   7. Support ~^Z in mux slaves.
+- *   8. Inspect or control sessions in master.
+- *   9. If we ever support the "signal" channel request, send signals on
+- *      sessions in master.
++ *   - Better fall-back from mux slave error to new connection.
++ *   - ExitOnForwardingFailure
++ *   - Maybe extension mechanisms for multi-X11/multi-agent forwarding
++ *   - Support ~^Z in mux slaves.
++ *   - Inspect or control sessions in master.
++ *   - If we ever support the "signal" channel request, send signals on
++ *     sessions in master.
+  */
+ 
++#include "includes.h"
++
+ #include <sys/types.h>
+ #include <sys/param.h>
+ #include <sys/stat.h>
+@@ -55,6 +51,14 @@
+ #include <paths.h>
+ #endif
+ 
++#ifdef HAVE_POLL_H
++#include <poll.h>
++#else
++# ifdef HAVE_SYS_POLL_H
++#  include <sys/poll.h>
++# endif
++#endif
++
+ #ifdef HAVE_UTIL_H
+ # include <util.h>
+ #endif
+@@ -87,13 +91,16 @@ extern int stdin_null_flag;
+ extern char *host;
+ int subsystem_flag;
+ extern Buffer command;
++extern volatile sig_atomic_t quit_pending;
++extern char *stdio_forward_host;
++extern int stdio_forward_port;
+ 
+ /* Context for session open confirmation callback */
+ struct mux_session_confirm_ctx {
+-	int want_tty;
+-	int want_subsys;
+-	int want_x_fwd;
+-	int want_agent_fwd;
++	u_int want_tty;
++	u_int want_subsys;
++	u_int want_x_fwd;
++	u_int want_agent_fwd;
+ 	Buffer cmd;
+ 	char *term;
+ 	struct termios tio;
+@@ -103,6 +110,9 @@ struct mux_session_confirm_ctx {
+ /* fd to control socket */
+ int muxserver_sock = -1;
+ 
++/* client request id */
++u_int muxclient_request_id = 0;
++
+ /* Multiplexing control command */
+ u_int muxclient_command = 0;
+ 
+@@ -112,269 +122,234 @@ static volatile sig_atomic_t muxclient_terminate = 0;
+ /* PID of multiplex server */
+ static u_int muxserver_pid = 0;
+ 
++static Channel *mux_listener_channel = NULL;
+ 
+-/* ** Multiplexing master support */
+-
+-/* Prepare a mux master to listen on a Unix domain socket. */
+-void
+-muxserver_listen(void)
+-{
+-	struct sockaddr_un addr;
+-	mode_t old_umask;
+-	int addr_len;
+-
+-	if (options.control_path == NULL ||
+-	    options.control_master == SSHCTL_MASTER_NO)
+-		return;
+-
+-	debug("setting up multiplex master socket");
+-
+-	memset(&addr, '\0', sizeof(addr));
+-	addr.sun_family = AF_UNIX;
+-	addr_len = offsetof(struct sockaddr_un, sun_path) +
+-	    strlen(options.control_path) + 1;
+-
+-	if (strlcpy(addr.sun_path, options.control_path,
+-	    sizeof(addr.sun_path)) >= sizeof(addr.sun_path))
+-		fatal("ControlPath too long");
++struct mux_master_state {
++	int hello_rcvd;
++};
+ 
+-	if ((muxserver_sock = socket(PF_UNIX, SOCK_STREAM, 0)) < 0)
+-		fatal("%s socket(): %s", __func__, strerror(errno));
++/* mux protocol messages */
++#define MUX_MSG_HELLO		0x00000001
++#define MUX_C_NEW_SESSION	0x10000002
++#define MUX_C_ALIVE_CHECK	0x10000004
++#define MUX_C_TERMINATE		0x10000005
++#define MUX_C_OPEN_FWD		0x10000006
++#define MUX_C_CLOSE_FWD		0x10000007
++#define MUX_C_NEW_STDIO_FWD	0x10000008
++#define MUX_S_OK		0x80000001
++#define MUX_S_PERMISSION_DENIED	0x80000002
++#define MUX_S_FAILURE		0x80000003
++#define MUX_S_EXIT_MESSAGE	0x80000004
++#define MUX_S_ALIVE		0x80000005
++#define MUX_S_SESSION_OPENED	0x80000006
++
++/* type codes for MUX_C_OPEN_FWD and MUX_C_CLOSE_FWD */
++#define MUX_FWD_LOCAL   1
++#define MUX_FWD_REMOTE  2
++#define MUX_FWD_DYNAMIC 3
++
++static void mux_session_confirm(int, void *);
++
++static int process_mux_master_hello(u_int, Channel *, Buffer *, Buffer *);
++static int process_mux_new_session(u_int, Channel *, Buffer *, Buffer *);
++static int process_mux_alive_check(u_int, Channel *, Buffer *, Buffer *);
++static int process_mux_terminate(u_int, Channel *, Buffer *, Buffer *);
++static int process_mux_open_fwd(u_int, Channel *, Buffer *, Buffer *);
++static int process_mux_close_fwd(u_int, Channel *, Buffer *, Buffer *);
++static int process_mux_stdio_fwd(u_int, Channel *, Buffer *, Buffer *);
++
++static const struct {
++	u_int type;
++	int (*handler)(u_int, Channel *, Buffer *, Buffer *);
++} mux_master_handlers[] = {
++	{ MUX_MSG_HELLO, process_mux_master_hello },
++	{ MUX_C_NEW_SESSION, process_mux_new_session },
++	{ MUX_C_ALIVE_CHECK, process_mux_alive_check },
++	{ MUX_C_TERMINATE, process_mux_terminate },
++	{ MUX_C_OPEN_FWD, process_mux_open_fwd },
++	{ MUX_C_CLOSE_FWD, process_mux_close_fwd },
++	{ MUX_C_NEW_STDIO_FWD, process_mux_stdio_fwd },
++	{ 0, NULL }
++};
+ 
+-	old_umask = umask(0177);
+-	if (bind(muxserver_sock, (struct sockaddr *)&addr, addr_len) == -1) {
+-		muxserver_sock = -1;
+-		if (errno == EINVAL || errno == EADDRINUSE) {
+-			error("ControlSocket %s already exists, "
+-			    "disabling multiplexing", options.control_path);
+-			close(muxserver_sock);
+-			muxserver_sock = -1;
+-			xfree(options.control_path);
+-			options.control_path = NULL;
+-			options.control_master = SSHCTL_MASTER_NO;
+-			return;
+-		} else
+-			fatal("%s bind(): %s", __func__, strerror(errno));
++/* Cleanup callback fired on closure of mux slave _session_ channel */
++/* ARGSUSED */
++static void
++mux_master_session_cleanup_cb(int cid, void *unused)
++{
++	Channel *cc, *c = channel_by_id(cid);
++
++	debug3("%s: entering for channel %d", __func__, cid);
++	if (c == NULL)
++		fatal("%s: channel_by_id(%i) == NULL", __func__, cid);
++	if (c->ctl_chan != -1) {
++		if ((cc = channel_by_id(c->ctl_chan)) == NULL)
++			fatal("%s: channel %d missing control channel %d",
++			    __func__, c->self, c->ctl_chan);
++		c->ctl_chan = -1;
++		cc->remote_id = -1;
++		chan_rcvd_oclose(cc);
+ 	}
+-	umask(old_umask);
+-
+-	if (listen(muxserver_sock, 64) == -1)
+-		fatal("%s listen(): %s", __func__, strerror(errno));
+-
+-	set_nonblock(muxserver_sock);
++	channel_cancel_cleanup(c->self);
+ }
+ 
+-/* Callback on open confirmation in mux master for a mux client session. */
++/* Cleanup callback fired on closure of mux slave _control_ channel */
++/* ARGSUSED */
+ static void
+-mux_session_confirm(int id, void *arg)
++mux_master_control_cleanup_cb(int cid, void *unused)
+ {
+-	struct mux_session_confirm_ctx *cctx = arg;
+-	const char *display;
+-	Channel *c;
+-	int i;
+-
+-	if (cctx == NULL)
+-		fatal("%s: cctx == NULL", __func__);
+-	if ((c = channel_lookup(id)) == NULL)
+-		fatal("%s: no channel for id %d", __func__, id);
+-
+-	display = getenv("DISPLAY");
+-	if (cctx->want_x_fwd && options.forward_x11 && display != NULL) {
+-		char *proto, *data;
+-		/* Get reasonable local authentication information. */
+-		client_x11_get_proto(display, options.xauth_location,
+-		    options.forward_x11_trusted, &proto, &data);
+-		/* Request forwarding with authentication spoofing. */
+-		debug("Requesting X11 forwarding with authentication spoofing.");
+-		x11_request_forwarding_with_spoofing(id, display, proto, data);
+-		/* XXX wait for reply */
+-	}
+-
+-	if (cctx->want_agent_fwd && options.forward_agent) {
+-		debug("Requesting authentication agent forwarding.");
+-		channel_request_start(id, "auth-agent-req at openssh.com", 0);
+-		packet_send();
++	Channel *sc, *c = channel_by_id(cid);
++
++	debug3("%s: entering for channel %d", __func__, cid);
++	if (c == NULL)
++		fatal("%s: channel_by_id(%i) == NULL", __func__, cid);
++	if (c->remote_id != -1) {
++		if ((sc = channel_by_id(c->remote_id)) == NULL)
++			debug2("%s: channel %d n session channel %d",
++			    __func__, c->self, c->remote_id);
++		c->remote_id = -1;
++		sc->ctl_chan = -1;
++		chan_mark_dead(sc);
+ 	}
+-
+-	client_session2_setup(id, cctx->want_tty, cctx->want_subsys,
+-	    cctx->term, &cctx->tio, c->rfd, &cctx->cmd, cctx->env);
+-
+-	c->open_confirm_ctx = NULL;
+-	buffer_free(&cctx->cmd);
+-	xfree(cctx->term);
+-	if (cctx->env != NULL) {
+-		for (i = 0; cctx->env[i] != NULL; i++)
+-			xfree(cctx->env[i]);
+-		xfree(cctx->env);
+-	}
+-	xfree(cctx);
++	channel_cancel_cleanup(c->self);
+ }
+ 
+-/*
+- * Accept a connection on the mux master socket and process the
+- * client's request. Returns flag indicating whether mux master should
+- * begin graceful close.
+- */
+-int
+-muxserver_accept_control(void)
++/* Check mux client environment variables before passing them to mux master. */
++static int
++env_permitted(char *env)
+ {
+-	Buffer m;
+-	Channel *c;
+-	int client_fd, new_fd[3], ver, allowed, window, packetmax;
+-	socklen_t addrlen;
+-	struct sockaddr_storage addr;
+-	struct mux_session_confirm_ctx *cctx;
+-	char *cmd;
+-	u_int i, j, len, env_len, mux_command, flags, escape_char;
+-	uid_t euid;
+-	gid_t egid;
+-	int start_close = 0;
+-
+-	/*
+-	 * Accept connection on control socket
+-	 */
+-	memset(&addr, 0, sizeof(addr));
+-	addrlen = sizeof(addr);
+-	if ((client_fd = accept(muxserver_sock,
+-	    (struct sockaddr*)&addr, &addrlen)) == -1) {
+-		error("%s accept: %s", __func__, strerror(errno));
+-		return 0;
+-	}
++	int i, ret;
++	char name[1024], *cp;
+ 
+-	if (getpeereid(client_fd, &euid, &egid) < 0) {
+-		error("%s getpeereid failed: %s", __func__, strerror(errno));
+-		close(client_fd);
++	if ((cp = strchr(env, '=')) == NULL || cp == env)
+ 		return 0;
+-	}
+-	if ((euid != 0) && (getuid() != euid)) {
+-		error("control mode uid mismatch: peer euid %u != uid %u",
+-		    (u_int) euid, (u_int) getuid());
+-		close(client_fd);
++	ret = snprintf(name, sizeof(name), "%.*s", (int)(cp - env), env);
++	if (ret <= 0 || (size_t)ret >= sizeof(name)) {
++		error("env_permitted: name '%.100s...' too long", env);
+ 		return 0;
+ 	}
+ 
+-	/* XXX handle asynchronously */
+-	unset_nonblock(client_fd);
++	for (i = 0; i < options.num_send_env; i++)
++		if (match_pattern(name, options.send_env[i]))
++			return 1;
++
++	return 0;
++}
++
++/* Mux master protocol message handlers */
+ 
+-	/* Read command */
+-	buffer_init(&m);
+-	if (ssh_msg_recv(client_fd, &m) == -1) {
+-		error("%s: client msg_recv failed", __func__);
+-		close(client_fd);
+-		buffer_free(&m);
+-		return 0;
++static int
++process_mux_master_hello(u_int rid, Channel *c, Buffer *m, Buffer *r)
++{
++	u_int ver;
++	struct mux_master_state *state = (struct mux_master_state *)c->mux_ctx;
++
++	if (state == NULL)
++		fatal("%s: channel %d: c->mux_ctx == NULL", __func__, c->self);
++	if (state->hello_rcvd) {
++		error("%s: HELLO received twice", __func__);
++		return -1;
+ 	}
+-	if ((ver = buffer_get_char(&m)) != SSHMUX_VER) {
+-		error("%s: wrong client version %d", __func__, ver);
+-		buffer_free(&m);
+-		close(client_fd);
+-		return 0;
++	if (buffer_get_int_ret(&ver, m) != 0) {
++ malf:
++		error("%s: malformed message", __func__);
++		return -1;
+ 	}
++	if (ver != SSHMUX_VER) {
++		error("Unsupported multiplexing protocol version %d "
++		    "(expected %d)", ver, SSHMUX_VER);
++		return -1;
++	}
++	debug2("%s: channel %d slave version %u", __func__, c->self, ver);
+ 
+-	allowed = 1;
+-	mux_command = buffer_get_int(&m);
+-	flags = buffer_get_int(&m);
+-
+-	buffer_clear(&m);
++	/* No extensions are presently defined */
++	while (buffer_len(m) > 0) {
++		char *name = buffer_get_string_ret(m, NULL);
++		char *value = buffer_get_string_ret(m, NULL);
+ 
+-	switch (mux_command) {
+-	case SSHMUX_COMMAND_OPEN:
+-		if (options.control_master == SSHCTL_MASTER_ASK ||
+-		    options.control_master == SSHCTL_MASTER_AUTO_ASK)
+-			allowed = ask_permission("Allow shared connection "
+-			    "to %s? ", host);
+-		/* continue below */
+-		break;
+-	case SSHMUX_COMMAND_TERMINATE:
+-		if (options.control_master == SSHCTL_MASTER_ASK ||
+-		    options.control_master == SSHCTL_MASTER_AUTO_ASK)
+-			allowed = ask_permission("Terminate shared connection "
+-			    "to %s? ", host);
+-		if (allowed)
+-			start_close = 1;
+-		/* FALLTHROUGH */
+-	case SSHMUX_COMMAND_ALIVE_CHECK:
+-		/* Reply for SSHMUX_COMMAND_TERMINATE and ALIVE_CHECK */
+-		buffer_clear(&m);
+-		buffer_put_int(&m, allowed);
+-		buffer_put_int(&m, getpid());
+-		if (ssh_msg_send(client_fd, SSHMUX_VER, &m) == -1) {
+-			error("%s: client msg_send failed", __func__);
+-			close(client_fd);
+-			buffer_free(&m);
+-			return start_close;
++		if (name == NULL || value == NULL) {
++			if (name != NULL)
++				xfree(name);
++			goto malf;
+ 		}
+-		buffer_free(&m);
+-		close(client_fd);
+-		return start_close;
+-	default:
+-		error("Unsupported command %d", mux_command);
+-		buffer_free(&m);
+-		close(client_fd);
+-		return 0;
++		debug2("Unrecognised slave extension \"%s\"", name);
++		xfree(name);
++		xfree(value);
+ 	}
++	state->hello_rcvd = 1;
++	return 0;
++}
++
++static int
++process_mux_new_session(u_int rid, Channel *c, Buffer *m, Buffer *r)
++{
++	Channel *nc;
++	struct mux_session_confirm_ctx *cctx;
++	char *reserved, *cmd, *cp;
++	u_int i, j, len, env_len, escape_char, window, packetmax;
++	int new_fd[3];
+ 
+ 	/* Reply for SSHMUX_COMMAND_OPEN */
+-	buffer_clear(&m);
+-	buffer_put_int(&m, allowed);
+-	buffer_put_int(&m, getpid());
+-	if (ssh_msg_send(client_fd, SSHMUX_VER, &m) == -1) {
+-		error("%s: client msg_send failed", __func__);
+-		close(client_fd);
+-		buffer_free(&m);
+-		return 0;
++	cctx = xcalloc(1, sizeof(*cctx));
++	cctx->term = NULL;
++	cmd = reserved = NULL;
++	if ((reserved = buffer_get_string_ret(m, NULL)) == NULL ||
++	    buffer_get_int_ret(&cctx->want_tty, m) != 0 ||
++	    buffer_get_int_ret(&cctx->want_x_fwd, m) != 0 ||
++	    buffer_get_int_ret(&cctx->want_agent_fwd, m) != 0 ||
++	    buffer_get_int_ret(&cctx->want_subsys, m) != 0 ||
++	    buffer_get_int_ret(&escape_char, m) != 0 ||
++	    (cctx->term = buffer_get_string_ret(m, &len)) == NULL ||
++	    (cmd = buffer_get_string_ret(m, &len)) == NULL) {
++ malf:
++		if (cmd != NULL)
++			xfree(cmd);
++		if (reserved != NULL)
++			xfree(reserved);
++		if (cctx->term != NULL)
++			xfree(cctx->term);
++		error("%s: malformed message", __func__);
++		return -1;
+ 	}
+-
+-	if (!allowed) {
+-		error("Refused control connection");
+-		close(client_fd);
+-		buffer_free(&m);
+-		return 0;
++	xfree(reserved);
++	reserved = NULL;
++
++	cctx->env = NULL;
++	env_len = 0;
++	while (buffer_len(m) > 0) {
++#define MUX_MAX_ENV_VARS	4096
++		if ((cp = buffer_get_string_ret(m, &len)) == NULL) {
++			xfree(cmd);
++			goto malf;
++		}
++		if (!env_permitted(cp)) {
++			xfree(cp);
++			continue;
++		}
++		cctx->env = xrealloc(cctx->env, env_len + 2,
++		    sizeof(*cctx->env));
++		cctx->env[env_len++] = cp;
++		cctx->env[env_len] = NULL;
++		if (env_len > MUX_MAX_ENV_VARS) {
++			error(">%d environment variables received, ignoring "
++			    "additional", MUX_MAX_ENV_VARS);
++			break;
++		}
+ 	}
+ 
+-	buffer_clear(&m);
+-	if (ssh_msg_recv(client_fd, &m) == -1) {
+-		error("%s: client msg_recv failed", __func__);
+-		close(client_fd);
+-		buffer_free(&m);
+-		return 0;
+-	}
+-	if ((ver = buffer_get_char(&m)) != SSHMUX_VER) {
+-		error("%s: wrong client version %d", __func__, ver);
+-		buffer_free(&m);
+-		close(client_fd);
+-		return 0;
+-	}
++	debug2("%s: channel %d: request tty %d, X %d, agent %d, subsys %d, "
++	    "term \"%s\", cmd \"%s\", env %u", __func__, c->self,
++	    cctx->want_tty, cctx->want_x_fwd, cctx->want_agent_fwd,
++	    cctx->want_subsys, cctx->term, cmd, env_len);
+ 
+-	cctx = xcalloc(1, sizeof(*cctx));
+-	cctx->want_tty = (flags & SSHMUX_FLAG_TTY) != 0;
+-	cctx->want_subsys = (flags & SSHMUX_FLAG_SUBSYS) != 0;
+-	cctx->want_x_fwd = (flags & SSHMUX_FLAG_X11_FWD) != 0;
+-	cctx->want_agent_fwd = (flags & SSHMUX_FLAG_AGENT_FWD) != 0;
+-	cctx->term = buffer_get_string(&m, &len);
+-	escape_char = buffer_get_int(&m);
+-
+-	cmd = buffer_get_string(&m, &len);
+ 	buffer_init(&cctx->cmd);
+ 	buffer_append(&cctx->cmd, cmd, strlen(cmd));
+-
+-	env_len = buffer_get_int(&m);
+-	env_len = MIN(env_len, 4096);
+-	debug3("%s: receiving %d env vars", __func__, env_len);
+-	if (env_len != 0) {
+-		cctx->env = xcalloc(env_len + 1, sizeof(*cctx->env));
+-		for (i = 0; i < env_len; i++)
+-			cctx->env[i] = buffer_get_string(&m, &len);
+-		cctx->env[i] = NULL;
+-	}
+-
+-	debug2("%s: accepted tty %d, subsys %d, cmd %s", __func__,
+-	    cctx->want_tty, cctx->want_subsys, cmd);
+ 	xfree(cmd);
++	cmd = NULL;
+ 
+ 	/* Gather fds from client */
+ 	for(i = 0; i < 3; i++) {
+-		if ((new_fd[i] = mm_receive_fd(client_fd)) == -1) {
++		if ((new_fd[i] = mm_receive_fd(c->sock)) == -1) {
+ 			error("%s: failed to receive fd %d from slave",
+ 			    __func__, i);
+ 			for (j = 0; j < i; j++)
+@@ -385,37 +360,56 @@ muxserver_accept_control(void)
+ 				xfree(cctx->env);
+ 			xfree(cctx->term);
+ 			buffer_free(&cctx->cmd);
+-			close(client_fd);
+ 			xfree(cctx);
+-			return 0;
++
++			/* prepare reply */
++			buffer_put_int(r, MUX_S_FAILURE);
++			buffer_put_int(r, rid);
++			buffer_put_cstring(r,
++			    "did not receive file descriptors");
++			return -1;
+ 		}
+ 	}
+ 
+-	debug2("%s: got fds stdin %d, stdout %d, stderr %d", __func__,
++	debug3("%s: got fds stdin %d, stdout %d, stderr %d", __func__,
+ 	    new_fd[0], new_fd[1], new_fd[2]);
+ 
+-	/* Try to pick up ttymodes from client before it goes raw */
+-	if (cctx->want_tty && tcgetattr(new_fd[0], &cctx->tio) == -1)
+-		error("%s: tcgetattr: %s", __func__, strerror(errno));
+-
+-	/* This roundtrip is just for synchronisation of ttymodes */
+-	buffer_clear(&m);
+-	if (ssh_msg_send(client_fd, SSHMUX_VER, &m) == -1) {
+-		error("%s: client msg_send failed", __func__);
+-		close(client_fd);
++	/* XXX support multiple child sessions in future */
++	if (c->remote_id != -1) {
++		debug2("%s: session already open", __func__);
++		/* prepare reply */
++		buffer_put_int(r, MUX_S_FAILURE);
++		buffer_put_int(r, rid);
++		buffer_put_cstring(r, "Multiple sessions not supported");
++ cleanup:
+ 		close(new_fd[0]);
+ 		close(new_fd[1]);
+ 		close(new_fd[2]);
+-		buffer_free(&m);
+ 		xfree(cctx->term);
+ 		if (env_len != 0) {
+ 			for (i = 0; i < env_len; i++)
+ 				xfree(cctx->env[i]);
+ 			xfree(cctx->env);
+ 		}
++		buffer_free(&cctx->cmd);
+ 		return 0;
+ 	}
+-	buffer_free(&m);
++
++	if (options.control_master == SSHCTL_MASTER_ASK ||
++	    options.control_master == SSHCTL_MASTER_AUTO_ASK) {
++		if (!ask_permission("Allow shared connection to %s? ", host)) {
++			debug2("%s: session refused by user", __func__);
++			/* prepare reply */
++			buffer_put_int(r, MUX_S_PERMISSION_DENIED);
++			buffer_put_int(r, rid);
++			buffer_put_cstring(r, "Permission denied");
++			goto cleanup;
++		}
++	}
++
++	/* Try to pick up ttymodes from client before it goes raw */
++	if (cctx->want_tty && tcgetattr(new_fd[0], &cctx->tio) == -1)
++		error("%s: tcgetattr: %s", __func__, strerror(errno));
+ 
+ 	/* enable nonblocking unless tty */
+ 	if (!isatty(new_fd[0]))
+@@ -425,257 +419,1084 @@ muxserver_accept_control(void)
+ 	if (!isatty(new_fd[2]))
+ 		set_nonblock(new_fd[2]);
+ 
+-	set_nonblock(client_fd);
+-
+ 	window = CHAN_SES_WINDOW_DEFAULT;
+ 	packetmax = CHAN_SES_PACKET_DEFAULT;
+ 	if (cctx->want_tty) {
+ 		window >>= 1;
+ 		packetmax >>= 1;
+ 	}
+-	
+-	c = channel_new("session", SSH_CHANNEL_OPENING,
++
++	nc = channel_new("session", SSH_CHANNEL_OPENING,
+ 	    new_fd[0], new_fd[1], new_fd[2], window, packetmax,
+ 	    CHAN_EXTENDED_WRITE, "client-session", /*nonblock*/0);
+ 
+-	c->ctl_fd = client_fd;
++	nc->ctl_chan = c->self;		/* link session -> control channel */
++	c->remote_id = nc->self; 	/* link control -> session channel */
++
+ 	if (cctx->want_tty && escape_char != 0xffffffff) {
+-		channel_register_filter(c->self,
++		channel_register_filter(nc->self,
+ 		    client_simple_escape_filter, NULL,
+ 		    client_filter_cleanup,
+ 		    client_new_escape_filter_ctx((int)escape_char));
+ 	}
+ 
+-	debug3("%s: channel_new: %d", __func__, c->self);
++	debug2("%s: channel_new: %d linked to control channel %d",
++	    __func__, nc->self, nc->ctl_chan);
+ 
+-	channel_send_open(c->self);
+-	channel_register_open_confirm(c->self, mux_session_confirm, cctx);
+-	return 0;
+-}
++	channel_send_open(nc->self);
++	channel_register_open_confirm(nc->self, mux_session_confirm, cctx);
++	channel_register_cleanup(nc->self, mux_master_session_cleanup_cb, 0);
+ 
+-/* ** Multiplexing client support */
++	/* prepare reply */
++	/* XXX defer until mux_session_confirm() fires */
++	buffer_put_int(r, MUX_S_SESSION_OPENED);
++	buffer_put_int(r, rid);
++	buffer_put_int(r, nc->self);
+ 
+-/* Exit signal handler */
+-static void
+-control_client_sighandler(int signo)
+-{
+-	muxclient_terminate = signo;
++	return 0;
+ }
+ 
+-/*
+- * Relay signal handler - used to pass some signals from mux client to
+- * mux master.
+- */
+-static void
+-control_client_sigrelay(int signo)
++static int
++process_mux_alive_check(u_int rid, Channel *c, Buffer *m, Buffer *r)
+ {
+-	int save_errno = errno;
++	debug2("%s: channel %d: alive check", __func__, c->self);
+ 
+-	if (muxserver_pid > 1)
+-		kill(muxserver_pid, signo);
++	/* prepare reply */
++	buffer_put_int(r, MUX_S_ALIVE);
++	buffer_put_int(r, rid);
++	buffer_put_int(r, (u_int)getpid());
+ 
+-	errno = save_errno;
++	return 0;
+ }
+ 
+-/* Check mux client environment variables before passing them to mux master. */
+ static int
+-env_permitted(char *env)
++process_mux_terminate(u_int rid, Channel *c, Buffer *m, Buffer *r)
+ {
+-	int i, ret;
+-	char name[1024], *cp;
+-
+-	if ((cp = strchr(env, '=')) == NULL || cp == env)
+-		return (0);
+-	ret = snprintf(name, sizeof(name), "%.*s", (int)(cp - env), env);
+-	if (ret <= 0 || (size_t)ret >= sizeof(name))
+-		fatal("env_permitted: name '%.100s...' too long", env);
+-
+-	for (i = 0; i < options.num_send_env; i++)
+-		if (match_pattern(name, options.send_env[i]))
+-			return (1);
++	debug2("%s: channel %d: terminate request", __func__, c->self);
++
++	if (options.control_master == SSHCTL_MASTER_ASK ||
++	    options.control_master == SSHCTL_MASTER_AUTO_ASK) {
++		if (!ask_permission("Terminate shared connection to %s? ",
++		    host)) {
++			debug2("%s: termination refused by user", __func__);
++			buffer_put_int(r, MUX_S_PERMISSION_DENIED);
++			buffer_put_int(r, rid);
++			buffer_put_cstring(r, "Permission denied");
++			return 0;
++		}
++	}
+ 
+-	return (0);
++	quit_pending = 1;
++	buffer_put_int(r, MUX_S_OK);
++	buffer_put_int(r, rid);
++	/* XXX exit happens too soon - message never makes it to client */
++	return 0;
+ }
+ 
+-/* Multiplex client main loop. */
+-void
+-muxclient(const char *path)
++static char *
++format_forward(u_int ftype, Forward *fwd)
+ {
+-	struct sockaddr_un addr;
+-	int i, r, fd, sock, exitval[2], num_env, addr_len;
+-	Buffer m;
+-	char *term;
+-	extern char **environ;
+-	u_int allowed, flags;
+-
+-	if (muxclient_command == 0)
+-		muxclient_command = SSHMUX_COMMAND_OPEN;
+-
+-	switch (options.control_master) {
+-	case SSHCTL_MASTER_AUTO:
+-	case SSHCTL_MASTER_AUTO_ASK:
+-		debug("auto-mux: Trying existing master");
+-		/* FALLTHROUGH */
+-	case SSHCTL_MASTER_NO:
++	char *ret;
++
++	switch (ftype) {
++	case MUX_FWD_LOCAL:
++		xasprintf(&ret, "local forward %.200s:%d -> %.200s:%d",
++		    (fwd->listen_host == NULL) ?
++		    (options.gateway_ports ? "*" : "LOCALHOST") :
++		    fwd->listen_host, fwd->listen_port,
++		    fwd->connect_host, fwd->connect_port);
++		break;
++	case MUX_FWD_DYNAMIC:
++		xasprintf(&ret, "dynamic forward %.200s:%d -> *",
++		    (fwd->listen_host == NULL) ?
++		    (options.gateway_ports ? "*" : "LOCALHOST") :
++		     fwd->listen_host, fwd->listen_port);
++		break;
++	case MUX_FWD_REMOTE:
++		xasprintf(&ret, "remote forward %.200s:%d -> %.200s:%d",
++		    (fwd->listen_host == NULL) ?
++		    "LOCALHOST" : fwd->listen_host,
++		    fwd->listen_port,
++		    fwd->connect_host, fwd->connect_port);
+ 		break;
+ 	default:
+-		return;
++		fatal("%s: unknown forward type %u", __func__, ftype);
+ 	}
++	return ret;
++}
+ 
+-	memset(&addr, '\0', sizeof(addr));
+-	addr.sun_family = AF_UNIX;
+-	addr_len = offsetof(struct sockaddr_un, sun_path) +
+-	    strlen(path) + 1;
++static int
++compare_host(const char *a, const char *b)
++{
++	if (a == NULL && b == NULL)
++		return 1;
++	if (a == NULL || b == NULL)
++		return 0;
++	return strcmp(a, b) == 0;
++}
+ 
+-	if (strlcpy(addr.sun_path, path,
+-	    sizeof(addr.sun_path)) >= sizeof(addr.sun_path))
+-		fatal("ControlPath too long");
++static int
++compare_forward(Forward *a, Forward *b)
++{
++	if (!compare_host(a->listen_host, b->listen_host))
++		return 0;
++	if (a->listen_port != b->listen_port)
++		return 0;
++	if (!compare_host(a->connect_host, b->connect_host))
++		return 0;
++	if (a->connect_port != b->connect_port)
++		return 0;
+ 
+-	if ((sock = socket(PF_UNIX, SOCK_STREAM, 0)) < 0)
+-		fatal("%s socket(): %s", __func__, strerror(errno));
++	return 1;
++}
+ 
+-	if (connect(sock, (struct sockaddr *)&addr, addr_len) == -1) {
+-		if (muxclient_command != SSHMUX_COMMAND_OPEN) {
+-			fatal("Control socket connect(%.100s): %s", path,
+-			    strerror(errno));
++static int
++process_mux_open_fwd(u_int rid, Channel *c, Buffer *m, Buffer *r)
++{
++	Forward fwd;
++	char *fwd_desc = NULL;
++	u_int ftype;
++	int i, ret = 0, freefwd = 1;
++
++	fwd.listen_host = fwd.connect_host = NULL;
++	if (buffer_get_int_ret(&ftype, m) != 0 ||
++	    (fwd.listen_host = buffer_get_string_ret(m, NULL)) == NULL ||
++	    buffer_get_int_ret(&fwd.listen_port, m) != 0 ||
++	    (fwd.connect_host = buffer_get_string_ret(m, NULL)) == NULL ||
++	    buffer_get_int_ret(&fwd.connect_port, m) != 0) {
++		error("%s: malformed message", __func__);
++		ret = -1;
++		goto out;
++	}
++
++	if (*fwd.listen_host == '\0') {
++		xfree(fwd.listen_host);
++		fwd.listen_host = NULL;
++	}
++	if (*fwd.connect_host == '\0') {
++		xfree(fwd.connect_host);
++		fwd.connect_host = NULL;
++	}
++
++	debug2("%s: channel %d: request %s", __func__, c->self,
++	    (fwd_desc = format_forward(ftype, &fwd)));
++
++	if (ftype != MUX_FWD_LOCAL && ftype != MUX_FWD_REMOTE &&
++	    ftype != MUX_FWD_DYNAMIC) {
++		logit("%s: invalid forwarding type %u", __func__, ftype);
++ invalid:
++		xfree(fwd.listen_host);
++		xfree(fwd.connect_host);
++		buffer_put_int(r, MUX_S_FAILURE);
++		buffer_put_int(r, rid);
++		buffer_put_cstring(r, "Invalid forwarding request");
++		return 0;
++	}
++	/* XXX support rport0 forwarding with reply of port assigned */
++	if (fwd.listen_port == 0 || fwd.listen_port >= 65536) {
++		logit("%s: invalid listen port %u", __func__,
++		    fwd.listen_port);
++		goto invalid;
++	}
++	if (fwd.connect_port >= 65536 || (ftype != MUX_FWD_DYNAMIC &&
++	    ftype != MUX_FWD_REMOTE && fwd.connect_port == 0)) {
++		logit("%s: invalid connect port %u", __func__,
++		    fwd.connect_port);
++		goto invalid;
++	}
++	if (ftype != MUX_FWD_DYNAMIC && fwd.connect_host == NULL) {
++		logit("%s: missing connect host", __func__);
++		goto invalid;
++	}
++
++	/* Skip forwards that have already been requested */
++	switch (ftype) {
++	case MUX_FWD_LOCAL:
++	case MUX_FWD_DYNAMIC:
++		for (i = 0; i < options.num_local_forwards; i++) {
++			if (compare_forward(&fwd,
++			    options.local_forwards + i)) {
++ exists:
++				debug2("%s: found existing forwarding",
++				    __func__);
++				buffer_put_int(r, MUX_S_OK);
++				buffer_put_int(r, rid);
++				goto out;
++			}
+ 		}
+-		if (errno == ENOENT)
+-			debug("Control socket \"%.100s\" does not exist", path);
+-		else {
+-			error("Control socket connect(%.100s): %s", path,
+-			    strerror(errno));
++		break;
++	case MUX_FWD_REMOTE:
++		for (i = 0; i < options.num_remote_forwards; i++) {
++			if (compare_forward(&fwd,
++			    options.remote_forwards + i))
++				goto exists;
+ 		}
+-		close(sock);
+-		return;
++		break;
+ 	}
+ 
+-	if (stdin_null_flag) {
+-		if ((fd = open(_PATH_DEVNULL, O_RDONLY)) == -1)
+-			fatal("open(/dev/null): %s", strerror(errno));
+-		if (dup2(fd, STDIN_FILENO) == -1)
+-			fatal("dup2: %s", strerror(errno));
+-		if (fd > STDERR_FILENO)
+-			close(fd);
++	if (options.control_master == SSHCTL_MASTER_ASK ||
++	    options.control_master == SSHCTL_MASTER_AUTO_ASK) {
++		if (!ask_permission("Open %s on %s?", fwd_desc, host)) {
++			debug2("%s: forwarding refused by user", __func__);
++			buffer_put_int(r, MUX_S_PERMISSION_DENIED);
++			buffer_put_int(r, rid);
++			buffer_put_cstring(r, "Permission denied");
++			goto out;
++		}
+ 	}
+ 
+-	term = getenv("TERM");
++	if (ftype == MUX_FWD_LOCAL || ftype == MUX_FWD_DYNAMIC) {
++		if (options.num_local_forwards + 1 >=
++		    SSH_MAX_FORWARDS_PER_DIRECTION ||
++		    channel_setup_local_fwd_listener(fwd.listen_host,
++		    fwd.listen_port, fwd.connect_host, fwd.connect_port,
++		    options.gateway_ports) < 0) {
++ fail:
++			logit("slave-requested %s failed", fwd_desc);
++			buffer_put_int(r, MUX_S_FAILURE);
++			buffer_put_int(r, rid);
++			buffer_put_cstring(r, "Port forwarding failed");
++			goto out;
++		}
++		add_local_forward(&options, &fwd);
++		freefwd = 0;
++	} else {
++		/* XXX wait for remote to confirm */
++		if (options.num_remote_forwards + 1 >=
++		    SSH_MAX_FORWARDS_PER_DIRECTION ||
++		    channel_request_remote_forwarding(fwd.listen_host,
++		    fwd.listen_port, fwd.connect_host, fwd.connect_port) < 0)
++			goto fail;
++		add_remote_forward(&options, &fwd);
++		freefwd = 0;
++	}
++	buffer_put_int(r, MUX_S_OK);
++	buffer_put_int(r, rid);
++ out:
++	if (fwd_desc != NULL)
++		xfree(fwd_desc);
++	if (freefwd) {
++		if (fwd.listen_host != NULL)
++			xfree(fwd.listen_host);
++		if (fwd.connect_host != NULL)
++			xfree(fwd.connect_host);
++	}
++	return ret;
++}
+ 
+-	flags = 0;
+-	if (tty_flag)
+-		flags |= SSHMUX_FLAG_TTY;
+-	if (subsystem_flag)
+-		flags |= SSHMUX_FLAG_SUBSYS;
+-	if (options.forward_x11)
+-		flags |= SSHMUX_FLAG_X11_FWD;
+-	if (options.forward_agent)
+-		flags |= SSHMUX_FLAG_AGENT_FWD;
++static int
++process_mux_close_fwd(u_int rid, Channel *c, Buffer *m, Buffer *r)
++{
++	Forward fwd;
++	char *fwd_desc = NULL;
++	u_int ftype;
++	int ret = 0;
++
++	fwd.listen_host = fwd.connect_host = NULL;
++	if (buffer_get_int_ret(&ftype, m) != 0 ||
++	    (fwd.listen_host = buffer_get_string_ret(m, NULL)) == NULL ||
++	    buffer_get_int_ret(&fwd.listen_port, m) != 0 ||
++	    (fwd.connect_host = buffer_get_string_ret(m, NULL)) == NULL ||
++	    buffer_get_int_ret(&fwd.connect_port, m) != 0) {
++		error("%s: malformed message", __func__);
++		ret = -1;
++		goto out;
++	}
+ 
+-	signal(SIGPIPE, SIG_IGN);
++	if (*fwd.listen_host == '\0') {
++		xfree(fwd.listen_host);
++		fwd.listen_host = NULL;
++	}
++	if (*fwd.connect_host == '\0') {
++		xfree(fwd.connect_host);
++		fwd.connect_host = NULL;
++	}
++
++	debug2("%s: channel %d: request %s", __func__, c->self,
++	    (fwd_desc = format_forward(ftype, &fwd)));
++
++	/* XXX implement this */
++	buffer_put_int(r, MUX_S_FAILURE);
++	buffer_put_int(r, rid);
++	buffer_put_cstring(r, "unimplemented");
++
++ out:
++	if (fwd_desc != NULL)
++		xfree(fwd_desc);
++	if (fwd.listen_host != NULL)
++		xfree(fwd.listen_host);
++	if (fwd.connect_host != NULL)
++		xfree(fwd.connect_host);
++
++	return ret;
++}
++
++static int
++process_mux_stdio_fwd(u_int rid, Channel *c, Buffer *m, Buffer *r)
++{
++	Channel *nc;
++	char *reserved, *chost;
++	u_int cport, i, j;
++	int new_fd[2];
++
++	chost = reserved = NULL;
++	if ((reserved = buffer_get_string_ret(m, NULL)) == NULL ||
++	   (chost = buffer_get_string_ret(m, NULL)) == NULL ||
++	    buffer_get_int_ret(&cport, m) != 0) {
++		if (reserved != NULL)
++			xfree(reserved);
++		if (chost != NULL)
++			xfree(chost);
++		error("%s: malformed message", __func__);
++		return -1;
++	}
++	xfree(reserved);
++
++	debug2("%s: channel %d: request stdio fwd to %s:%u",
++	    __func__, c->self, chost, cport);
++
++	/* Gather fds from client */
++	for(i = 0; i < 2; i++) {
++		if ((new_fd[i] = mm_receive_fd(c->sock)) == -1) {
++			error("%s: failed to receive fd %d from slave",
++			    __func__, i);
++			for (j = 0; j < i; j++)
++				close(new_fd[j]);
++			xfree(chost);
++
++			/* prepare reply */
++			buffer_put_int(r, MUX_S_FAILURE);
++			buffer_put_int(r, rid);
++			buffer_put_cstring(r,
++			    "did not receive file descriptors");
++			return -1;
++		}
++	}
++
++	debug3("%s: got fds stdin %d, stdout %d", __func__,
++	    new_fd[0], new_fd[1]);
++
++	/* XXX support multiple child sessions in future */
++	if (c->remote_id != -1) {
++		debug2("%s: session already open", __func__);
++		/* prepare reply */
++		buffer_put_int(r, MUX_S_FAILURE);
++		buffer_put_int(r, rid);
++		buffer_put_cstring(r, "Multiple sessions not supported");
++ cleanup:
++		close(new_fd[0]);
++		close(new_fd[1]);
++		xfree(chost);
++		return 0;
++	}
++
++	if (options.control_master == SSHCTL_MASTER_ASK ||
++	    options.control_master == SSHCTL_MASTER_AUTO_ASK) {
++		if (!ask_permission("Allow forward to to %s:%u? ",
++		    chost, cport)) {
++			debug2("%s: stdio fwd refused by user", __func__);
++			/* prepare reply */
++			buffer_put_int(r, MUX_S_PERMISSION_DENIED);
++			buffer_put_int(r, rid);
++			buffer_put_cstring(r, "Permission denied");
++			goto cleanup;
++		}
++	}
++
++	/* enable nonblocking unless tty */
++	if (!isatty(new_fd[0]))
++		set_nonblock(new_fd[0]);
++	if (!isatty(new_fd[1]))
++		set_nonblock(new_fd[1]);
++
++	nc = channel_connect_stdio_fwd(chost, cport, new_fd[0], new_fd[1]);
+ 
++	nc->ctl_chan = c->self;		/* link session -> control channel */
++	c->remote_id = nc->self; 	/* link control -> session channel */
++
++	debug2("%s: channel_new: %d linked to control channel %d",
++	    __func__, nc->self, nc->ctl_chan);
++
++	channel_register_cleanup(nc->self, mux_master_session_cleanup_cb, 0);
++
++	/* prepare reply */
++	/* XXX defer until channel confirmed */
++	buffer_put_int(r, MUX_S_SESSION_OPENED);
++	buffer_put_int(r, rid);
++	buffer_put_int(r, nc->self);
++
++	return 0;
++}
++
++/* Channel callbacks fired on read/write from mux slave fd */
++static int
++mux_master_read_cb(Channel *c)
++{
++	struct mux_master_state *state = (struct mux_master_state *)c->mux_ctx;
++	Buffer in, out;
++	void *ptr;
++	u_int type, rid, have, i;
++	int ret = -1;
++
++	/* Setup ctx and  */
++	if (c->mux_ctx == NULL) {
++		state = xcalloc(1, sizeof(state));
++		c->mux_ctx = state;
++		channel_register_cleanup(c->self,
++		    mux_master_control_cleanup_cb, 0);
++
++		/* Send hello */
++		buffer_init(&out);
++		buffer_put_int(&out, MUX_MSG_HELLO);
++		buffer_put_int(&out, SSHMUX_VER);
++		/* no extensions */
++		buffer_put_string(&c->output, buffer_ptr(&out),
++		    buffer_len(&out));
++		buffer_free(&out);
++		debug3("%s: channel %d: hello sent", __func__, c->self);
++		return 0;
++	}
++
++	buffer_init(&in);
++	buffer_init(&out);
++
++	/* Channel code ensures that we receive whole packets */
++	if ((ptr = buffer_get_string_ptr_ret(&c->input, &have)) == NULL) {
++ malf:
++		error("%s: malformed message", __func__);
++		goto out;
++	}
++	buffer_append(&in, ptr, have);
++
++	if (buffer_get_int_ret(&type, &in) != 0)
++		goto malf;
++	debug3("%s: channel %d packet type 0x%08x len %u",
++	    __func__, c->self, type, buffer_len(&in));
++
++	if (type == MUX_MSG_HELLO)
++		rid = 0;
++	else {
++		if (!state->hello_rcvd) {
++			error("%s: expected MUX_MSG_HELLO(0x%08x), "
++			    "received 0x%08x", __func__, MUX_MSG_HELLO, type);
++			goto out;
++		}
++		if (buffer_get_int_ret(&rid, &in) != 0)
++			goto malf;
++	}
++
++	for (i = 0; mux_master_handlers[i].handler != NULL; i++) {
++		if (type == mux_master_handlers[i].type) {
++			ret = mux_master_handlers[i].handler(rid, c, &in, &out);
++			break;
++		}
++	}
++	if (mux_master_handlers[i].handler == NULL) {
++		error("%s: unsupported mux message 0x%08x", __func__, type);
++		buffer_put_int(&out, MUX_S_FAILURE);
++		buffer_put_int(&out, rid);
++		buffer_put_cstring(&out, "unsupported request");
++		ret = 0;
++	}
++	/* Enqueue reply packet */
++	if (buffer_len(&out) != 0) {
++		buffer_put_string(&c->output, buffer_ptr(&out),
++		    buffer_len(&out));
++	}
++ out:
++	buffer_free(&in);
++	buffer_free(&out);
++	return ret;
++}
++
++void
++mux_exit_message(Channel *c, int exitval)
++{
++	Buffer m;
++	Channel *mux_chan;
++
++	debug3("%s: channel %d: exit message, evitval %d", __func__, c->self,
++	    exitval);
++
++	if ((mux_chan = channel_by_id(c->ctl_chan)) == NULL)
++		fatal("%s: channel %d missing mux channel %d",
++		    __func__, c->self, c->ctl_chan);
++
++	/* Append exit message packet to control socket output queue */
+ 	buffer_init(&m);
++	buffer_put_int(&m, MUX_S_EXIT_MESSAGE);
++	buffer_put_int(&m, c->self);
++	buffer_put_int(&m, exitval);
+ 
+-	/* Send our command to server */
+-	buffer_put_int(&m, muxclient_command);
+-	buffer_put_int(&m, flags);
+-	if (ssh_msg_send(sock, SSHMUX_VER, &m) == -1) {
+-		error("%s: msg_send", __func__);
+- muxerr:
+-		close(sock);
+-		buffer_free(&m);
+-		if (muxclient_command != SSHMUX_COMMAND_OPEN)
+-			cleanup_exit(255);
+-		logit("Falling back to non-multiplexed connection");
+-		xfree(options.control_path);
+-		options.control_path = NULL;
+-		options.control_master = SSHCTL_MASTER_NO;
++	buffer_put_string(&mux_chan->output, buffer_ptr(&m), buffer_len(&m));
++	buffer_free(&m);
++}
++
++/* Prepare a mux master to listen on a Unix domain socket. */
++void
++muxserver_listen(void)
++{
++	struct sockaddr_un addr;
++	socklen_t sun_len;
++	mode_t old_umask;
++
++	if (options.control_path == NULL ||
++	    options.control_master == SSHCTL_MASTER_NO)
+ 		return;
++
++	debug("setting up multiplex master socket");
++
++	memset(&addr, '\0', sizeof(addr));
++	addr.sun_family = AF_UNIX;
++	sun_len = offsetof(struct sockaddr_un, sun_path) +
++	    strlen(options.control_path) + 1;
++
++	if (strlcpy(addr.sun_path, options.control_path,
++	    sizeof(addr.sun_path)) >= sizeof(addr.sun_path))
++		fatal("ControlPath too long");
++
++	if ((muxserver_sock = socket(PF_UNIX, SOCK_STREAM, 0)) < 0)
++		fatal("%s socket(): %s", __func__, strerror(errno));
++
++	old_umask = umask(0177);
++	if (bind(muxserver_sock, (struct sockaddr *)&addr, sun_len) == -1) {
++		muxserver_sock = -1;
++		if (errno == EINVAL || errno == EADDRINUSE) {
++			error("ControlSocket %s already exists, "
++			    "disabling multiplexing", options.control_path);
++			close(muxserver_sock);
++			muxserver_sock = -1;
++			xfree(options.control_path);
++			options.control_path = NULL;
++			options.control_master = SSHCTL_MASTER_NO;
++			return;
++		} else
++			fatal("%s bind(): %s", __func__, strerror(errno));
++	}
++	umask(old_umask);
++
++	if (listen(muxserver_sock, 64) == -1)
++		fatal("%s listen(): %s", __func__, strerror(errno));
++
++	set_nonblock(muxserver_sock);
++
++	mux_listener_channel = channel_new("mux listener",
++	    SSH_CHANNEL_MUX_LISTENER, muxserver_sock, muxserver_sock, -1,
++	    CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT,
++	    0, addr.sun_path, 1);
++	mux_listener_channel->mux_rcb = mux_master_read_cb;
++	debug3("%s: mux listener channel %d fd %d", __func__,
++	    mux_listener_channel->self, mux_listener_channel->sock);
++}
++
++/* Callback on open confirmation in mux master for a mux client session. */
++static void
++mux_session_confirm(int id, void *arg)
++{
++	struct mux_session_confirm_ctx *cctx = arg;
++	const char *display;
++	Channel *c;
++	int i;
++
++	if (cctx == NULL)
++		fatal("%s: cctx == NULL", __func__);
++	if ((c = channel_by_id(id)) == NULL)
++		fatal("%s: no channel for id %d", __func__, id);
++
++	display = getenv("DISPLAY");
++	if (cctx->want_x_fwd && options.forward_x11 && display != NULL) {
++		char *proto, *data;
++		/* Get reasonable local authentication information. */
++		client_x11_get_proto(display, options.xauth_location,
++		    options.forward_x11_trusted, &proto, &data);
++		/* Request forwarding with authentication spoofing. */
++		debug("Requesting X11 forwarding with authentication spoofing.");
++		x11_request_forwarding_with_spoofing(id, display, proto, data);
++		/* XXX wait for reply */
++	}
++
++	if (cctx->want_agent_fwd && options.forward_agent) {
++		debug("Requesting authentication agent forwarding.");
++		channel_request_start(id, "auth-agent-req at openssh.com", 0);
++		packet_send();
++	}
++
++	client_session2_setup(id, cctx->want_tty, cctx->want_subsys,
++	    cctx->term, &cctx->tio, c->rfd, &cctx->cmd, cctx->env);
++
++	c->open_confirm_ctx = NULL;
++	buffer_free(&cctx->cmd);
++	xfree(cctx->term);
++	if (cctx->env != NULL) {
++		for (i = 0; cctx->env[i] != NULL; i++)
++			xfree(cctx->env[i]);
++		xfree(cctx->env);
++	}
++	xfree(cctx);
++}
++
++/* ** Multiplexing client support */
++
++/* Exit signal handler */
++static void
++control_client_sighandler(int signo)
++{
++	muxclient_terminate = signo;
++}
++
++/*
++ * Relay signal handler - used to pass some signals from mux client to
++ * mux master.
++ */
++static void
++control_client_sigrelay(int signo)
++{
++	int save_errno = errno;
++
++	if (muxserver_pid > 1)
++		kill(muxserver_pid, signo);
++
++	errno = save_errno;
++}
++
++static int
++mux_client_read(int fd, Buffer *b, u_int need)
++{
++	u_int have;
++	ssize_t len;
++	u_char *p;
++	struct pollfd pfd;
++
++	pfd.fd = fd;
++	pfd.events = POLLIN;
++	p = buffer_append_space(b, need);
++	for (have = 0; have < need; ) {
++		if (muxclient_terminate) {
++			errno = EINTR;
++			return -1;
++		}
++		len = read(fd, p + have, need - have);
++		if (len < 0) {
++			switch (errno) {
++#if defined(EWOULDBLOCK) && (EWOULDBLOCK != EAGAIN)
++			case EWOULDBLOCK:
++#endif
++			case EAGAIN:
++				(void)poll(&pfd, 1, -1);
++				/* FALLTHROUGH */
++			case EINTR:
++				continue;
++			default:
++				return -1;
++			}
++		}
++		if (len == 0) {
++			errno = EPIPE;
++			return -1;
++		}
++		have += (u_int)len;
++	}
++	return 0;
++}
++
++static int
++mux_client_write_packet(int fd, Buffer *m)
++{
++	Buffer queue;
++	u_int have, need;
++	int oerrno, len;
++	u_char *ptr;
++	struct pollfd pfd;
++
++	pfd.fd = fd;
++	pfd.events = POLLOUT;
++	buffer_init(&queue);
++	buffer_put_string(&queue, buffer_ptr(m), buffer_len(m));
++
++	need = buffer_len(&queue);
++	ptr = buffer_ptr(&queue);
++
++	for (have = 0; have < need; ) {
++		if (muxclient_terminate) {
++			buffer_free(&queue);
++			errno = EINTR;
++			return -1;
++		}
++		len = write(fd, ptr + have, need - have);
++		if (len < 0) {
++			switch (errno) {
++#if defined(EWOULDBLOCK) && (EWOULDBLOCK != EAGAIN)
++			case EWOULDBLOCK:
++#endif
++			case EAGAIN:
++				(void)poll(&pfd, 1, -1);
++				/* FALLTHROUGH */
++			case EINTR:
++				continue;
++			default:
++				oerrno = errno;
++				buffer_free(&queue);
++				errno = oerrno;
++				return -1;
++			}
++		}
++		if (len == 0) {
++			buffer_free(&queue);
++			errno = EPIPE;
++			return -1;
++		}
++		have += (u_int)len;
++	}
++	buffer_free(&queue);
++	return 0;
++}
++
++static int
++mux_client_read_packet(int fd, Buffer *m)
++{
++	Buffer queue;
++	u_int need, have;
++	void *ptr;
++	int oerrno;
++
++	buffer_init(&queue);
++	if (mux_client_read(fd, &queue, 4) != 0) {
++		if ((oerrno = errno) == EPIPE)
++		debug3("%s: read header failed: %s", __func__, strerror(errno));
++		errno = oerrno;
++		return -1;
++	}
++	need = get_u32(buffer_ptr(&queue));
++	if (mux_client_read(fd, &queue, need) != 0) {
++		oerrno = errno;
++		debug3("%s: read body failed: %s", __func__, strerror(errno));
++		errno = oerrno;
++		return -1;
+ 	}
++	ptr = buffer_get_string_ptr(&queue, &have);
++	buffer_append(m, ptr, have);
++	buffer_free(&queue);
++	return 0;
++}
++
++static int
++mux_client_hello_exchange(int fd)
++{
++	Buffer m;
++	u_int type, ver;
++
++	buffer_init(&m);
++	buffer_put_int(&m, MUX_MSG_HELLO);
++	buffer_put_int(&m, SSHMUX_VER);
++	/* no extensions */
++
++	if (mux_client_write_packet(fd, &m) != 0)
++		fatal("%s: write packet: %s", __func__, strerror(errno));
++
+ 	buffer_clear(&m);
+ 
+-	/* Get authorisation status and PID of controlee */
+-	if (ssh_msg_recv(sock, &m) == -1) {
+-		error("%s: Did not receive reply from master", __func__);
+-		goto muxerr;
++	/* Read their HELLO */
++	if (mux_client_read_packet(fd, &m) != 0) {
++		buffer_free(&m);
++		return -1;
+ 	}
+-	if (buffer_get_char(&m) != SSHMUX_VER) {
+-		error("%s: Master replied with wrong version", __func__);
+-		goto muxerr;
++
++	type = buffer_get_int(&m);
++	if (type != MUX_MSG_HELLO)
++		fatal("%s: expected HELLO (%u) received %u",
++		    __func__, MUX_MSG_HELLO, type);
++	ver = buffer_get_int(&m);
++	if (ver != SSHMUX_VER)
++		fatal("Unsupported multiplexing protocol version %d "
++		    "(expected %d)", ver, SSHMUX_VER);
++	debug2("%s: master version %u", __func__, ver);
++	/* No extensions are presently defined */
++	while (buffer_len(&m) > 0) {
++		char *name = buffer_get_string(&m, NULL);
++		char *value = buffer_get_string(&m, NULL);
++
++		debug2("Unrecognised master extension \"%s\"", name);
++		xfree(name);
++		xfree(value);
+ 	}
+-	if (buffer_get_int_ret(&allowed, &m) != 0) {
+-		error("%s: bad server reply", __func__);
+-		goto muxerr;
++	buffer_free(&m);
++	return 0;
++}
++
++static u_int
++mux_client_request_alive(int fd)
++{
++	Buffer m;
++	char *e;
++	u_int pid, type, rid;
++
++	debug3("%s: entering", __func__);
++
++	buffer_init(&m);
++	buffer_put_int(&m, MUX_C_ALIVE_CHECK);
++	buffer_put_int(&m, muxclient_request_id);
++
++	if (mux_client_write_packet(fd, &m) != 0)
++		fatal("%s: write packet: %s", __func__, strerror(errno));
++
++	buffer_clear(&m);
++
++	/* Read their reply */
++	if (mux_client_read_packet(fd, &m) != 0) {
++		buffer_free(&m);
++		return 0;
+ 	}
+-	if (allowed != 1) {
+-		error("Connection to master denied");
+-		goto muxerr;
++
++	type = buffer_get_int(&m);
++	if (type != MUX_S_ALIVE) {
++		e = buffer_get_string(&m, NULL);
++		fatal("%s: master returned error: %s", __func__, e);
+ 	}
+-	muxserver_pid = buffer_get_int(&m);
++
++	if ((rid = buffer_get_int(&m)) != muxclient_request_id)
++		fatal("%s: out of sequence reply: my id %u theirs %u",
++		    __func__, muxclient_request_id, rid);
++	pid = buffer_get_int(&m);
++	buffer_free(&m);
++
++	debug3("%s: done pid = %u", __func__, pid);
++
++	muxclient_request_id++;
++
++	return pid;
++}
++
++static void
++mux_client_request_terminate(int fd)
++{
++	Buffer m;
++	char *e;
++	u_int type, rid;
++
++	debug3("%s: entering", __func__);
++
++	buffer_init(&m);
++	buffer_put_int(&m, MUX_C_TERMINATE);
++	buffer_put_int(&m, muxclient_request_id);
++
++	if (mux_client_write_packet(fd, &m) != 0)
++		fatal("%s: write packet: %s", __func__, strerror(errno));
+ 
+ 	buffer_clear(&m);
+ 
+-	switch (muxclient_command) {
+-	case SSHMUX_COMMAND_ALIVE_CHECK:
+-		fprintf(stderr, "Master running (pid=%d)\r\n",
+-		    muxserver_pid);
+-		exit(0);
+-	case SSHMUX_COMMAND_TERMINATE:
+-		fprintf(stderr, "Exit request sent.\r\n");
+-		exit(0);
+-	case SSHMUX_COMMAND_OPEN:
+-		buffer_put_cstring(&m, term ? term : "");
+-		if (options.escape_char == SSH_ESCAPECHAR_NONE)
+-			buffer_put_int(&m, 0xffffffff);
+-		else
+-			buffer_put_int(&m, options.escape_char);
+-		buffer_append(&command, "\0", 1);
+-		buffer_put_cstring(&m, buffer_ptr(&command));
+-
+-		if (options.num_send_env == 0 || environ == NULL) {
+-			buffer_put_int(&m, 0);
+-		} else {
+-			/* Pass environment */
+-			num_env = 0;
+-			for (i = 0; environ[i] != NULL; i++) {
+-				if (env_permitted(environ[i]))
+-					num_env++; /* Count */
+-			}
+-			buffer_put_int(&m, num_env);
+-		for (i = 0; environ[i] != NULL && num_env >= 0; i++) {
+-				if (env_permitted(environ[i])) {
+-					num_env--;
+-					buffer_put_cstring(&m, environ[i]);
+-				}
+-			}
++	/* Read their reply */
++	if (mux_client_read_packet(fd, &m) != 0) {
++		/* Remote end exited already */
++		if (errno == EPIPE) {
++			buffer_free(&m);
++			return;
+ 		}
++		fatal("%s: read from master failed: %s",
++		    __func__, strerror(errno));
++	}
++
++	type = buffer_get_int(&m);
++	if ((rid = buffer_get_int(&m)) != muxclient_request_id)
++		fatal("%s: out of sequence reply: my id %u theirs %u",
++		    __func__, muxclient_request_id, rid);
++	switch (type) {
++	case MUX_S_OK:
+ 		break;
++	case MUX_S_PERMISSION_DENIED:
++		e = buffer_get_string(&m, NULL);
++		fatal("Master refused termination request: %s", e);
++	case MUX_S_FAILURE:
++		e = buffer_get_string(&m, NULL);
++		fatal("%s: termination request failed: %s", __func__, e);
+ 	default:
+-		fatal("unrecognised muxclient_command %d", muxclient_command);
++		fatal("%s: unexpected response from master 0x%08x",
++		    __func__, type);
+ 	}
++	buffer_free(&m);
++	muxclient_request_id++;
++}
+ 
+-	if (ssh_msg_send(sock, SSHMUX_VER, &m) == -1) {
+-		error("%s: msg_send", __func__);
+-		goto muxerr;
++static int
++mux_client_request_forward(int fd, u_int ftype, Forward *fwd)
++{
++	Buffer m;
++	char *e, *fwd_desc;
++	u_int type, rid;
++
++	fwd_desc = format_forward(ftype, fwd);
++	debug("Requesting %s", fwd_desc);
++	xfree(fwd_desc);
++
++	buffer_init(&m);
++	buffer_put_int(&m, MUX_C_OPEN_FWD);
++	buffer_put_int(&m, muxclient_request_id);
++	buffer_put_int(&m, ftype);
++	buffer_put_cstring(&m,
++	    fwd->listen_host == NULL ? "" : fwd->listen_host);
++	buffer_put_int(&m, fwd->listen_port);
++	buffer_put_cstring(&m,
++	    fwd->connect_host == NULL ? "" : fwd->connect_host);
++	buffer_put_int(&m, fwd->connect_port);
++
++	if (mux_client_write_packet(fd, &m) != 0)
++		fatal("%s: write packet: %s", __func__, strerror(errno));
++
++	buffer_clear(&m);
++
++	/* Read their reply */
++	if (mux_client_read_packet(fd, &m) != 0) {
++		buffer_free(&m);
++		return -1;
+ 	}
+ 
+-	if (mm_send_fd(sock, STDIN_FILENO) == -1 ||
+-	    mm_send_fd(sock, STDOUT_FILENO) == -1 ||
+-	    mm_send_fd(sock, STDERR_FILENO) == -1) {
+-		error("%s: send fds failed", __func__);
+-		goto muxerr;
++	type = buffer_get_int(&m);
++	if ((rid = buffer_get_int(&m)) != muxclient_request_id)
++		fatal("%s: out of sequence reply: my id %u theirs %u",
++		    __func__, muxclient_request_id, rid);
++	switch (type) {
++	case MUX_S_OK:
++		break;
++	case MUX_S_PERMISSION_DENIED:
++		e = buffer_get_string(&m, NULL);
++		buffer_free(&m);
++		error("Master refused forwarding request: %s", e);
++		return -1;
++	case MUX_S_FAILURE:
++		e = buffer_get_string(&m, NULL);
++		buffer_free(&m);
++		error("%s: session request failed: %s", __func__, e);
++		return -1;
++	default:
++		fatal("%s: unexpected response from master 0x%08x",
++		    __func__, type);
+ 	}
++	buffer_free(&m);
+ 
+-	/*
+-	 * Mux errors are non-recoverable from this point as the master
+-	 * has ownership of the session now.
+-	 */
++	muxclient_request_id++;
++	return 0;
++}
++
++static int
++mux_client_request_forwards(int fd)
++{
++	int i;
++
++	debug3("%s: requesting forwardings: %d local, %d remote", __func__,
++	    options.num_local_forwards, options.num_remote_forwards);
++
++	/* XXX ExitOnForwardingFailure */
++	for (i = 0; i < options.num_local_forwards; i++) {
++		if (mux_client_request_forward(fd,
++		    options.local_forwards[i].connect_port == 0 ?
++		    MUX_FWD_DYNAMIC : MUX_FWD_LOCAL,
++		    options.local_forwards + i) != 0)
++			return -1;
++	}
++	for (i = 0; i < options.num_remote_forwards; i++) {
++		if (mux_client_request_forward(fd, MUX_FWD_REMOTE,
++		    options.remote_forwards + i) != 0)
++			return -1;
++	}
++	return 0;
++}
++
++static int
++mux_client_request_session(int fd)
++{
++	Buffer m;
++	char *e, *term;
++	u_int i, rid, sid, esid, exitval, type, exitval_seen;
++	extern char **environ;
++	int devnull;
++
++	debug3("%s: entering", __func__);
++
++	if ((muxserver_pid = mux_client_request_alive(fd)) == 0) {
++		error("%s: master alive request failed", __func__);
++		return -1;
++	}
++
++	signal(SIGPIPE, SIG_IGN);
++
++	if (stdin_null_flag) {
++		if ((devnull = open(_PATH_DEVNULL, O_RDONLY)) == -1)
++			fatal("open(/dev/null): %s", strerror(errno));
++		if (dup2(devnull, STDIN_FILENO) == -1)
++			fatal("dup2: %s", strerror(errno));
++		if (devnull > STDERR_FILENO)
++			close(devnull);
++	}
++
++	term = getenv("TERM");
++
++	buffer_init(&m);
++	buffer_put_int(&m, MUX_C_NEW_SESSION);
++	buffer_put_int(&m, muxclient_request_id);
++	buffer_put_cstring(&m, ""); /* reserved */
++	buffer_put_int(&m, tty_flag);
++	buffer_put_int(&m, options.forward_x11);
++	buffer_put_int(&m, options.forward_agent);
++	buffer_put_int(&m, subsystem_flag);
++	buffer_put_int(&m, options.escape_char == SSH_ESCAPECHAR_NONE ?
++	    0xffffffff : (u_int)options.escape_char);
++	buffer_put_cstring(&m, term == NULL ? "" : term);
++	buffer_put_string(&m, buffer_ptr(&command), buffer_len(&command));
++
++	if (options.num_send_env > 0 && environ != NULL) {
++		/* Pass environment */
++		for (i = 0; environ[i] != NULL; i++) {
++			if (env_permitted(environ[i])) {
++				buffer_put_cstring(&m, environ[i]);
++			}
++		}
++	}
++
++	if (mux_client_write_packet(fd, &m) != 0)
++		fatal("%s: write packet: %s", __func__, strerror(errno));
++
++	/* Send the stdio file descriptors */
++	if (mm_send_fd(fd, STDIN_FILENO) == -1 ||
++	    mm_send_fd(fd, STDOUT_FILENO) == -1 ||
++	    mm_send_fd(fd, STDERR_FILENO) == -1)
++		fatal("%s: send fds failed", __func__);
+ 
+-	/* Wait for reply, so master has a chance to gather ttymodes */
++	debug3("%s: session request sent", __func__);
++
++	/* Read their reply */
+ 	buffer_clear(&m);
+-	if (ssh_msg_recv(sock, &m) == -1)
+-		fatal("%s: msg_recv", __func__);
+-	if (buffer_get_char(&m) != SSHMUX_VER)
+-		fatal("%s: wrong version", __func__);
+-	buffer_free(&m);
++	if (mux_client_read_packet(fd, &m) != 0) {
++		error("%s: read from master failed: %s",
++		    __func__, strerror(errno));
++		buffer_free(&m);
++		return -1;
++	}
++
++	type = buffer_get_int(&m);
++	if ((rid = buffer_get_int(&m)) != muxclient_request_id)
++		fatal("%s: out of sequence reply: my id %u theirs %u",
++		    __func__, muxclient_request_id, rid);
++	switch (type) {
++	case MUX_S_SESSION_OPENED:
++		sid = buffer_get_int(&m);
++		debug("%s: master session id: %u", __func__, sid);
++		break;
++	case MUX_S_PERMISSION_DENIED:
++		e = buffer_get_string(&m, NULL);
++		buffer_free(&m);
++		error("Master refused forwarding request: %s", e);
++		return -1;
++	case MUX_S_FAILURE:
++		e = buffer_get_string(&m, NULL);
++		buffer_free(&m);
++		error("%s: forwarding request failed: %s", __func__, e);
++		return -1;
++	default:
++		buffer_free(&m);
++		error("%s: unexpected response from master 0x%08x",
++		    __func__, type);
++		return -1;
++	}
++	muxclient_request_id++;
+ 
+ 	signal(SIGHUP, control_client_sighandler);
+ 	signal(SIGINT, control_client_sighandler);
+@@ -687,42 +1508,230 @@ muxclient(const char *path)
+ 
+ 	/*
+ 	 * Stick around until the controlee closes the client_fd.
+-	 * Before it does, it is expected to write this process' exit
+-	 * value (one int). This process must read the value and wait for
+-	 * the closure of the client_fd; if this one closes early, the 
+-	 * multiplex master will terminate early too (possibly losing data).
++	 * Before it does, it is expected to write an exit message.
++	 * This process must read the value and wait for the closure of
++	 * the client_fd; if this one closes early, the multiplex master will
++	 * terminate early too (possibly losing data).
+ 	 */
+-	exitval[0] = 0;
+-	for (i = 0; !muxclient_terminate && i < (int)sizeof(exitval);) {
+-		r = read(sock, (char *)exitval + i, sizeof(exitval) - i);
+-		if (r == 0) {
+-			debug2("Received EOF from master");
++	for (exitval = 255, exitval_seen = 0;;) {
++		buffer_clear(&m);
++		if (mux_client_read_packet(fd, &m) != 0)
+ 			break;
++		type = buffer_get_int(&m);
++		if (type != MUX_S_EXIT_MESSAGE) {
++			e = buffer_get_string(&m, NULL);
++			fatal("%s: master returned error: %s", __func__, e);
+ 		}
+-		if (r == -1) {
+-			if (errno == EINTR)
+-				continue;
+-			fatal("%s: read %s", __func__, strerror(errno));
+-		}
+-		i += r;
++		if ((esid = buffer_get_int(&m)) != sid)
++			fatal("%s: exit on unknown session: my id %u theirs %u",
++			    __func__, sid, esid);
++		debug("%s: master session id: %u", __func__, sid);
++		if (exitval_seen)
++			fatal("%s: exitval sent twice", __func__);
++		exitval = buffer_get_int(&m);
++		exitval_seen = 1;
+ 	}
+ 
+-	close(sock);
++	close(fd);
+ 	leave_raw_mode();
+-	if (i > (int)sizeof(int))
+-		fatal("%s: master returned too much data (%d > %lu)",
+-		    __func__, i, (u_long)sizeof(int));
+ 	if (muxclient_terminate) {
+ 		debug2("Exiting on signal %d", muxclient_terminate);
+-		exitval[0] = 255;
+-	} else if (i < (int)sizeof(int)) {
++		exitval = 255;
++	} else if (!exitval_seen) {
+ 		debug2("Control master terminated unexpectedly");
+-		exitval[0] = 255;
++		exitval = 255;
+ 	} else
+-		debug2("Received exit status from master %d", exitval[0]);
++		debug2("Received exit status from master %d", exitval);
+ 
+ 	if (tty_flag && options.log_level != SYSLOG_LEVEL_QUIET)
+ 		fprintf(stderr, "Shared connection to %s closed.\r\n", host);
+ 
+-	exit(exitval[0]);
++	exit(exitval);
+ }
++
++static int
++mux_client_request_stdio_fwd(int fd)
++{
++	Buffer m;
++	char *e;
++	u_int type, rid, sid;
++	int devnull;
++
++	debug3("%s: entering", __func__);
++
++	if ((muxserver_pid = mux_client_request_alive(fd)) == 0) {
++		error("%s: master alive request failed", __func__);
++		return -1;
++	}
++
++	signal(SIGPIPE, SIG_IGN);
++
++	if (stdin_null_flag) {
++		if ((devnull = open(_PATH_DEVNULL, O_RDONLY)) == -1)
++			fatal("open(/dev/null): %s", strerror(errno));
++		if (dup2(devnull, STDIN_FILENO) == -1)
++			fatal("dup2: %s", strerror(errno));
++		if (devnull > STDERR_FILENO)
++			close(devnull);
++	}
++
++	buffer_init(&m);
++	buffer_put_int(&m, MUX_C_NEW_STDIO_FWD);
++	buffer_put_int(&m, muxclient_request_id);
++	buffer_put_cstring(&m, ""); /* reserved */
++	buffer_put_cstring(&m, stdio_forward_host);
++	buffer_put_int(&m, stdio_forward_port);
++
++	if (mux_client_write_packet(fd, &m) != 0)
++		fatal("%s: write packet: %s", __func__, strerror(errno));
++
++	/* Send the stdio file descriptors */
++	if (mm_send_fd(fd, STDIN_FILENO) == -1 ||
++	    mm_send_fd(fd, STDOUT_FILENO) == -1)
++		fatal("%s: send fds failed", __func__);
++
++	debug3("%s: stdio forward request sent", __func__);
++
++	/* Read their reply */
++	buffer_clear(&m);
++
++	if (mux_client_read_packet(fd, &m) != 0) {
++		error("%s: read from master failed: %s",
++		    __func__, strerror(errno));
++		buffer_free(&m);
++		return -1;
++	}
++
++	type = buffer_get_int(&m);
++	if ((rid = buffer_get_int(&m)) != muxclient_request_id)
++		fatal("%s: out of sequence reply: my id %u theirs %u",
++		    __func__, muxclient_request_id, rid);
++	switch (type) {
++	case MUX_S_SESSION_OPENED:
++		sid = buffer_get_int(&m);
++		debug("%s: master session id: %u", __func__, sid);
++		break;
++	case MUX_S_PERMISSION_DENIED:
++		e = buffer_get_string(&m, NULL);
++		buffer_free(&m);
++		fatal("Master refused forwarding request: %s", e);
++	case MUX_S_FAILURE:
++		e = buffer_get_string(&m, NULL);
++		buffer_free(&m);
++		fatal("%s: stdio forwarding request failed: %s", __func__, e);
++	default:
++		buffer_free(&m);
++		error("%s: unexpected response from master 0x%08x",
++		    __func__, type);
++		return -1;
++	}
++	muxclient_request_id++;
++
++	signal(SIGHUP, control_client_sighandler);
++	signal(SIGINT, control_client_sighandler);
++	signal(SIGTERM, control_client_sighandler);
++	signal(SIGWINCH, control_client_sigrelay);
++
++	/*
++	 * Stick around until the controlee closes the client_fd.
++	 */
++	buffer_clear(&m);
++	if (mux_client_read_packet(fd, &m) != 0) {
++		if (errno == EPIPE ||
++		    (errno == EINTR && muxclient_terminate != 0))
++			return 0;
++		fatal("%s: mux_client_read_packet: %s",
++		    __func__, strerror(errno));
++	}
++	fatal("%s: master returned unexpected message %u", __func__, type);
++}
++
++/* Multiplex client main loop. */
++void
++muxclient(const char *path)
++{
++	struct sockaddr_un addr;
++	socklen_t sun_len;
++	int sock;
++	u_int pid;
++
++	if (muxclient_command == 0) {
++		if (stdio_forward_host != NULL)
++			muxclient_command = SSHMUX_COMMAND_STDIO_FWD;
++		else
++			muxclient_command = SSHMUX_COMMAND_OPEN;
++	}
++
++	switch (options.control_master) {
++	case SSHCTL_MASTER_AUTO:
++	case SSHCTL_MASTER_AUTO_ASK:
++		debug("auto-mux: Trying existing master");
++		/* FALLTHROUGH */
++	case SSHCTL_MASTER_NO:
++		break;
++	default:
++		return;
++	}
++
++	memset(&addr, '\0', sizeof(addr));
++	addr.sun_family = AF_UNIX;
++	sun_len = offsetof(struct sockaddr_un, sun_path) +
++	    strlen(path) + 1;
++
++	if (strlcpy(addr.sun_path, path,
++	    sizeof(addr.sun_path)) >= sizeof(addr.sun_path))
++		fatal("ControlPath too long");
++
++	if ((sock = socket(PF_UNIX, SOCK_STREAM, 0)) < 0)
++		fatal("%s socket(): %s", __func__, strerror(errno));
++
++	if (connect(sock, (struct sockaddr *)&addr, sun_len) == -1) {
++		switch (muxclient_command) {
++		case SSHMUX_COMMAND_OPEN:
++		case SSHMUX_COMMAND_STDIO_FWD:
++			break;
++		default:
++			fatal("Control socket connect(%.100s): %s", path,
++			    strerror(errno));
++		}
++		if (errno == ENOENT)
++			debug("Control socket \"%.100s\" does not exist", path);
++		else {
++			error("Control socket connect(%.100s): %s", path,
++			    strerror(errno));
++		}
++		close(sock);
++		return;
++	}
++	set_nonblock(sock);
++
++	if (mux_client_hello_exchange(sock) != 0) {
++		error("%s: master hello exchange failed", __func__);
++		close(sock);
++		return;
++	}
++
++	switch (muxclient_command) {
++	case SSHMUX_COMMAND_ALIVE_CHECK:
++		if ((pid = mux_client_request_alive(sock)) == 0)
++			fatal("%s: master alive check failed", __func__);
++		fprintf(stderr, "Master running (pid=%d)\r\n", pid);
++		exit(0);
++	case SSHMUX_COMMAND_TERMINATE:
++		mux_client_request_terminate(sock);
++		fprintf(stderr, "Exit request sent.\r\n");
++		exit(0);
++	case SSHMUX_COMMAND_OPEN:
++		if (mux_client_request_forwards(sock) != 0) {
++			error("%s: master forward request failed", __func__);
++			return;
++		}
++		mux_client_request_session(sock);
++		return;
++	case SSHMUX_COMMAND_STDIO_FWD:
++		mux_client_request_stdio_fwd(sock);
++		exit(0);
++	default:
++		fatal("unrecognised muxclient_command %d", muxclient_command);
++	}
++ }
+diff --git a/nchan.c b/nchan.c
+index 160445e..20f6a2f 100644
+--- a/nchan.c
++++ b/nchan.c
+@@ -1,4 +1,4 @@
+-/* $OpenBSD: nchan.c,v 1.62 2008/11/07 18:50:18 stevesk Exp $ */
++/* $OpenBSD: nchan.c,v 1.63 2010/01/26 01:28:35 djm Exp $ */
+ /*
+  * Copyright (c) 1999, 2000, 2001, 2002 Markus Friedl.  All rights reserved.
+  *
+@@ -161,7 +161,7 @@ chan_ibuf_empty(Channel *c)
+ 	switch (c->istate) {
+ 	case CHAN_INPUT_WAIT_DRAIN:
+ 		if (compat20) {
+-			if (!(c->flags & CHAN_CLOSE_SENT))
++			if (!(c->flags & (CHAN_CLOSE_SENT|CHAN_LOCAL)))
+ 				chan_send_eof2(c);
+ 			chan_set_istate(c, CHAN_INPUT_CLOSED);
+ 		} else {
+@@ -278,9 +278,12 @@ static void
+ chan_rcvd_close2(Channel *c)
+ {
+ 	debug2("channel %d: rcvd close", c->self);
+-	if (c->flags & CHAN_CLOSE_RCVD)
+-		error("channel %d: protocol error: close rcvd twice", c->self);
+-	c->flags |= CHAN_CLOSE_RCVD;
++	if (!(c->flags & CHAN_LOCAL)) {
++		if (c->flags & CHAN_CLOSE_RCVD)
++			error("channel %d: protocol error: close rcvd twice",
++			    c->self);
++		c->flags |= CHAN_CLOSE_RCVD;
++	}
+ 	if (c->type == SSH_CHANNEL_LARVAL) {
+ 		/* tear down larval channels immediately */
+ 		chan_set_ostate(c, CHAN_OUTPUT_CLOSED);
+@@ -302,11 +305,13 @@ chan_rcvd_close2(Channel *c)
+ 		chan_set_istate(c, CHAN_INPUT_CLOSED);
+ 		break;
+ 	case CHAN_INPUT_WAIT_DRAIN:
+-		chan_send_eof2(c);
++		if (!(c->flags & CHAN_LOCAL))
++			chan_send_eof2(c);
+ 		chan_set_istate(c, CHAN_INPUT_CLOSED);
+ 		break;
+ 	}
+ }
++
+ void
+ chan_rcvd_eow(Channel *c)
+ {
+@@ -454,6 +459,10 @@ chan_is_dead(Channel *c, int do_send)
+ 		    c->self, c->efd, buffer_len(&c->extended));
+ 		return 0;
+ 	}
++	if (c->flags & CHAN_LOCAL) {
++		debug2("channel %d: is dead (local)", c->self);
++		return 1;
++	}		
+ 	if (!(c->flags & CHAN_CLOSE_SENT)) {
+ 		if (do_send) {
+ 			chan_send_close2(c);
+diff --git a/readconf.c b/readconf.c
+index c63bd1a..a43e593 100644
+--- a/readconf.c
++++ b/readconf.c
+@@ -130,7 +130,8 @@ typedef enum {
+ 	oAddressFamily, oGssAuthentication, oGssDelegateCreds,
+ 	oGssTrustDns, oGssKeyEx, oGssClientIdentity, oGssRenewalRekey,
+ 	oServerAliveInterval, oServerAliveCountMax, oIdentitiesOnly,
+-	oSendEnv, oControlPath, oControlMaster, oHashKnownHosts,
++	oSendEnv, oControlPath, oControlMaster, oControlPersist,
++	oHashKnownHosts,
+ 	oTunnel, oTunnelDevice, oLocalCommand, oPermitLocalCommand,
+ 	oVisualHostKey, oUseRoaming, oZeroKnowledgePasswordAuthentication,
+ 	oKexAlgorithms,
+@@ -244,6 +245,7 @@ static struct {
+ 	{ "sendenv", oSendEnv },
+ 	{ "controlpath", oControlPath },
+ 	{ "controlmaster", oControlMaster },
++	{ "controlpersist", oControlPersist },
+ 	{ "hashknownhosts", oHashKnownHosts },
+ 	{ "tunnel", oTunnel },
+ 	{ "tunneldevice", oTunnelDevice },
+@@ -937,6 +939,30 @@ parse_int:
+ 			*intptr = value;
+ 		break;
+ 
++	case oControlPersist:
++		/* no/false/yes/true, or a time spec */
++		intptr = &options->control_persist;
++		arg = strdelim(&s);
++		if (!arg || *arg == '\0')
++			fatal("%.200s line %d: Missing ControlPersist"
++			    " argument.", filename, linenum);
++		value = 0;
++		value2 = 0;	/* timeout */
++		if (strcmp(arg, "no") == 0 || strcmp(arg, "false") == 0)
++			value = 0;
++		else if (strcmp(arg, "yes") == 0 || strcmp(arg, "true") == 0)
++			value = 1;
++		else if ((value2 = convtime(arg)) >= 0)
++			value = 1;
++		else
++			fatal("%.200s line %d: Bad ControlPersist argument.",
++			    filename, linenum);
++		if (*activep && *intptr == -1) {
++			*intptr = value;
++			options->control_persist_timeout = value2;
++		}
++		break;
++
+ 	case oHashKnownHosts:
+ 		intptr = &options->hash_known_hosts;
+ 		goto parse_flag;
+@@ -1143,6 +1169,8 @@ initialize_options(Options * options)
+ 	options->num_send_env = 0;
+ 	options->control_path = NULL;
+ 	options->control_master = -1;
++	options->control_persist = -1;
++	options->control_persist_timeout = 0;
+ 	options->hash_known_hosts = -1;
+ 	options->tun_open = -1;
+ 	options->tun_local = -1;
+@@ -1292,6 +1320,10 @@ fill_default_options(Options * options)
+ 		options->server_alive_count_max = 3;
+ 	if (options->control_master == -1)
+ 		options->control_master = 0;
++	if (options->control_persist == -1) {
++		options->control_persist = 0;
++		options->control_persist_timeout = 0;
++	}
+ 	if (options->hash_known_hosts == -1)
+ 		options->hash_known_hosts = 0;
+ 	if (options->tun_open == -1)
+diff --git a/readconf.h b/readconf.h
+index 83a1a98..d58da4d 100644
+--- a/readconf.h
++++ b/readconf.h
+@@ -121,6 +121,8 @@ typedef struct {
+ 
+ 	char	*control_path;
+ 	int	control_master;
++	int     control_persist; /* ControlPersist flag */
++	int     control_persist_timeout; /* ControlPersist timeout (seconds) */
+ 
+ 	int	hash_known_hosts;
+ 
+diff --git a/ssh.c b/ssh.c
+index 26a0d75..0ee8efb 100644
+--- a/ssh.c
++++ b/ssh.c
+@@ -132,6 +132,15 @@ int no_shell_flag = 0;
+ int stdin_null_flag = 0;
+ 
+ /*
++ * Flag indicating that the current process should be backgrounded and
++ * a new slave launched in the foreground for ControlPersist.
++ */
++int need_controlpersist_detach = 0;
++
++/* Copies of flags for ControlPersist foreground slave */
++int ostdin_null_flag, ono_shell_flag, ono_tty_flag, otty_flag;
++
++/*
+  * Flag indicating that ssh should fork after authentication.  This is useful
+  * so that the passphrase can be entered manually, and then ssh goes to the
+  * background.
+@@ -242,6 +251,12 @@ main(int ac, char **av)
+ 	init_rng();
+ 
+ 	/*
++	 * Discard other fds that are hanging around. These can cause problem
++	 * with backgrounded ssh processes started by ControlPersist.
++	 */
++	closefrom(STDERR_FILENO + 1);
++
++	/*
+ 	 * Save the original real uid.  It will be needed later (uid-swapping
+ 	 * may clobber the real uid).
+ 	 */
+@@ -337,6 +352,11 @@ main(int ac, char **av)
+ 			options.gateway_ports = 1;
+ 			break;
+ 		case 'O':
++			if (stdio_forward_host != NULL)
++				fatal("Cannot specify multiplexing "
++				    "command with -W");
++			else if (muxclient_command != 0)
++				fatal("Multiplexing command already specified");
+ 			if (strcmp(optarg, "check") == 0)
+ 				muxclient_command = SSHMUX_COMMAND_ALIVE_CHECK;
+ 			else if (strcmp(optarg, "exit") == 0)
+@@ -413,6 +433,10 @@ main(int ac, char **av)
+ 			}
+ 			break;
+ 		case 'W':
++			if (stdio_forward_host != NULL)
++				fatal("stdio forward already specified");
++			if (muxclient_command != 0)
++				fatal("Cannot specify stdio forward with -O");
+ 			if (parse_forward(&fwd, optarg, 1, 0)) {
+ 				stdio_forward_host = fwd.listen_host;
+ 				stdio_forward_port = fwd.listen_port;
+@@ -902,6 +926,62 @@ main(int ac, char **av)
+ 	return exit_status;
+ }
+ 
++static void
++control_persist_detach(void)
++{
++	pid_t pid;
++	int devnull;
++
++	debug("%s: backgrounding master process", __func__);
++
++ 	/*
++ 	 * master (current process) into the background, and make the
++ 	 * foreground process a client of the backgrounded master.
++ 	 */
++	switch ((pid = fork())) {
++	case -1:
++		fatal("%s: fork: %s", __func__, strerror(errno));
++	case 0:
++		/* Child: master process continues mainloop */
++ 		break;
++ 	default:
++		/* Parent: set up mux slave to connect to backgrounded master */
++		debug2("%s: background process is %ld", __func__, (long)pid);
++		stdin_null_flag = ostdin_null_flag;
++		no_shell_flag = ono_shell_flag;
++		no_tty_flag = ono_tty_flag;
++		tty_flag = otty_flag;
++ 		close(muxserver_sock);
++ 		muxserver_sock = -1;
++		options.control_master = SSHCTL_MASTER_NO;
++ 		muxclient(options.control_path);
++		/* muxclient() doesn't return on success. */
++ 		fatal("Failed to connect to new control master");
++ 	}
++	if ((devnull = open(_PATH_DEVNULL, O_RDWR)) == -1) {
++		error("%s: open(\"/dev/null\"): %s", __func__,
++		    strerror(errno));
++	} else {
++		if (dup2(devnull, STDIN_FILENO) == -1 ||
++		    dup2(devnull, STDOUT_FILENO) == -1)
++			error("%s: dup2: %s", __func__, strerror(errno));
++		if (devnull > STDERR_FILENO)
++			close(devnull);
++	}
++}
++
++/* Do fork() after authentication. Used by "ssh -f" */
++static void
++fork_postauth(void)
++{
++	if (need_controlpersist_detach)
++		control_persist_detach();
++	debug("forking to background");
++	fork_after_authentication_flag = 0;
++	if (daemon(1, 1) < 0)
++		fatal("daemon() failed: %.200s", strerror(errno));
++}
++
+ /* Callback for remote forward global requests */
+ static void
+ ssh_confirm_remote_forward(int type, u_int32_t seq, void *ctxt)
+@@ -928,12 +1008,8 @@ ssh_confirm_remote_forward(int type, u_int32_t seq, void *ctxt)
+ 	}
+ 	if (++remote_forward_confirms_received == options.num_remote_forwards) {
+ 		debug("All remote forwarding requests processed");
+-		if (fork_after_authentication_flag) {
+-			fork_after_authentication_flag = 0;
+-			if (daemon(1, 1) < 0)
+-				fatal("daemon() failed: %.200s",
+-				    strerror(errno));
+-		}
++		if (fork_after_authentication_flag)
++			fork_postauth();
+ 	}
+ }
+ 
+@@ -944,18 +1020,26 @@ client_cleanup_stdio_fwd(int id, void *arg)
+ 	cleanup_exit(0);
+ }
+ 
+-static int
+-client_setup_stdio_fwd(const char *host_to_connect, u_short port_to_connect)
++static void
++ssh_init_stdio_forwarding(void)
+ {
+ 	Channel *c;
++	int in, out;
++
++	if (stdio_forward_host == NULL)
++		return;
++	if (!compat20) 
++		fatal("stdio forwarding require Protocol 2");
+ 
+-	debug3("client_setup_stdio_fwd %s:%d", host_to_connect,
+-	    port_to_connect);
+-	if ((c = channel_connect_stdio_fwd(host_to_connect, port_to_connect))
+-	    == NULL)
+-		return 0;
++	debug3("%s: %s:%d", __func__, stdio_forward_host, stdio_forward_port);
++
++	if ((in = dup(STDIN_FILENO)) < 0 ||
++	    (out = dup(STDOUT_FILENO)) < 0)
++		fatal("channel_connect_stdio_fwd: dup() in/out failed");
++	if ((c = channel_connect_stdio_fwd(stdio_forward_host,
++	    stdio_forward_port, in, out)) == NULL)
++		fatal("%s: channel_connect_stdio_fwd failed", __func__);
+ 	channel_register_cleanup(c->self, client_cleanup_stdio_fwd, 0);
+-	return 1;
+ }
+ 
+ static void
+@@ -964,15 +1048,6 @@ ssh_init_forwarding(void)
+ 	int success = 0;
+ 	int i;
+ 
+-	if (stdio_forward_host != NULL) {
+-		if (!compat20) {
+-			fatal("stdio forwarding require Protocol 2");
+-		}
+-		if (!client_setup_stdio_fwd(stdio_forward_host,
+-		    stdio_forward_port))
+-			fatal("Failed to connect in stdio forward mode.");
+-	}
+-
+ 	/* Initiate local TCP/IP port forwardings. */
+ 	for (i = 0; i < options.num_local_forwards; i++) {
+ 		debug("Local connections to %.200s:%d forwarded to remote "
+@@ -1157,6 +1232,7 @@ ssh_session(void)
+ 	}
+ 
+ 	/* Initiate port forwardings. */
++	ssh_init_stdio_forwarding();
+ 	ssh_init_forwarding();
+ 
+ 	/* Execute a local command */
+@@ -1168,12 +1244,13 @@ ssh_session(void)
+ 	 * If requested and we are not interested in replies to remote
+ 	 * forwarding requests, then let ssh continue in the background.
+ 	 */
+-	if (fork_after_authentication_flag &&
+-	    (!options.exit_on_forward_failure ||
+-	    options.num_remote_forwards == 0)) {
+-		fork_after_authentication_flag = 0;
+-		if (daemon(1, 1) < 0)
+-			fatal("daemon() failed: %.200s", strerror(errno));
++	if (fork_after_authentication_flag) {
++		if (options.exit_on_forward_failure &&
++		    options.num_remote_forwards > 0) {
++			debug("deferring postauth fork until remote forward "
++			    "confirmation received");
++		} else
++			fork_postauth();
+ 	}
+ 
+ 	/*
+@@ -1290,8 +1367,42 @@ ssh_session2(void)
+ 	int id = -1;
+ 
+ 	/* XXX should be pre-session */
++	if (!options.control_persist)
++		ssh_init_stdio_forwarding();
+ 	ssh_init_forwarding();
+ 
++	/* Start listening for multiplex clients */
++	muxserver_listen();
++
++ 	/*
++	 * If we are in control persist mode and have a working mux listen
++	 * socket, then prepare to background ourselves and have a foreground
++	 * client attach as a control slave.
++	 * NB. we must save copies of the flags that we override for
++	 * the backgrounding, since we defer attachment of the slave until
++	 * after the connection is fully established (in particular,
++	 * async rfwd replies have been received for ExitOnForwardFailure).
++	 */
++ 	if (options.control_persist && muxserver_sock != -1) {
++		ostdin_null_flag = stdin_null_flag;
++		ono_shell_flag = no_shell_flag;
++		ono_tty_flag = no_tty_flag;
++		otty_flag = tty_flag;
++ 		stdin_null_flag = 1;
++ 		no_shell_flag = 1;
++ 		no_tty_flag = 1;
++ 		tty_flag = 0;
++		if (!fork_after_authentication_flag)
++			need_controlpersist_detach = 1;
++		fork_after_authentication_flag = 1;
++ 	}
++	/*
++	 * ControlPersist mux listen socket setup failed, attempt the
++	 * stdio forward setup that we skipped earlier.
++	 */
++	if (options.control_persist && muxserver_sock == -1)
++		ssh_init_stdio_forwarding();
++
+ 	if (!no_shell_flag || (datafellows & SSH_BUG_DUMMYCHAN))
+ 		id = ssh_session2_open();
+ 
+diff --git a/ssh_config.5 b/ssh_config.5
+index 0dd1178..b9d95c8 100644
+--- a/ssh_config.5
++++ b/ssh_config.5
+@@ -319,6 +319,28 @@ It is recommended that any
+ used for opportunistic connection sharing include
+ at least %h, %p, and %r.
+ This ensures that shared connections are uniquely identified.
++.It Cm ControlPersist
++When used in conjunction with
++.Cm ControlMaster ,
++specifies that the master connection should remain open
++in the background (waiting for future client connections)
++after the initial client connection has been closed.
++If set to
++.Dq no ,
++then the master connection will not be placed into the background,
++and will close as soon as the initial client connection is closed.
++If set to
++.Dq yes ,
++then the master connection will remain in the background indefinitely
++(until killed or closed via a mechanism such as the
++.Xr ssh 1
++.Dq Fl O No exit
++option).
++If set to a time in seconds, or a time in any of the formats documented in
++.Xr sshd_config 5 ,
++then the backgrounded master connection will automatically terminate
++after it has remained idle (with no client connections) for the
++specified time.
+ .It Cm DynamicForward
+ Specifies that a TCP port on the local machine be forwarded
+ over the secure channel, and the application
diff --git a/openssh-5.3p1-FIPS-mode-SP800-131A.patch b/openssh-5.3p1-FIPS-mode-SP800-131A.patch
new file mode 100644
index 0000000..e31a603
--- /dev/null
+++ b/openssh-5.3p1-FIPS-mode-SP800-131A.patch
@@ -0,0 +1,236 @@
+From 86d6076046bf66fca7fd08471a641f6e5e75fd9e Mon Sep 17 00:00:00 2001
+From: Petr Lautrbach <plautrba at redhat.com>
+Date: Fri, 7 Mar 2014 18:15:20 +0100
+Subject: [PATCH] openssh-5.3p1-FIPS-mode-SP800-131A.patch
+
+---
+ dh.c          |  3 ++-
+ dh.h          |  1 +
+ kex.c         | 10 ++++++++++
+ kexgexc.c     |  6 ++++--
+ kexgexs.c     |  6 +++---
+ key.c         |  8 ++++++--
+ myproposal.h  |  4 ++++
+ ssh-keygen.c  | 11 ++++++-----
+ sshconnect2.c |  2 ++
+ sshd.c        |  2 ++
+ 10 files changed, 40 insertions(+), 13 deletions(-)
+
+diff --git a/dh.c b/dh.c
+index b766053..916af72 100644
+--- a/dh.c
++++ b/dh.c
+@@ -29,6 +29,7 @@
+ 
+ #include <openssl/bn.h>
+ #include <openssl/dh.h>
++#include <openssl/fips.h>
+ 
+ #include <stdarg.h>
+ #include <stdio.h>
+@@ -339,7 +340,7 @@ dh_estimate(int bits)
+ {
+ 
+ 	if (bits <= 128)
+-		return (1024);	/* O(2**86) */
++		return (FIPS_mode() ? 2048 : 1024);	/* O(2**86) */
+ 	if (bits <= 192)
+ 		return (2048);	/* O(2**116) */
+ 	return (4096);		/* O(2**156) */
+diff --git a/dh.h b/dh.h
+index dfc1480..a945320 100644
+--- a/dh.h
++++ b/dh.h
+@@ -44,6 +44,7 @@ int	 dh_pub_is_valid(DH *, BIGNUM *);
+ int	 dh_estimate(int);
+ 
+ #define DH_GRP_MIN	1024
++#define DH_GRP_MIN_FIPS	2048
+ #define DH_GRP_MAX	8192
+ 
+ /*
+diff --git a/kex.c b/kex.c
+index 01f7231..f09199f 100644
+--- a/kex.c
++++ b/kex.c
+@@ -34,6 +34,7 @@
+ #include <string.h>
+ 
+ #include <openssl/crypto.h>
++#include <openssl/fips.h>
+ 
+ #include "xmalloc.h"
+ #include "ssh2.h"
+@@ -85,6 +86,15 @@ kex_names_valid(const char *names)
+ 			xfree(s);
+ 			return 0;
+ 		}
++		if (FIPS_mode()) {
++			if (strcmp(p, KEX_DHGEX_SHA256) != 0 &&
++			    strcmp(p, KEX_DHGEX_SHA1) != 0 &&
++			    strcmp(p, KEX_DH14) != 0 ) {
++				error("\"%.100s\" is not allowed in FIPS mode", p);
++				xfree(s);
++				return 0;
++			}
++		}
+ 	}
+ 	debug3("kex names ok: [%s]", names);
+ 	xfree(s);
+diff --git a/kexgexc.c b/kexgexc.c
+index adb973d..7b49c81 100644
+--- a/kexgexc.c
++++ b/kexgexc.c
+@@ -26,6 +26,8 @@
+ 
+ #include "includes.h"
+ 
++#include <openssl/fips.h>
++
+ #include <sys/types.h>
+ 
+ #include <stdarg.h>
+@@ -62,13 +64,13 @@ kexgex_client(Kex *kex)
+ 		/* Old GEX request */
+ 		packet_start(SSH2_MSG_KEX_DH_GEX_REQUEST_OLD);
+ 		packet_put_int(nbits);
+-		min = DH_GRP_MIN;
++		min = FIPS_mode() ? DH_GRP_MIN_FIPS : DH_GRP_MIN;
+ 		max = DH_GRP_MAX;
+ 
+ 		debug("SSH2_MSG_KEX_DH_GEX_REQUEST_OLD(%u) sent", nbits);
+ 	} else {
+ 		/* New GEX request */
+-		min = DH_GRP_MIN;
++		min = FIPS_mode() ? DH_GRP_MIN_FIPS : DH_GRP_MIN;
+ 		max = DH_GRP_MAX;
+ 		packet_start(SSH2_MSG_KEX_DH_GEX_REQUEST);
+ 		packet_put_int(min);
+diff --git a/kexgexs.c b/kexgexs.c
+index f4156af..06977ff 100644
+--- a/kexgexs.c
++++ b/kexgexs.c
+@@ -78,16 +78,16 @@ kexgex_server(Kex *kex)
+ 		omin = min = packet_get_int();
+ 		onbits = nbits = packet_get_int();
+ 		omax = max = packet_get_int();
+-		min = MAX(DH_GRP_MIN, min);
++		min = MAX(FIPS_mode() ? DH_GRP_MIN_FIPS : DH_GRP_MIN, min);
+ 		max = MIN(DH_GRP_MAX, max);
+-		nbits = MAX(DH_GRP_MIN, nbits);
++		nbits = MAX(FIPS_mode() ? DH_GRP_MIN_FIPS : DH_GRP_MIN, nbits);
+ 		nbits = MIN(DH_GRP_MAX, nbits);
+ 		break;
+ 	case SSH2_MSG_KEX_DH_GEX_REQUEST_OLD:
+ 		debug("SSH2_MSG_KEX_DH_GEX_REQUEST_OLD received");
+ 		onbits = nbits = packet_get_int();
+ 		/* unused for old GEX */
+-		omin = min = DH_GRP_MIN;
++		omin = min = FIPS_mode() ? DH_GRP_MIN_FIPS : DH_GRP_MIN;
+ 		omax = max = DH_GRP_MAX;
+ 		break;
+ 	default:
+diff --git a/key.c b/key.c
+index 574ad81..ad15ce9 100644
+--- a/key.c
++++ b/key.c
+@@ -40,6 +40,7 @@
+ #include <sys/types.h>
+ 
+ #include <openssl/evp.h>
++#include <openssl/fips.h>
+ #include <openbsd-compat/openssl-compat.h>
+ 
+ #include <stdarg.h>
+@@ -931,9 +932,12 @@ rsa_generate_private_key(u_int bits)
+ {
+ 	RSA *private;
+ 
+-	private = RSA_generate_key(bits, 35, NULL, NULL);
+-	if (private == NULL)
++	private = RSA_generate_key(bits, (FIPS_mode() ? 65537 : 35), NULL, NULL);
++	if (private == NULL) {
++		if (FIPS_mode())
++			fprintf(stderr, "the key length might be unsupported by FIPS mode approved key generation method\n");
+ 		fatal("rsa_generate_private_key: key generation failed.");
++	}
+ 	return private;
+ }
+ 
+diff --git a/myproposal.h b/myproposal.h
+index f973345..b7fb4a1 100644
+--- a/myproposal.h
++++ b/myproposal.h
+@@ -39,6 +39,10 @@
+ 	"diffie-hellman-group14-sha1," \
+ 	"diffie-hellman-group1-sha1"
+ #endif
++#define KEX_DEFAULT_KEX_FIPS		\
++	"diffie-hellman-group-exchange-sha256," \
++	"diffie-hellman-group-exchange-sha1," \
++	"diffie-hellman-group14-sha1"
+ 
+ #define	KEX_DEFAULT_PK_ALG	\
+ 				"ssh-rsa-cert-v01 at openssh.com," \
+diff --git a/ssh-keygen.c b/ssh-keygen.c
+index 51915ad..c9eefb3 100644
+--- a/ssh-keygen.c
++++ b/ssh-keygen.c
+@@ -1907,6 +1907,8 @@ main(int argc, char **argv)
+ 		fprintf(stderr, "unknown key type %s\n", key_type_name);
+ 		exit(1);
+ 	}
++	if (type == KEY_DSA && FIPS_mode())
++		fatal("DSA keys are not allowed in FIPS mode");
+ 	if (bits == 0)
+ 		bits = (type == KEY_DSA) ? DEFAULT_BITS_DSA : DEFAULT_BITS;
+ 	if (type == KEY_DSA && bits != 1024)
+@@ -2014,15 +2016,14 @@ passphrase_again:
+ 	fclose(f);
+ 
+ 	if (!quiet) {
+-		int fips_on = FIPS_mode();
+-		char *fp = key_fingerprint(public, fips_on ? SSH_FP_SHA1 : SSH_FP_MD5, SSH_FP_HEX);
+-		char *ra = key_fingerprint(public, fips_on ? SSH_FP_SHA1 : SSH_FP_MD5,
++		char *fp = key_fingerprint(public, FIPS_mode() ? SSH_FP_SHA1 : SSH_FP_MD5, SSH_FP_HEX);
++		char *ra = key_fingerprint(public, FIPS_mode() ? SSH_FP_SHA1 : SSH_FP_MD5,
+ 		    SSH_FP_RANDOMART);
+ 		printf("Your public key has been saved in %s.\n",
+ 		    identity_file);
+-		printf("The key %sfingerprint is:\n", fips_on ? "SHA1 " : "");
++		printf("The key %sfingerprint is:\n", FIPS_mode() ? "SHA1 " : "");
+ 		printf("%s %s\n", fp, comment);
+-		printf("The key's %srandomart image is:\n", fips_on ? "SHA1 " :"");
++		printf("The key's %srandomart image is:\n", FIPS_mode() ? "SHA1 " :"");
+ 		printf("%s\n", ra);
+ 		xfree(ra);
+ 		xfree(fp);
+diff --git a/sshconnect2.c b/sshconnect2.c
+index a9d12a6..b311d01 100644
+--- a/sshconnect2.c
++++ b/sshconnect2.c
+@@ -175,6 +175,8 @@ ssh_kex2(char *host, struct sockaddr *hostaddr)
+ 
+ 	if (options.kex_algorithms != NULL)
+ 		myproposal[PROPOSAL_KEX_ALGS] = options.kex_algorithms;
++	else if (FIPS_mode())
++		myproposal[PROPOSAL_KEX_ALGS] = KEX_DEFAULT_KEX_FIPS;
+ 
+ #ifdef GSSAPI
+ 	/* If we've got GSSAPI algorithms, then we also support the
+diff --git a/sshd.c b/sshd.c
+index 4a257ba..106b494 100644
+--- a/sshd.c
++++ b/sshd.c
+@@ -2465,6 +2465,8 @@ do_ssh2_kex(void)
+ 	}
+ 	if (options.kex_algorithms != NULL)
+ 		myproposal[PROPOSAL_KEX_ALGS] = options.kex_algorithms;
++	else if (FIPS_mode())
++		myproposal[PROPOSAL_KEX_ALGS] = KEX_DEFAULT_KEX_FIPS;
+ 
+ 	myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = list_hostkey_types();
+ 
+-- 
+1.8.3.1
+
diff --git a/openssh-5.3p1-audit.patch b/openssh-5.3p1-audit.patch
index 6c9c981..7603e46 100644
--- a/openssh-5.3p1-audit.patch
+++ b/openssh-5.3p1-audit.patch
@@ -2109,7 +2109,7 @@ diff -up openssh-5.3p1/session.c.audit openssh-5.3p1/session.c
  #endif
  	if (s->ttyfd != -1)
  		ret = do_exec_pty(s, command);
-@@ -1675,7 +1687,10 @@ do_child(Session *s, const char *command
+@@ -1680,7 +1692,10 @@ do_child(Session *s, const char *command
  	int r = 0;
  
  	/* remove hostkey from the child's memory */
@@ -2121,7 +2121,7 @@ diff -up openssh-5.3p1/session.c.audit openssh-5.3p1/session.c
  
  	/* Force a password change */
  	if (s->authctxt->force_pwchange) {
-@@ -1895,6 +1910,7 @@ session_unused(int id)
+@@ -1896,6 +1911,7 @@ session_unused(int id)
  	sessions[id].ttyfd = -1;
  	sessions[id].ptymaster = -1;
  	sessions[id].x11_chanids = NULL;
@@ -2129,7 +2129,7 @@ diff -up openssh-5.3p1/session.c.audit openssh-5.3p1/session.c
  	sessions[id].next_unused = sessions_first_unused;
  	sessions_first_unused = id;
  }
-@@ -1977,6 +1993,19 @@ session_open(Authctxt *authctxt, int cha
+@@ -1978,6 +1994,19 @@ session_open(Authctxt *authctxt, int cha
  }
  
  Session *
@@ -2149,7 +2149,7 @@ diff -up openssh-5.3p1/session.c.audit openssh-5.3p1/session.c
  session_by_tty(char *tty)
  {
  	int i;
-@@ -2500,6 +2529,30 @@ session_exit_message(Session *s, int sta
+@@ -2501,6 +2530,30 @@ session_exit_message(Session *s, int sta
  		chan_write_failed(c);
  }
  
@@ -2180,7 +2180,7 @@ diff -up openssh-5.3p1/session.c.audit openssh-5.3p1/session.c
  void
  session_close(Session *s)
  {
-@@ -2508,6 +2561,10 @@ session_close(Session *s)
+@@ -2509,6 +2562,10 @@ session_close(Session *s)
  	debug("session_close: session %d pid %ld", s->self, (long)s->pid);
  	if (s->ttyfd != -1)
  		session_pty_cleanup(s);
@@ -2191,7 +2191,7 @@ diff -up openssh-5.3p1/session.c.audit openssh-5.3p1/session.c
  	if (s->term)
  		xfree(s->term);
  	if (s->display)
-@@ -2727,6 +2784,15 @@ do_authenticated2(Authctxt *authctxt)
+@@ -2728,6 +2785,15 @@ do_authenticated2(Authctxt *authctxt)
  	server_loop2(authctxt);
  }
  
@@ -2207,7 +2207,7 @@ diff -up openssh-5.3p1/session.c.audit openssh-5.3p1/session.c
  void
  do_cleanup(Authctxt *authctxt)
  {
-@@ -2775,5 +2841,5 @@ do_cleanup(Authctxt *authctxt)
+@@ -2776,5 +2842,5 @@ do_cleanup(Authctxt *authctxt)
  	 * or if running in monitor.
  	 */
  	if (!use_privsep || mm_is_monitor())
@@ -2242,8 +2242,8 @@ diff -up openssh-5.3p1/session.h.audit openssh-5.3p1/session.h
  void	 session_close(Session *);
  void	 do_setusercontext(struct passwd *);
 diff -up openssh-5.3p1/sshd.c.audit openssh-5.3p1/sshd.c
---- openssh-5.3p1/sshd.c.audit	2011-04-04 20:10:06.631648733 +0200
-+++ openssh-5.3p1/sshd.c	2011-04-04 20:10:09.464661420 +0200
+--- openssh-5.3p1/sshd.c.audit	2013-11-12 15:58:33.887318203 +0100
++++ openssh-5.3p1/sshd.c	2013-11-12 16:27:07.664020662 +0100
 @@ -120,6 +120,7 @@
  #endif
  #include "monitor_wrap.h"
@@ -2363,6 +2363,15 @@ diff -up openssh-5.3p1/sshd.c.audit openssh-5.3p1/sshd.c
  		}
  	}
  
+@@ -633,7 +683,7 @@ privsep_preauth(Authctxt *authctxt)
+ 	/* Store a pointer to the kex for later rekeying */
+ 	pmonitor->m_pkex = &xxx_kex;
+ 
+-	pid = fork();
++	pmonitor->m_pid = pid = fork();
+ 	if (pid == -1) {
+ 		fatal("fork of unprivileged child failed");
+ 	} else if (pid != 0) {
 @@ -665,6 +715,8 @@ privsep_preauth(Authctxt *authctxt)
  	return (0);
  }
@@ -2391,7 +2400,7 @@ diff -up openssh-5.3p1/sshd.c.audit openssh-5.3p1/sshd.c
  			close_listen_socks();
  			unlink(options.pid_file);
  			exit(255);
-@@ -1952,6 +2009,7 @@ main(int ac, char **av)
+@@ -1955,6 +2012,7 @@ main(int ac, char **av)
  	 */
  	if (use_privsep) {
  		mm_send_keystate(pmonitor);
@@ -2399,7 +2408,7 @@ diff -up openssh-5.3p1/sshd.c.audit openssh-5.3p1/sshd.c
  		exit(0);
  	}
  
-@@ -1996,8 +2054,9 @@ main(int ac, char **av)
+@@ -1999,8 +2057,9 @@ main(int ac, char **av)
  	if (use_privsep) {
  		privsep_postauth(authctxt);
  		/* the monitor process [priv] will not return */
@@ -2411,7 +2420,7 @@ diff -up openssh-5.3p1/sshd.c.audit openssh-5.3p1/sshd.c
  	}
  
  	packet_set_timeout(options.client_alive_interval,
-@@ -2007,6 +2066,9 @@ main(int ac, char **av)
+@@ -2010,6 +2069,9 @@ main(int ac, char **av)
  	do_authenticated(authctxt);
  
  	/* The connection has been terminated. */
@@ -2421,7 +2430,7 @@ diff -up openssh-5.3p1/sshd.c.audit openssh-5.3p1/sshd.c
  	packet_get_state(MODE_IN, NULL, NULL, NULL, &ibytes);
  	packet_get_state(MODE_OUT, NULL, NULL, NULL, &obytes);
  	verbose("Transferred: sent %llu, received %llu bytes", obytes, ibytes);
-@@ -2163,6 +2225,10 @@ do_ssh1_kex(void)
+@@ -2166,6 +2228,10 @@ do_ssh1_kex(void)
  		if (cookie[i] != packet_get_char())
  			packet_disconnect("IP Spoofing check bytes do not match.");
  
@@ -2432,7 +2441,7 @@ diff -up openssh-5.3p1/sshd.c.audit openssh-5.3p1/sshd.c
  	debug("Encryption type: %.200s", cipher_name(cipher_type));
  
  	/* Get the encrypted integer. */
-@@ -2229,7 +2295,7 @@ do_ssh1_kex(void)
+@@ -2232,7 +2298,7 @@ do_ssh1_kex(void)
  			session_id[i] = session_key[i] ^ session_key[i + 16];
  	}
  	/* Destroy the private and public keys. No longer. */
@@ -2441,7 +2450,7 @@ diff -up openssh-5.3p1/sshd.c.audit openssh-5.3p1/sshd.c
  
  	if (use_privsep)
  		mm_ssh1_session_id(session_id);
-@@ -2370,11 +2436,27 @@ do_ssh2_kex(void)
+@@ -2373,11 +2439,27 @@ do_ssh2_kex(void)
  void
  cleanup_exit(int i)
  {
@@ -2458,7 +2467,7 @@ diff -up openssh-5.3p1/sshd.c.audit openssh-5.3p1/sshd.c
 +
  	if (the_authctxt)
  		do_cleanup(the_authctxt);
-+	is_privsep_child = use_privsep && pmonitor != NULL && !mm_is_monitor();
++	is_privsep_child = use_privsep && pmonitor != NULL && pmonitor->m_pid == 0;
 +	if (sensitive_data.host_keys != NULL)
 +		destroy_sensitive_data(is_privsep_child);
 +	packet_destroy_all(1, is_privsep_child);
diff --git a/openssh-5.3p1-ecdsa-ecdh.patch b/openssh-5.3p1-ecdsa-ecdh.patch
new file mode 100644
index 0000000..1f95f37
--- /dev/null
+++ b/openssh-5.3p1-ecdsa-ecdh.patch
@@ -0,0 +1,4105 @@
+From 69360ff02b4fc792f75c85c6bea9439e87a3d01a Mon Sep 17 00:00:00 2001
+From: Petr Lautrbach <plautrba at redhat.com>
+Date: Mon, 10 Mar 2014 16:02:53 +0100
+Subject: [PATCH] openssh-5.3p1-ecdsa-ecdh.patch
+
+---
+ ChangeLog               |  78 ++++++
+ Makefile.in             |   9 +-
+ PROTOCOL                |  43 +++-
+ PROTOCOL.agent          |  44 +++-
+ PROTOCOL.certkeys       |  87 ++++---
+ auth2-jpake.c           |   7 +-
+ authfd.c                |  20 +-
+ authfile.c              |  33 +++
+ bufec.c                 | 146 +++++++++++
+ buffer.h                |   9 +
+ configure.ac            | 116 +++++++++
+ dns.c                   |   3 +-
+ kex.c                   |  18 +-
+ kex.h                   |  23 +-
+ kexecdh.c               | 117 +++++++++
+ kexecdhc.c              | 169 +++++++++++++
+ kexecdhs.c              | 174 +++++++++++++
+ key.c                   | 644 +++++++++++++++++++++++++++++++++++++++++++++++-
+ key.h                   |  34 ++-
+ monitor.c               |   7 +-
+ monitor_wrap.c          |   1 +
+ myproposal.h            |   2 +-
+ packet.c                |  16 ++
+ packet.h                |   9 +
+ pathnames.h             |   2 +
+ readconf.c              |   7 +
+ regress/cert-hostkey.sh |  57 ++++-
+ regress/cert-userkey.sh |  17 +-
+ ssh-add.1               |   9 +-
+ ssh-add.c               |   1 +
+ ssh-agent.1             |   7 +-
+ ssh-agent.c             |  61 ++++-
+ ssh-ecdsa.c             | 168 +++++++++++++
+ ssh-keygen.1            |  39 ++-
+ ssh-keygen.c            |  53 +++-
+ ssh-keyscan.1           |  16 +-
+ ssh-keyscan.c           |  11 +-
+ ssh-keysign.8           |   2 +-
+ ssh.1                   |  26 +-
+ ssh.c                   |  17 +-
+ ssh2.h                  |   4 +
+ ssh_config.5            |  18 +-
+ sshconnect.c            |   2 +-
+ sshconnect2.c           |   1 +
+ sshd.8                  |  15 +-
+ sshd.c                  |   5 +
+ sshd_config.5           |   8 +-
+ uuencode.c              |   4 +-
+ uuencode.h              |   4 +-
+ 49 files changed, 2193 insertions(+), 170 deletions(-)
+ create mode 100644 bufec.c
+ create mode 100644 kexecdh.c
+ create mode 100644 kexecdhc.c
+ create mode 100644 kexecdhs.c
+ create mode 100644 ssh-ecdsa.c
+
+diff --git a/ChangeLog b/ChangeLog
+index ed12723..e1ab9ca 100644
+--- a/ChangeLog
++++ b/ChangeLog
+@@ -1,4 +1,22 @@
++20131109
++ - (dtucker) [configure.ac kex.c key.c myproposal.h] Test for the presence of
++   NID_X9_62_prime256v1, NID_secp384r1 and NID_secp521r1 and test that the
++   latter actually works before using it.  Fedora (at least) has NID_secp521r1
++   that doesn't work (see https://bugzilla.redhat.com/show_bug.cgi?id=1021897).
++
++20101021
++   - djm at cvs.openbsd.org 2010/08/31 12:24:09
++     [regress/cert-hostkey.sh regress/cert-userkey.sh]
++     tests for ECDSA certificates
++
+ 20100924
++ - (djm) OpenBSD CVS Sync
++   - naddy at cvs.openbsd.org 2010/09/10 15:19:29
++     [ssh-keygen.1]
++     * mention ECDSA in more places
++     * less repetition in FILES section
++     * SSHv1 keys are still encrypted with 3DES
++     help and ok jmc@
+    - djm at cvs.openbsd.org 2010/09/22 05:01:30
+      [kex.c kex.h kexecdh.c kexecdhc.c kexecdhs.c readconf.c readconf.h]
+      [servconf.c servconf.h ssh_config.5 sshconnect2.c sshd.c sshd_config.5]
+@@ -6,6 +24,66 @@
+      selection of which key exchange methods are used by ssh(1) and sshd(8)
+      and their order of preference.
+      ok markus@
++   - djm at cvs.openbsd.org 2010/09/11 21:44:20
++     [ssh.1]
++     mention RFC 5656 for ECC stuff
++
++20100910
++  - markus at cvs.openbsd.org 2010/09/02 16:07:25
++     [ssh-keygen.c]
++     permit -b 256, 384 or 521 as key size for ECDSA; ok djm@
++   - naddy at cvs.openbsd.org 2010/09/02 17:21:50
++     [ssh-keygen.c]
++     Switch ECDSA default key size to 256 bits, which according to RFC5656
++     should still be better than our current RSA-2048 default.
++     ok djm@, markus@
++   - djm at cvs.openbsd.org 2010/09/09 10:45:45
++     [kex.c kex.h kexecdh.c key.c key.h monitor.c ssh-ecdsa.c]
++     ECDH/ECDSA compliance fix: these methods vary the hash function they use
++     (SHA256/384/512) depending on the length of the curve in use. The previous
++     code incorrectly used SHA256 in all cases.
++     
++     This fix will cause authentication failure when using 384 or 521-bit curve
++     keys if one peer hasn't been upgraded and the other has. (256-bit curve
++     keys work ok). In particular you may need to specify HostkeyAlgorithms
++     when connecting to a server that has not been upgraded from an upgraded
++     client.
++     
++     ok naddy@
++ - (djm) [authfd.c authfile.c bufec.c buffer.h configure.ac kex.h kexecdh.c]
++   [kexecdhc.c kexecdhs.c key.c key.h myproposal.h packet.c readconf.c]
++   [ssh-agent.c ssh-ecdsa.c ssh-keygen.c ssh.c] Disable ECDH and ECDSA on
++   platforms that don't have the requisite OpenSSL support. ok dtucker@
++ - (dtucker) [kex.h key.c packet.h ssh-agent.c ssh.c] A few more ECC ifdefs
++   for missing headers and compiler warnings.
++
++20100831
++   - djm at cvs.openbsd.org 2010/08/31 11:54:45
++     [PROTOCOL PROTOCOL.agent PROTOCOL.certkeys auth2-jpake.c authfd.c]
++     [authfile.c buffer.h dns.c kex.c kex.h key.c key.h monitor.c]
++     [monitor_wrap.c myproposal.h packet.c packet.h pathnames.h readconf.c]
++     [ssh-add.1 ssh-add.c ssh-agent.1 ssh-agent.c ssh-keygen.1 ssh-keygen.c]
++     [ssh-keyscan.1 ssh-keyscan.c ssh-keysign.8 ssh.1 ssh.c ssh2.h]
++     [ssh_config.5 sshconnect.c sshconnect2.c sshd.8 sshd.c sshd_config.5]
++     [uuencode.c uuencode.h bufec.c kexecdh.c kexecdhc.c kexecdhs.c ssh-ecdsa.c]
++     Implement Elliptic Curve Cryptography modes for key exchange (ECDH) and
++     host/user keys (ECDSA) as specified by RFC5656. ECDH and ECDSA offer
++     better performance than plain DH and DSA at the same equivalent symmetric
++     key length, as well as much shorter keys.
++     
++     Only the mandatory sections of RFC5656 are implemented, specifically the
++     three REQUIRED curves nistp256, nistp384 and nistp521 and only ECDH and
++     ECDSA. Point compression (optional in RFC5656 is NOT implemented).
++     
++     Certificate host and user keys using the new ECDSA key types are supported.
++     
++     Note that this code has not been tested for interoperability and may be
++     subject to change.
++     
++     feedback and ok markus@
++ - (djm) [Makefile.in] Add new ECC files
++ - (djm) [bufec.c kexecdh.c kexecdhc.c kexecdhs.c ssh-ecdsa.c] include
++   includes.h
+ 
+ 20100521
+  - (djm) OpenBSD CVS Sync
+diff --git a/Makefile.in b/Makefile.in
+index 075ac8b..07013c4 100644
+--- a/Makefile.in
++++ b/Makefile.in
+@@ -75,9 +75,10 @@ LIBSSH_OBJS=acss.o authfd.o authfile.o bufaux.o bufbn.o buffer.o \
+ 	log.o match.o md-sha256.o moduli.o nchan.o packet.o \
+ 	readpass.o rsa.o ttymodes.o xmalloc.o addrmatch.o \
+ 	atomicio.o key.o dispatch.o kex.o mac.o uidswap.o uuencode.o misc.o \
+-	monitor_fdpass.o rijndael.o ssh-dss.o ssh-rsa.o dh.o kexdh.o \
+-	kexgex.o kexdhc.o kexgexc.o scard.o msg.o progressmeter.o dns.o \
+-	entropy.o scard-opensc.o gss-genr.o umac.o jpake.o schnorr.o nsskeys.o \
++	monitor_fdpass.o rijndael.o ssh-dss.o ssh-ecdsa.o ssh-rsa.o dh.o \
++	kexdh.o kexgex.o kexdhc.o kexgexc.o bufec.o kexecdh.o kexecdhc.o \
++	scard.o msg.o progressmeter.o dns.o entropy.o scard-opensc.o gss-genr.o umac.o jpake.o \
++	schnorr.o nsskeys.o \
+ 	kexgssc.o auditstub.o ssh-pkcs11.o stringprep.o
+ 
+ SSHOBJS= ssh.o readconf.o clientloop.o sshtty.o \
+@@ -90,7 +91,7 @@ SSHDOBJS=sshd.o auth-rhosts.o auth-passwd.o auth-rsa.o auth-rh-rsa.o \
+ 	auth-chall.o auth2-chall.o groupaccess.o \
+ 	auth-skey.o auth-bsdauth.o auth2-hostbased.o auth2-kbdint.o \
+ 	auth2-none.o auth2-passwd.o auth2-pubkey.o auth2-jpake.o \
+-	monitor_mm.o monitor.o monitor_wrap.o kexdhs.o kexgexs.o \
++	monitor_mm.o monitor.o monitor_wrap.o kexdhs.o kexgexs.o kexecdhs.o \
+ 	auth-krb5.o audit-linux.o \
+  	auth2-gss.o gss-serv.o gss-serv-krb5.o kexgsss.o\
+ 	loginrec.o auth-pam.o auth-shadow.o auth-sia.o md5crypt.o \
+diff --git a/PROTOCOL b/PROTOCOL
+index 0176d68..417b679 100644
+--- a/PROTOCOL
++++ b/PROTOCOL
+@@ -12,7 +12,9 @@ explicitly implemented as extensions described below.
+ The protocol used by OpenSSH's ssh-agent is described in the file
+ PROTOCOL.agent
+ 
+-1. transport: Protocol 2 MAC algorithm "umac-64 at openssh.com"
++1. Transport protocol changes
++
++1.1. transport: Protocol 2 MAC algorithm "umac-64 at openssh.com"
+ 
+ This is a new transport-layer MAC method using the UMAC algorithm
+ (rfc4418). This method is identical to the "umac-64" method documented
+@@ -20,7 +22,7 @@ in:
+ 
+ http://www.openssh.com/txt/draft-miller-secsh-umac-01.txt
+ 
+-2. transport: Protocol 2 compression algorithm "zlib at openssh.com"
++1.2. transport: Protocol 2 compression algorithm "zlib at openssh.com"
+ 
+ This transport-layer compression method uses the zlib compression
+ algorithm (identical to the "zlib" method in rfc4253), but delays the
+@@ -31,14 +33,27 @@ The method is documented in:
+ 
+ http://www.openssh.com/txt/draft-miller-secsh-compression-delayed-00.txt
+ 
+-3. transport: New public key algorithms "ssh-rsa-cert-v00 at openssh.com" and
+-   "ssh-dsa-cert-v00 at openssh.com"
++1.3. transport: New public key algorithms "ssh-rsa-cert-v00 at openssh.com",
++     "ssh-dsa-cert-v00 at openssh.com",
++     "ecdsa-sha2-nistp256-cert-v01 at openssh.com",
++     "ecdsa-sha2-nistp384-cert-v01 at openssh.com" and
++     "ecdsa-sha2-nistp521-cert-v01 at openssh.com"
+ 
+-OpenSSH introduces two new public key algorithms to support certificate
++OpenSSH introduces new public key algorithms to support certificate
+ authentication for users and hostkeys. These methods are documented in
+ the file PROTOCOL.certkeys
+ 
+-4. connection: Channel write close extension "eow at openssh.com"
++1.4. transport: Elliptic Curve cryptography
++
++OpenSSH supports ECC key exchange and public key authentication as
++specified in RFC5656. Only the ecdsa-sha2-nistp256, ecdsa-sha2-nistp384
++and ecdsa-sha2-nistp521 curves over GF(p) are supported. Elliptic
++curve points encoded using point compression are NOT accepted or
++generated.
++
++2. Connection protocol changes
++
++2.1. connection: Channel write close extension "eow at openssh.com"
+ 
+ The SSH connection protocol (rfc4254) provides the SSH_MSG_CHANNEL_EOF
+ message to allow an endpoint to signal its peer that it will send no
+@@ -77,8 +92,8 @@ message is only sent to OpenSSH peers (identified by banner).
+ Other SSH implementations may be whitelisted to receive this message
+ upon request.
+ 
+-5. connection: disallow additional sessions extension
+-   "no-more-sessions at openssh.com"
++2.2. connection: disallow additional sessions extension
++     "no-more-sessions at openssh.com"
+ 
+ Most SSH connections will only ever request a single session, but a
+ attacker may abuse a running ssh client to surreptitiously open
+@@ -105,7 +120,7 @@ of this message, the no-more-sessions request is only sent to OpenSSH
+ servers (identified by banner). Other SSH implementations may be
+ whitelisted to receive this message upon request.
+ 
+-6. connection: Tunnel forward extension "tun at openssh.com"
++2.3. connection: Tunnel forward extension "tun at openssh.com"
+ 
+ OpenSSH supports layer 2 and layer 3 tunnelling via the "tun at openssh.com"
+ channel type. This channel type supports forwarding of network packets
+@@ -166,7 +181,9 @@ The contents of the "data" field for layer 3 packets is:
+ The "frame" field contains an IEEE 802.3 Ethernet frame, including
+ header.
+ 
+-7. sftp: Reversal of arguments to SSH_FXP_SYMLINK
++3. SFTP protocol changes
++
++3.1. sftp: Reversal of arguments to SSH_FXP_SYMLINK
+ 
+ When OpenSSH's sftp-server was implemented, the order of the arguments
+ to the SSH_FXP_SYMLINK method was inadvertently reversed. Unfortunately,
+@@ -179,7 +196,7 @@ SSH_FXP_SYMLINK as follows:
+ 	string		targetpath
+ 	string		linkpath
+ 
+-8. sftp: Server extension announcement in SSH_FXP_VERSION
++3.2. sftp: Server extension announcement in SSH_FXP_VERSION
+ 
+ OpenSSH's sftp-server lists the extensions it supports using the
+ standard extension announcement mechanism in the SSH_FXP_VERSION server
+@@ -200,7 +217,7 @@ ever changed in an incompatible way. The server MAY advertise the same
+ extension with multiple versions (though this is unlikely). Clients MUST
+ check the version number before attempting to use the extension.
+ 
+-9. sftp: Extension request "posix-rename at openssh.com"
++3.3. sftp: Extension request "posix-rename at openssh.com"
+ 
+ This operation provides a rename operation with POSIX semantics, which
+ are different to those provided by the standard SSH_FXP_RENAME in
+@@ -217,7 +234,7 @@ rename(oldpath, newpath) and will respond with a SSH_FXP_STATUS message.
+ This extension is advertised in the SSH_FXP_VERSION hello with version
+ "1".
+ 
+-10. sftp: Extension requests "statvfs at openssh.com" and
++3.4. sftp: Extension requests "statvfs at openssh.com" and
+          "fstatvfs at openssh.com"
+ 
+ These requests correspond to the statvfs and fstatvfs POSIX system
+diff --git a/PROTOCOL.agent b/PROTOCOL.agent
+index b34fcd3..de94d03 100644
+--- a/PROTOCOL.agent
++++ b/PROTOCOL.agent
+@@ -159,8 +159,8 @@ successfully added or a SSH_AGENT_FAILURE if an error occurred.
+ 
+ 2.2.3 Add protocol 2 key
+ 
+-The OpenSSH agent supports DSA and RSA keys for protocol 2. DSA keys may
+-be added using the following request
++The OpenSSH agent supports DSA, ECDSA and RSA keys for protocol 2. DSA
++keys may be added using the following request
+ 
+ 	byte			SSH2_AGENTC_ADD_IDENTITY or
+ 				SSH2_AGENTC_ADD_ID_CONSTRAINED
+@@ -182,6 +182,30 @@ DSA certificates may be added with:
+ 	string			key_comment
+ 	constraint[]		key_constraints
+ 
++ECDSA keys may be added using the following request
++
++	byte			SSH2_AGENTC_ADD_IDENTITY or
++				SSH2_AGENTC_ADD_ID_CONSTRAINED
++	string			"ecdsa-sha2-nistp256" |
++				"ecdsa-sha2-nistp384" |
++				"ecdsa-sha2-nistp521"
++	string			ecdsa_curve_name
++	string			ecdsa_public_key
++	mpint			ecdsa_private
++	string			key_comment
++	constraint[]		key_constraints
++
++ECDSA certificates may be added with:
++	byte			SSH2_AGENTC_ADD_IDENTITY or
++				SSH2_AGENTC_ADD_ID_CONSTRAINED
++	string			"ecdsa-sha2-nistp256-cert-v01 at openssh.com" |
++				"ecdsa-sha2-nistp384-cert-v01 at openssh.com" |
++				"ecdsa-sha2-nistp521-cert-v01 at openssh.com"
++	string			certificate
++	mpint			ecdsa_private_key
++	string			key_comment
++	constraint[]		key_constraints
++
+ RSA keys may be added with this request:
+ 
+ 	byte			SSH2_AGENTC_ADD_IDENTITY or
+@@ -214,7 +238,7 @@ order to the protocol 1 add keys message. As with the corresponding
+ protocol 1 "add key" request, the private key is overspecified to avoid
+ redundant processing.
+ 
+-For both DSA and RSA key add requests, "key_constraints" may only be
++For DSA, ECDSA and RSA key add requests, "key_constraints" may only be
+ present if the request type is SSH2_AGENTC_ADD_ID_CONSTRAINED.
+ 
+ The agent will reply with a SSH_AGENT_SUCCESS if the key has been
+@@ -294,8 +318,7 @@ Protocol 2 keys may be removed with the following request:
+ 	string			key_blob
+ 
+ Where "key_blob" is encoded as per RFC 4253 section 6.6 "Public Key
+-Algorithms" for either of the supported key types: "ssh-dss" or
+-"ssh-rsa".
++Algorithms" for any of the supported protocol 2 key types.
+ 
+ The agent will delete any private key matching the specified public key
+ and return SSH_AGENT_SUCCESS. If no such key was found, the agent will
+@@ -364,8 +387,7 @@ Followed by zero or more consecutive keys, encoded as:
+ 	string			key_comment
+ 
+ Where "key_blob" is encoded as per RFC 4253 section 6.6 "Public Key
+-Algorithms" for either of the supported key types: "ssh-dss" or
+-"ssh-rsa".
++Algorithms" for any of the supported protocol 2 key types.
+ 
+ 2.6 Private key operations
+ 
+@@ -429,9 +451,9 @@ a protocol 2 key:
+ 	uint32			flags
+ 
+ Where "key_blob" is encoded as per RFC 4253 section 6.6 "Public Key
+-Algorithms" for either of the supported key types: "ssh-dss" or
+-"ssh-rsa". "flags" is a bit-mask, but at present only one possible value
+-is defined (see below for its meaning):
++Algorithms" for any of the supported protocol 2 key types. "flags" is
++a bit-mask, but at present only one possible value is defined (see below
++for its meaning):
+ 
+ 	SSH_AGENT_OLD_SIGNATURE		1
+ 
+@@ -535,4 +557,4 @@ Locking and unlocking affects both protocol 1 and protocol 2 keys.
+ 	SSH_AGENT_CONSTRAIN_LIFETIME			1
+ 	SSH_AGENT_CONSTRAIN_CONFIRM			2
+ 
+-$OpenBSD: PROTOCOL.agent,v 1.5 2010/02/26 20:29:54 djm Exp $
++$OpenBSD: PROTOCOL.agent,v 1.6 2010/08/31 11:54:45 djm Exp $
+diff --git a/PROTOCOL.certkeys b/PROTOCOL.certkeys
+index d9707c7..a8d002b 100644
+--- a/PROTOCOL.certkeys
++++ b/PROTOCOL.certkeys
+@@ -5,31 +5,37 @@ Background
+ ----------
+ 
+ The SSH protocol currently supports a simple public key authentication
+-mechanism. Unlike other public key implementations, SSH eschews the
+-use of X.509 certificates and uses raw keys. This approach has some
+-benefits relating to simplicity of configuration and minimisation
+-of attack surface, but it does not support the important use-cases
+-of centrally managed, passwordless authentication and centrally
+-certified host keys.
++mechanism. Unlike other public key implementations, SSH eschews the use
++of X.509 certificates and uses raw keys. This approach has some benefits
++relating to simplicity of configuration and minimisation of attack
++surface, but it does not support the important use-cases of centrally
++managed, passwordless authentication and centrally certified host keys.
+ 
+ These protocol extensions build on the simple public key authentication
+-system already in SSH to allow certificate-based authentication.
+-The certificates used are not traditional X.509 certificates, with
+-numerous options and complex encoding rules, but something rather
+-more minimal: a key, some identity information and usage options
+-that have been signed with some other trusted key.
++system already in SSH to allow certificate-based authentication. The
++certificates used are not traditional X.509 certificates, with numerous
++options and complex encoding rules, but something rather more minimal: a
++key, some identity information and usage options that have been signed
++with some other trusted key.
+ 
+ A sshd server may be configured to allow authentication via certified
+-keys, by extending the existing ~/.ssh/authorized_keys mechanism
+-to allow specification of certification authority keys in addition
+-to raw user keys. The ssh client will support automatic verification
+-of acceptance of certified host keys, by adding a similar ability
+-to specify CA keys in ~/.ssh/known_hosts.
++keys, by extending the existing ~/.ssh/authorized_keys mechanism to
++allow specification of certification authority keys in addition to
++raw user keys. The ssh client will support automatic verification of
++acceptance of certified host keys, by adding a similar ability to
++specify CA keys in ~/.ssh/known_hosts.
+ 
+-Certified keys are represented using two new key types:
+-ssh-rsa-cert-v01 at openssh.com and ssh-dss-cert-v01 at openssh.com that
+-include certification information along with the public key that is used
+-to sign challenges. ssh-keygen performs the CA signing operation.
++Certified keys are represented using new key types:
++
++    ssh-rsa-cert-v01 at openssh.com
++    ssh-dss-cert-v01 at openssh.com
++    ecdsa-sha2-nistp256-cert-v01 at openssh.com
++    ecdsa-sha2-nistp384-cert-v01 at openssh.com
++    ecdsa-sha2-nistp521-cert-v01 at openssh.com
++
++These include certification information along with the public key
++that is used to sign challenges. ssh-keygen performs the CA signing
++operation.
+ 
+ Protocol extensions
+ -------------------
+@@ -47,10 +53,9 @@ in RFC4252 section 7.
+ New public key formats
+ ----------------------
+ 
+-The ssh-rsa-cert-v01 at openssh.com and ssh-dss-cert-v01 at openssh.com key
+-types take a similar high-level format (note: data types and
+-encoding are as per RFC4251 section 5). The serialised wire encoding of
+-these certificates is also used for storing them on disk.
++The certificate key types take a similar high-level format (note: data
++types and encoding are as per RFC4251 section 5). The serialised wire
++encoding of these certificates is also used for storing them on disk.
+ 
+ #define SSH_CERT_TYPE_USER    1
+ #define SSH_CERT_TYPE_HOST    2
+@@ -93,6 +98,26 @@ DSA certificate
+     string    signature key
+     string    signature
+ 
++ECDSA certificate
++
++    string    "ecdsa-sha2-nistp256 at openssh.com" |
++              "ecdsa-sha2-nistp384 at openssh.com" |
++              "ecdsa-sha2-nistp521 at openssh.com"
++    string    nonce
++    string    curve
++    string    public_key
++    uint64    serial
++    uint32    type
++    string    key id
++    string    valid principals
++    uint64    valid after
++    uint64    valid before
++    string    critical options
++    string    extensions
++    string    reserved
++    string    signature key
++    string    signature
++
+ The nonce field is a CA-provided random bitstring of arbitrary length
+ (but typically 16 or 32 bytes) included to make attacks that depend on
+ inducing collisions in the signature hash infeasible.
+@@ -101,6 +126,9 @@ e and n are the RSA exponent and public modulus respectively.
+ 
+ p, q, g, y are the DSA parameters as described in FIPS-186-2.
+ 
++curve and public key are respectively the ECDSA "[identifier]" and "Q"
++defined in section 3.1 of RFC5656.
++
+ serial is an optional certificate serial number set by the CA to
+ provide an abbreviated way to refer to certificates from that CA.
+ If a CA does not with to number its certificates it must set this
+@@ -123,7 +151,8 @@ any principal of the specified type. XXX DNS wildcards?
+ "valid after" and "valid before" specify a validity period for the
+ certificate. Each represents a time in seconds since 1970-01-01
+ 00:00:00. A certificate is considered valid if:
+-	 valid after <= current time < valid before
++
++    valid after <= current time < valid before
+ 
+ criticial options is a set of zero or more key options encoded as
+ below. All such options are "critical" in the sense that an implementation
+@@ -137,15 +166,17 @@ The reserved field is currently unused and is ignored in this version of
+ the protocol.
+ 
+ signature key contains the CA key used to sign the certificate.
+-The valid key types for CA keys are ssh-rsa and ssh-dss. "Chained"
++The valid key types for CA keys are ssh-rsa, ssh-dss and the ECDSA types
++ecdsa-sha2-nistp256, ecdsa-sha2-nistp384, ecdsa-sha2-nistp521. "Chained"
+ certificates, where the signature key type is a certificate type itself
+ are NOT supported. Note that it is possible for a RSA certificate key to
+-be signed by a DSS CA key and vice-versa.
++be signed by a DSS or ECDSA CA key and vice-versa.
+ 
+ signature is computed over all preceding fields from the initial string
+ up to, and including the signature key. Signatures are computed and
+ encoded according to the rules defined for the CA's public key algorithm
+-(RFC4253 section 6.6 for ssh-rsa and ssh-dss).
++(RFC4253 section 6.6 for ssh-rsa and ssh-dss, RFC5656 for the ECDSA
++types).
+ 
+ Critical options
+ ----------------
+diff --git a/auth2-jpake.c b/auth2-jpake.c
+index 5de5506..a460e82 100644
+--- a/auth2-jpake.c
++++ b/auth2-jpake.c
+@@ -1,4 +1,4 @@
+-/* $OpenBSD: auth2-jpake.c,v 1.3 2009/03/05 07:18:19 djm Exp $ */
++/* $OpenBSD: auth2-jpake.c,v 1.4 2010/08/31 11:54:45 djm Exp $ */
+ /*
+  * Copyright (c) 2008 Damien Miller.  All rights reserved.
+  *
+@@ -162,6 +162,11 @@ derive_rawsalt(const char *username, u_char *rawsalt, u_int len)
+ 			fatal("%s: DSA key missing priv_key", __func__);
+ 		buffer_put_bignum2(&b, k->dsa->priv_key);
+ 		break;
++	case KEY_ECDSA:
++		if (EC_KEY_get0_private_key(k->ecdsa) == NULL)
++			fatal("%s: ECDSA key missing priv_key", __func__);
++		buffer_put_bignum2(&b, EC_KEY_get0_private_key(k->ecdsa));
++		break;
+ 	default:
+ 		fatal("%s: unknown key type %d", __func__, k->type);
+ 	}
+diff --git a/authfd.c b/authfd.c
+index 867aff4..cecade1 100644
+--- a/authfd.c
++++ b/authfd.c
+@@ -509,6 +509,21 @@ ssh_encode_identity_ssh2(Buffer *b, Key *key, const char *comment)
+ 		    buffer_len(&key->cert->certblob));
+ 		buffer_put_bignum2(b, key->dsa->priv_key);
+ 		break;
++#ifdef OPENSSL_HAS_ECC
++	case KEY_ECDSA:
++		buffer_put_cstring(b, key_curve_nid_to_name(key->ecdsa_nid));
++		buffer_put_ecpoint(b, EC_KEY_get0_group(key->ecdsa),
++		    EC_KEY_get0_public_key(key->ecdsa));
++		buffer_put_bignum2(b, EC_KEY_get0_private_key(key->ecdsa));
++		break;
++	case KEY_ECDSA_CERT:
++		if (key->cert == NULL || buffer_len(&key->cert->certblob) == 0)
++			fatal("%s: no cert/certblob", __func__);
++		buffer_put_string(b, buffer_ptr(&key->cert->certblob),
++		    buffer_len(&key->cert->certblob));
++		buffer_put_bignum2(b, EC_KEY_get0_private_key(key->ecdsa));
++		break;
++#endif
+ 	}
+ 	buffer_put_cstring(b, comment);
+ }
+@@ -541,6 +556,8 @@ ssh_add_identity_constrained(AuthenticationConnection *auth, Key *key,
+ 	case KEY_DSA:
+ 	case KEY_DSA_CERT:
+ 	case KEY_DSA_CERT_V00:
++	case KEY_ECDSA:
++	case KEY_ECDSA_CERT:
+ 		type = constrained ?
+ 		    SSH2_AGENTC_ADD_ID_CONSTRAINED :
+ 		    SSH2_AGENTC_ADD_IDENTITY;
+@@ -595,7 +612,8 @@ ssh_remove_identity(AuthenticationConnection *auth, Key *key)
+ 		buffer_put_bignum(&msg, key->rsa->e);
+ 		buffer_put_bignum(&msg, key->rsa->n);
+ 	} else if (key_type_plain(key->type) == KEY_DSA ||
+-	    key_type_plain(key->type) == KEY_RSA) {
++	    key_type_plain(key->type) == KEY_RSA ||
++	    key_type_plain(key->type) == KEY_ECDSA) {
+ 		key_to_blob(key, &blob, &blen);
+ 		buffer_put_char(&msg, SSH2_AGENTC_REMOVE_IDENTITY);
+ 		buffer_put_string(&msg, blob, blen);
+diff --git a/authfile.c b/authfile.c
+index c0c0261..d275296 100644
+--- a/authfile.c
++++ b/authfile.c
+@@ -212,6 +212,12 @@ key_save_private_pem(Key *key, const char *filename, const char *_passphrase,
+ 		success = PEM_write_DSAPrivateKey(fp, key->dsa,
+ 		    cipher, passphrase, len, NULL, NULL);
+ 		break;
++#ifdef OPENSSL_HAS_ECC
++	case KEY_ECDSA:
++		success = PEM_write_ECPrivateKey(fp, key->ecdsa,
++		    cipher, passphrase, len, NULL, NULL);
++		break;
++#endif
+ 	case KEY_RSA:
+ 		success = PEM_write_RSAPrivateKey(fp, key->rsa,
+ 		    cipher, passphrase, len, NULL, NULL);
+@@ -230,6 +236,7 @@ key_save_private(Key *key, const char *filename, const char *passphrase,
+ 		return key_save_private_rsa1(key, filename, passphrase,
+ 		    comment);
+ 	case KEY_DSA:
++	case KEY_ECDSA:
+ 	case KEY_RSA:
+ 		return key_save_private_pem(key, filename, passphrase,
+ 		    comment);
+@@ -515,6 +522,31 @@ key_load_private_pem(int fd, int type, const char *passphrase,
+ #ifdef DEBUG_PK
+ 		DSA_print_fp(stderr, prv->dsa, 8);
+ #endif
++#ifdef OPENSSL_HAS_ECC
++	} else if (pk->type == EVP_PKEY_EC &&
++	    (type == KEY_UNSPEC||type==KEY_ECDSA)) {
++		prv = key_new(KEY_UNSPEC);
++		prv->ecdsa = EVP_PKEY_get1_EC_KEY(pk);
++		prv->type = KEY_ECDSA;
++		prv->ecdsa_nid = key_ecdsa_group_to_nid(
++		    EC_KEY_get0_group(prv->ecdsa));
++		if (key_curve_nid_to_name(prv->ecdsa_nid) == NULL) {
++			key_free(prv);
++			prv = NULL;
++		}
++		if (key_ec_validate_public(EC_KEY_get0_group(prv->ecdsa),
++		    EC_KEY_get0_public_key(prv->ecdsa)) != 0 ||
++		    key_ec_validate_private(prv->ecdsa) != 0) {
++			error("%s: bad ECDSA key", __func__);
++			key_free(prv);
++			prv = NULL;
++		}
++		name = "ecdsa w/o comment";
++#ifdef DEBUG_PK
++		if (prv->ecdsa != NULL)
++			key_dump_ec_key(prv->ecdsa);
++#endif
++#endif /* OPENSSL_HAS_ECC */
+ 	} else {
+ 		error("PEM_read_PrivateKey: mismatch or "
+ 		    "unknown EVP_PKEY save_type %d", pk->save_type);
+@@ -581,6 +613,7 @@ key_load_private_type(int type, const char *filename, const char *passphrase,
+ 		    commentp);
+ 		/* closes fd */
+ 	case KEY_DSA:
++	case KEY_ECDSA:
+ 	case KEY_RSA:
+ 	case KEY_UNSPEC:
+ 		return key_load_private_pem(fd, type, passphrase, commentp);
+diff --git a/bufec.c b/bufec.c
+new file mode 100644
+index 0000000..3dcb494
+--- /dev/null
++++ b/bufec.c
+@@ -0,0 +1,146 @@
++/* $OpenBSD: bufec.c,v 1.1 2010/08/31 11:54:45 djm Exp $ */
++/*
++ * Copyright (c) 2010 Damien Miller <djm at mindrot.org>
++ *
++ * Permission to use, copy, modify, and distribute this software for any
++ * purpose with or without fee is hereby granted, provided that the above
++ * copyright notice and this permission notice appear in all copies.
++ *
++ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
++ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
++ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
++ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
++ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
++ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
++ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
++ */
++
++#include "includes.h"
++
++#ifdef OPENSSL_HAS_ECC
++
++#include <sys/types.h>
++
++#include <openssl/bn.h>
++#include <openssl/ec.h>
++
++#include <string.h>
++#include <stdarg.h>
++
++#include "xmalloc.h"
++#include "buffer.h"
++#include "log.h"
++#include "misc.h"
++
++/*
++ * Maximum supported EC GFp field length is 528 bits. SEC1 uncompressed
++ * encoding represents this as two bitstring points that should each
++ * be no longer than the field length, SEC1 specifies a 1 byte
++ * point type header.
++ * Being paranoid here may insulate us to parsing problems in
++ * EC_POINT_oct2point.
++ */
++#define BUFFER_MAX_ECPOINT_LEN ((528*2 / 8) + 1)
++
++/*
++ * Append an EC_POINT to the buffer as a string containing a SEC1 encoded
++ * uncompressed point. Fortunately OpenSSL handles the gory details for us.
++ */
++int
++buffer_put_ecpoint_ret(Buffer *buffer, const EC_GROUP *curve,
++    const EC_POINT *point)
++{
++	u_char *buf = NULL;
++	size_t len;
++	BN_CTX *bnctx;
++	int ret = -1;
++
++	/* Determine length */
++	if ((bnctx = BN_CTX_new()) == NULL)
++		fatal("%s: BN_CTX_new failed", __func__);
++	len = EC_POINT_point2oct(curve, point, POINT_CONVERSION_UNCOMPRESSED,
++	    NULL, 0, bnctx);
++	if (len > BUFFER_MAX_ECPOINT_LEN) {
++		error("%s: giant EC point: len = %lu (max %u)",
++		    __func__, (u_long)len, BUFFER_MAX_ECPOINT_LEN);
++		goto out;
++	}
++	/* Convert */
++	buf = xmalloc(len);
++	if (EC_POINT_point2oct(curve, point, POINT_CONVERSION_UNCOMPRESSED,
++	    buf, len, bnctx) != len) {
++		error("%s: EC_POINT_point2oct length mismatch", __func__);
++		goto out;
++	}
++	/* Append */
++	buffer_put_string(buffer, buf, len);
++	ret = 0;
++ out:
++	if (buf != NULL) {
++		bzero(buf, len);
++		xfree(buf);
++	}
++	BN_CTX_free(bnctx);
++	return ret;
++}
++
++void
++buffer_put_ecpoint(Buffer *buffer, const EC_GROUP *curve,
++    const EC_POINT *point)
++{
++	if (buffer_put_ecpoint_ret(buffer, curve, point) == -1)
++		fatal("%s: buffer error", __func__);
++}
++
++int
++buffer_get_ecpoint_ret(Buffer *buffer, const EC_GROUP *curve,
++    EC_POINT *point)
++{
++	u_char *buf;
++	u_int len;
++	BN_CTX *bnctx;
++	int ret = -1;
++
++	if ((buf = buffer_get_string_ret(buffer, &len)) == NULL) {
++		error("%s: invalid point", __func__);
++		return -1;
++	}
++	if ((bnctx = BN_CTX_new()) == NULL)
++		fatal("%s: BN_CTX_new failed", __func__);
++	if (len > BUFFER_MAX_ECPOINT_LEN) {
++		error("%s: EC_POINT too long: %u > max %u", __func__,
++		    len, BUFFER_MAX_ECPOINT_LEN);
++		goto out;
++	}
++	if (len == 0) {
++		error("%s: EC_POINT buffer is empty", __func__);
++		goto out;
++	}
++	if (buf[0] != POINT_CONVERSION_UNCOMPRESSED) {
++		error("%s: EC_POINT is in an incorrect form: "
++		    "0x%02x (want 0x%02x)", __func__, buf[0],
++		    POINT_CONVERSION_UNCOMPRESSED);
++		goto out;
++	}
++	if (EC_POINT_oct2point(curve, point, buf, len, bnctx) != 1) {
++		error("buffer_get_bignum2_ret: BN_bin2bn failed");
++		goto out;
++	}
++	/* EC_POINT_oct2point verifies that the point is on the curve for us */
++	ret = 0;
++ out:
++	BN_CTX_free(bnctx);
++	bzero(buf, len);
++	xfree(buf);
++	return ret;
++}
++
++void
++buffer_get_ecpoint(Buffer *buffer, const EC_GROUP *curve,
++    EC_POINT *point)
++{
++	if (buffer_get_ecpoint_ret(buffer, curve, point) == -1)
++		fatal("%s: buffer error", __func__);
++}
++
++#endif /* OPENSSL_HAS_ECC */
+diff --git a/buffer.h b/buffer.h
+index 6ae2619..fd19405 100644
+--- a/buffer.h
++++ b/buffer.h
+@@ -84,4 +84,13 @@ void	*buffer_get_string_ret(Buffer *, u_int *);
+ void   *buffer_get_string_ptr_ret(Buffer *, u_int *);
+ int	buffer_get_char_ret(char *, Buffer *);
+ 
++#ifdef OPENSSL_HAS_ECC
++#include <openssl/ec.h>
++
++int	buffer_put_ecpoint_ret(Buffer *, const EC_GROUP *, const EC_POINT *);
++void	buffer_put_ecpoint(Buffer *, const EC_GROUP *, const EC_POINT *);
++int	buffer_get_ecpoint_ret(Buffer *, const EC_GROUP *, EC_POINT *);
++void	buffer_get_ecpoint(Buffer *, const EC_GROUP *, EC_POINT *);
++#endif
++
+ #endif				/* BUFFER_H */
+diff --git a/configure.ac b/configure.ac
+index c65824b..4a021a7 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -2263,6 +2263,122 @@ fi
+ # Search for SHA256 support in libc and/or OpenSSL
+ AC_CHECK_FUNCS(SHA256_Update EVP_sha256)
+ 
++# Check complete ECC support in OpenSSL
++AC_MSG_CHECKING([whether OpenSSL has NID_X9_62_prime256v1])
++AC_LINK_IFELSE(
++	[AC_LANG_PROGRAM([[
++#include <openssl/ec.h>
++#include <openssl/ecdh.h>
++#include <openssl/ecdsa.h>
++#include <openssl/evp.h>
++#include <openssl/objects.h>
++#include <openssl/opensslv.h>
++#if OPENSSL_VERSION_NUMBER < 0x0090807f /* 0.9.8g */
++# error "OpenSSL < 0.9.8g has unreliable ECC code"
++#endif
++	]], [[
++	EC_KEY *e = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
++	const EVP_MD *m = EVP_sha256(); /* We need this too */
++	]])],
++	[ AC_MSG_RESULT([yes])
++	  enable_nistp256=1 ],
++	[ AC_MSG_RESULT([no]) ]
++)
++
++AC_MSG_CHECKING([whether OpenSSL has NID_secp384r1])
++AC_LINK_IFELSE(
++	[AC_LANG_PROGRAM([[
++#include <openssl/ec.h>
++#include <openssl/ecdh.h>
++#include <openssl/ecdsa.h>
++#include <openssl/evp.h>
++#include <openssl/objects.h>
++#include <openssl/opensslv.h>
++#if OPENSSL_VERSION_NUMBER < 0x0090807f /* 0.9.8g */
++# error "OpenSSL < 0.9.8g has unreliable ECC code"
++#endif
++	]], [[
++	EC_KEY *e = EC_KEY_new_by_curve_name(NID_secp384r1);
++	const EVP_MD *m = EVP_sha384(); /* We need this too */
++	]])],
++	[ AC_MSG_RESULT([yes])
++	  enable_nistp384=1 ],
++	[ AC_MSG_RESULT([no]) ]
++)
++
++AC_MSG_CHECKING([whether OpenSSL has NID_secp521r1])
++AC_LINK_IFELSE(
++	[AC_LANG_PROGRAM([[
++#include <openssl/ec.h>
++#include <openssl/ecdh.h>
++#include <openssl/ecdsa.h>
++#include <openssl/evp.h>
++#include <openssl/objects.h>
++#include <openssl/opensslv.h>
++#if OPENSSL_VERSION_NUMBER < 0x0090807f /* 0.9.8g */
++# error "OpenSSL < 0.9.8g has unreliable ECC code"
++#endif
++	]], [[
++	EC_KEY *e = EC_KEY_new_by_curve_name(NID_secp521r1);
++	const EVP_MD *m = EVP_sha512(); /* We need this too */
++	]])],
++	[ AC_MSG_RESULT([yes])
++	  AC_MSG_CHECKING([if OpenSSL's NID_secp521r1 is functional])
++	  AC_RUN_IFELSE(
++		[AC_LANG_PROGRAM([[
++#include <openssl/ec.h>
++#include <openssl/ecdh.h>
++#include <openssl/ecdsa.h>
++#include <openssl/evp.h>
++#include <openssl/objects.h>
++#include <openssl/opensslv.h>
++		]],[[
++		EC_KEY *e = EC_KEY_new_by_curve_name(NID_secp521r1);
++		const EVP_MD *m = EVP_sha512(); /* We need this too */
++		exit(e == NULL || m == NULL);
++		]])],
++		[ AC_MSG_RESULT([yes])
++		  enable_nistp521=1 ],
++		[ AC_MSG_RESULT([no]) ],
++		[ AC_MSG_WARN([cross-compiling: assuming yes])
++		  enable_nistp521=1 ]
++	  )],
++	AC_MSG_RESULT([no])
++)
++
++COMMENT_OUT_ECC="#no ecc#"
++TEST_SSH_ECC=no
++
++if test x$enable_nistp256 = x1 || test x$enable_nistp384 = x1 || \
++    x$enable_nistp521 = x1; then
++	AC_DEFINE(OPENSSL_HAS_ECC, [1], [OpenSSL has ECC])
++fi
++if test x$enable_nistp256 = x1; then
++	AC_DEFINE([OPENSSL_HAS_NISTP256], [1],
++	    [libcrypto has NID_X9_62_prime256v1])
++	TEST_SSH_ECC=yes
++	COMMENT_OUT_ECC=""
++else
++	unsupported_algorithms="$unsupported_algorithms ecdsa-sha2-nistp256 \
++	    ecdh-sha2-nistp256 ecdsa-sha2-nistp256-cert-v01 at openssh.com"
++fi
++if test x$enable_nistp384 = x1; then
++	AC_DEFINE([OPENSSL_HAS_NISTP384], [1], [libcrypto has NID_secp384r1])
++	TEST_SSH_ECC=yes
++	COMMENT_OUT_ECC=""
++else
++	unsupported_algorithms="$unsupported_algorithms ecdsa-sha2-nistp384 \
++	    ecdh-sha2-nistp384 ecdsa-sha2-nistp384-cert-v01 at openssh.com"
++fi
++if test x$enable_nistp521 = x1; then
++	AC_DEFINE([OPENSSL_HAS_NISTP521], [1], [libcrypto has NID_secp521r1])
++	TEST_SSH_ECC=yes
++	COMMENT_OUT_ECC=""
++else
++	unsupported_algorithms="$unsupported_algorithms ecdh-sha2-nistp521 \
++	    ecdsa-sha2-nistp521 ecdsa-sha2-nistp521-cert-v01 at openssh.com"
++fi
++
+ saved_LIBS="$LIBS"
+ AC_CHECK_LIB(iaf, ia_openinfo, [
+ 	LIBS="$LIBS -liaf"
+diff --git a/dns.c b/dns.c
+index 30c89eb..dfa4c1e 100644
+--- a/dns.c
++++ b/dns.c
+@@ -1,4 +1,4 @@
+-/* $OpenBSD: dns.c,v 1.26 2010/02/26 20:29:54 djm Exp $ */
++/* $OpenBSD: dns.c,v 1.27 2010/08/31 11:54:45 djm Exp $ */
+ 
+ /*
+  * Copyright (c) 2003 Wesley Griffin. All rights reserved.
+@@ -86,6 +86,7 @@ dns_read_key(u_int8_t *algorithm, u_int8_t *digest_type,
+ 	case KEY_DSA:
+ 		*algorithm = SSHFP_KEY_DSA;
+ 		break;
++	/* XXX KEY_ECDSA */
+ 	default:
+ 		*algorithm = SSHFP_KEY_RESERVED; /* 0 */
+ 	}
+diff --git a/kex.c b/kex.c
+index f09199f..b322b21 100644
+--- a/kex.c
++++ b/kex.c
+@@ -81,7 +81,10 @@ kex_names_valid(const char *names)
+ 	    	if (strcmp(p, KEX_DHGEX_SHA256) != 0 &&
+ 		    strcmp(p, KEX_DHGEX_SHA1) != 0 &&
+ 		    strcmp(p, KEX_DH14) != 0 &&
+-		    strcmp(p, KEX_DH1) != 0 ) {
++		    strcmp(p, KEX_DH1) != 0 &&
++		    (strncmp(p, KEX_ECDH_SHA2_STEM,
++		    sizeof(KEX_ECDH_SHA2_STEM) - 1) != 0 ||
++		    kex_ecdh_name_to_nid(p) == -1)) {
+ 			error("Unsupported KEX algorithm \"%.100s\"", p);
+ 			xfree(s);
+ 			return 0;
+@@ -89,7 +92,10 @@ kex_names_valid(const char *names)
+ 		if (FIPS_mode()) {
+ 			if (strcmp(p, KEX_DHGEX_SHA256) != 0 &&
+ 			    strcmp(p, KEX_DHGEX_SHA1) != 0 &&
+-			    strcmp(p, KEX_DH14) != 0 ) {
++			    strcmp(p, KEX_DH14) != 0 &&
++			    (strncmp(p, KEX_ECDH_SHA2_STEM,
++			    sizeof(KEX_ECDH_SHA2_STEM) - 1) != 0 ||
++			    kex_ecdh_name_to_nid(p) == -1)) {
+ 				error("\"%.100s\" is not allowed in FIPS mode", p);
+ 				xfree(s);
+ 				return 0;
+@@ -376,6 +382,10 @@ choose_kex(Kex *k, char *client, char *server)
+ 	} else if (strcmp(k->name, KEX_DHGEX_SHA256) == 0) {
+ 		k->kex_type = KEX_DH_GEX_SHA256;
+ 		k->evp_md = evp_ssh_sha256();
++	} else if (strncmp(k->name, KEX_ECDH_SHA2_STEM,
++	    sizeof(KEX_ECDH_SHA2_STEM) - 1) == 0) {
++ 		k->kex_type = KEX_ECDH_SHA2;
++		k->evp_md = kex_ecdh_name_to_evpmd(k->name);
+ #endif
+ #ifdef GSSAPI
+ 	} else if (strncmp(k->name, KEX_GSS_GEX_SHA1_ID,
+@@ -617,11 +627,11 @@ derive_ssh1_session_id(BIGNUM *host_modulus, BIGNUM *server_modulus,
+ 	memset(&md, 0, sizeof(md));
+ }
+ 
+-#if defined(DEBUG_KEX) || defined(DEBUG_KEXDH)
++#if defined(DEBUG_KEX) || defined(DEBUG_KEXDH) || defined(DEBUG_KEXECDH)
+ void
+ dump_digest(char *msg, u_char *digest, int len)
+ {
+-	u_int i;
++	int i;
+ 
+ 	fprintf(stderr, "%s\n", msg);
+ 	for (i = 0; i < len; i++) {
+diff --git a/kex.h b/kex.h
+index 314d6fd..79a9f60 100644
+--- a/kex.h
++++ b/kex.h
+@@ -29,6 +29,9 @@
+ #include <signal.h>
+ #include <openssl/evp.h>
+ #include <openssl/hmac.h>
++#ifdef OPENSSL_HAS_ECC
++#include <openssl/ec.h>
++#endif
+ 
+ #define KEX_COOKIE_LEN	16
+ 
+@@ -36,6 +39,8 @@
+ #define	KEX_DH14		"diffie-hellman-group14-sha1"
+ #define	KEX_DHGEX_SHA1		"diffie-hellman-group-exchange-sha1"
+ #define	KEX_DHGEX_SHA256	"diffie-hellman-group-exchange-sha256"
++/* The following represents the family of ECDH methods */
++#define	KEX_ECDH_SHA2_STEM	"ecdh-sha2-"
+ 
+ #define COMP_NONE	0
+ #define COMP_ZLIB	1
+@@ -66,6 +71,7 @@ enum kex_exchange {
+ 	KEX_DH_GRP14_SHA1,
+ 	KEX_DH_GEX_SHA1,
+ 	KEX_DH_GEX_SHA256,
++	KEX_ECDH_SHA2,
+ 	KEX_GSS_GRP1_SHA1,
+ 	KEX_GSS_GRP14_SHA1,
+ 	KEX_GSS_GEX_SHA1,
+@@ -141,6 +147,8 @@ struct Kex {
+ 
+ int	 kex_names_valid(const char *);
+ 
++int	 kex_names_valid(const char *);
++
+ Kex	*kex_setup(char *[PROPOSAL_MAX]);
+ void	 kex_finish(Kex *);
+ 
+@@ -154,6 +162,8 @@ void	 kexdh_client(Kex *);
+ void	 kexdh_server(Kex *);
+ void	 kexgex_client(Kex *);
+ void	 kexgex_server(Kex *);
++void	 kexecdh_client(Kex *);
++void	 kexecdh_server(Kex *);
+ 
+ #ifdef GSSAPI
+ void	kexgss_client(Kex *);
+@@ -169,11 +179,22 @@ void
+ kexgex_hash(const EVP_MD *, char *, char *, char *, int, char *,
+     int, u_char *, int, int, int, int, BIGNUM *, BIGNUM *, BIGNUM *,
+     BIGNUM *, BIGNUM *, u_char **, u_int *);
++#ifdef OPENSSL_HAS_ECC
++void
++kex_ecdh_hash(const EVP_MD *, const EC_GROUP *, char *, char *, char *, int,
++    char *, int, u_char *, int, const EC_POINT *, const EC_POINT *,
++    const BIGNUM *, u_char **, u_int *);
++int	kex_ecdh_name_to_nid(const char *);
++const EVP_MD *kex_ecdh_name_to_evpmd(const char *);
++#else
++# define kex_ecdh_name_to_nid(x) (-1)
++# define kex_ecdh_name_to_evpmd(x) (NULL)
++#endif
+ 
+ void
+ derive_ssh1_session_id(BIGNUM *, BIGNUM *, u_int8_t[8], u_int8_t[16]);
+ 
+-#if defined(DEBUG_KEX) || defined(DEBUG_KEXDH)
++#if defined(DEBUG_KEX) || defined(DEBUG_KEXDH) || defined(DEBUG_KEXECDH)
+ void	dump_digest(char *, u_char *, int);
+ #endif
+ 
+diff --git a/kexecdh.c b/kexecdh.c
+new file mode 100644
+index 0000000..f13f69d
+--- /dev/null
++++ b/kexecdh.c
+@@ -0,0 +1,117 @@
++/* $OpenBSD: kexecdh.c,v 1.3 2010/09/22 05:01:29 djm Exp $ */
++/*
++ * Copyright (c) 2001 Markus Friedl.  All rights reserved.
++ * Copyright (c) 2010 Damien Miller.  All rights reserved.
++ *
++ * Redistribution and use in source and binary forms, with or without
++ * modification, are permitted provided that the following conditions
++ * are met:
++ * 1. Redistributions of source code must retain the above copyright
++ *    notice, this list of conditions and the following disclaimer.
++ * 2. Redistributions in binary form must reproduce the above copyright
++ *    notice, this list of conditions and the following disclaimer in the
++ *    documentation and/or other materials provided with the distribution.
++ *
++ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
++ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
++ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
++ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
++ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
++ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
++ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
++ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
++ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
++ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
++ */
++
++#include "includes.h"
++
++#ifdef OPENSSL_HAS_ECC
++
++#include <sys/types.h>
++
++#include <signal.h>
++#include <string.h>
++
++#include <openssl/bn.h>
++#include <openssl/evp.h>
++#include <openssl/ec.h>
++#include <openssl/ecdh.h>
++
++#include "buffer.h"
++#include "ssh2.h"
++#include "key.h"
++#include "cipher.h"
++#include "kex.h"
++#include "log.h"
++
++int
++kex_ecdh_name_to_nid(const char *kexname)
++{
++	if (strlen(kexname) < sizeof(KEX_ECDH_SHA2_STEM) - 1)
++		fatal("%s: kexname too short \"%s\"", __func__, kexname);
++	return key_curve_name_to_nid(kexname + sizeof(KEX_ECDH_SHA2_STEM) - 1);
++}
++
++const EVP_MD *
++kex_ecdh_name_to_evpmd(const char *kexname)
++{
++	int nid = kex_ecdh_name_to_nid(kexname);
++
++	if (nid == -1)
++		fatal("%s: unsupported ECDH curve \"%s\"", __func__, kexname);
++	return key_ec_nid_to_evpmd(nid);
++}
++
++void
++kex_ecdh_hash(
++    const EVP_MD *evp_md,
++    const EC_GROUP *ec_group,
++    char *client_version_string,
++    char *server_version_string,
++    char *ckexinit, int ckexinitlen,
++    char *skexinit, int skexinitlen,
++    u_char *serverhostkeyblob, int sbloblen,
++    const EC_POINT *client_dh_pub,
++    const EC_POINT *server_dh_pub,
++    const BIGNUM *shared_secret,
++    u_char **hash, u_int *hashlen)
++{
++	Buffer b;
++	EVP_MD_CTX md;
++	static u_char digest[EVP_MAX_MD_SIZE];
++
++	buffer_init(&b);
++	buffer_put_cstring(&b, client_version_string);
++	buffer_put_cstring(&b, server_version_string);
++
++	/* kexinit messages: fake header: len+SSH2_MSG_KEXINIT */
++	buffer_put_int(&b, ckexinitlen+1);
++	buffer_put_char(&b, SSH2_MSG_KEXINIT);
++	buffer_append(&b, ckexinit, ckexinitlen);
++	buffer_put_int(&b, skexinitlen+1);
++	buffer_put_char(&b, SSH2_MSG_KEXINIT);
++	buffer_append(&b, skexinit, skexinitlen);
++
++	buffer_put_string(&b, serverhostkeyblob, sbloblen);
++	buffer_put_ecpoint(&b, ec_group, client_dh_pub);
++	buffer_put_ecpoint(&b, ec_group, server_dh_pub);
++	buffer_put_bignum2(&b, shared_secret);
++
++#ifdef DEBUG_KEX
++	buffer_dump(&b);
++#endif
++	EVP_DigestInit(&md, evp_md);
++	EVP_DigestUpdate(&md, buffer_ptr(&b), buffer_len(&b));
++	EVP_DigestFinal(&md, digest, NULL);
++
++	buffer_free(&b);
++
++#ifdef DEBUG_KEX
++	dump_digest("hash", digest, EVP_MD_size(evp_md));
++#endif
++	*hash = digest;
++	*hashlen = EVP_MD_size(evp_md);
++}
++
++#endif /* OPENSSL_HAS_ECC */
+diff --git a/kexecdhc.c b/kexecdhc.c
+new file mode 100644
+index 0000000..6424763
+--- /dev/null
++++ b/kexecdhc.c
+@@ -0,0 +1,169 @@
++/* $OpenBSD: kexecdhc.c,v 1.2 2010/09/22 05:01:29 djm Exp $ */
++/*
++ * Copyright (c) 2001 Markus Friedl.  All rights reserved.
++ * Copyright (c) 2010 Damien Miller.  All rights reserved.
++ *
++ * Redistribution and use in source and binary forms, with or without
++ * modification, are permitted provided that the following conditions
++ * are met:
++ * 1. Redistributions of source code must retain the above copyright
++ *    notice, this list of conditions and the following disclaimer.
++ * 2. Redistributions in binary form must reproduce the above copyright
++ *    notice, this list of conditions and the following disclaimer in the
++ *    documentation and/or other materials provided with the distribution.
++ *
++ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
++ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
++ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
++ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
++ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
++ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
++ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
++ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
++ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
++ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
++ */
++
++#include "includes.h"
++
++#include <sys/types.h>
++
++#include <stdio.h>
++#include <string.h>
++#include <signal.h>
++
++#include "xmalloc.h"
++#include "buffer.h"
++#include "key.h"
++#include "cipher.h"
++#include "kex.h"
++#include "log.h"
++#include "packet.h"
++#include "dh.h"
++#include "ssh2.h"
++
++#ifdef OPENSSL_HAS_ECC
++
++#include <openssl/ecdh.h>
++
++void
++kexecdh_client(Kex *kex)
++{
++	EC_KEY *client_key;
++	EC_POINT *server_public;
++	const EC_GROUP *group;
++	BIGNUM *shared_secret;
++	Key *server_host_key;
++	u_char *server_host_key_blob = NULL, *signature = NULL;
++	u_char *kbuf, *hash;
++	u_int klen, slen, sbloblen, hashlen;
++	int curve_nid;
++
++	if ((curve_nid = kex_ecdh_name_to_nid(kex->name)) == -1)
++		fatal("%s: unsupported ECDH curve \"%s\"", __func__, kex->name);
++	if ((client_key = EC_KEY_new_by_curve_name(curve_nid)) == NULL)
++		fatal("%s: EC_KEY_new_by_curve_name failed", __func__);
++	if (EC_KEY_generate_key(client_key) != 1)
++		fatal("%s: EC_KEY_generate_key failed", __func__);
++	group = EC_KEY_get0_group(client_key);
++
++	packet_start(SSH2_MSG_KEX_ECDH_INIT);
++	packet_put_ecpoint(group, EC_KEY_get0_public_key(client_key));
++	packet_send();
++	debug("sending SSH2_MSG_KEX_ECDH_INIT");
++
++#ifdef DEBUG_KEXECDH
++	fputs("client private key:\n", stderr);
++	key_dump_ec_key(client_key);
++#endif
++
++	debug("expecting SSH2_MSG_KEX_ECDH_REPLY");
++	packet_read_expect(SSH2_MSG_KEX_ECDH_REPLY);
++
++	/* hostkey */
++	server_host_key_blob = packet_get_string(&sbloblen);
++	server_host_key = key_from_blob(server_host_key_blob, sbloblen);
++	if (server_host_key == NULL)
++		fatal("cannot decode server_host_key_blob");
++	if (server_host_key->type != kex->hostkey_type)
++		fatal("type mismatch for decoded server_host_key_blob");
++	if (kex->verify_host_key == NULL)
++		fatal("cannot verify server_host_key");
++	if (kex->verify_host_key(server_host_key) == -1)
++		fatal("server_host_key verification failed");
++
++	/* Q_S, server public key */
++	if ((server_public = EC_POINT_new(group)) == NULL)
++		fatal("%s: EC_POINT_new failed", __func__);
++	packet_get_ecpoint(group, server_public);
++
++	if (key_ec_validate_public(group, server_public) != 0)
++		fatal("%s: invalid server public key", __func__);
++
++#ifdef DEBUG_KEXECDH
++	fputs("server public key:\n", stderr);
++	key_dump_ec_point(group, server_public);
++#endif
++
++	/* signed H */
++	signature = packet_get_string(&slen);
++	packet_check_eom();
++
++	klen = (EC_GROUP_get_degree(group) + 7) / 8;
++	kbuf = xmalloc(klen);
++	if (ECDH_compute_key(kbuf, klen, server_public,
++	    client_key, NULL) != (int)klen)
++		fatal("%s: ECDH_compute_key failed", __func__);
++
++#ifdef DEBUG_KEXECDH
++	dump_digest("shared secret", kbuf, klen);
++#endif
++	if ((shared_secret = BN_new()) == NULL)
++		fatal("%s: BN_new failed", __func__);
++	if (BN_bin2bn(kbuf, klen, shared_secret) == NULL)
++		fatal("%s: BN_bin2bn failed", __func__);
++	memset(kbuf, 0, klen);
++	xfree(kbuf);
++
++	/* calc and verify H */
++	kex_ecdh_hash(
++	    kex->evp_md,
++	    group,
++	    kex->client_version_string,
++	    kex->server_version_string,
++	    buffer_ptr(&kex->my), buffer_len(&kex->my),
++	    buffer_ptr(&kex->peer), buffer_len(&kex->peer),
++	    server_host_key_blob, sbloblen,
++	    EC_KEY_get0_public_key(client_key),
++	    server_public,
++	    shared_secret,
++	    &hash, &hashlen
++	);
++	xfree(server_host_key_blob);
++	EC_POINT_clear_free(server_public);
++	EC_KEY_free(client_key);
++
++	if (key_verify(server_host_key, signature, slen, hash, hashlen) != 1)
++		fatal("key_verify failed for server_host_key");
++	key_free(server_host_key);
++	xfree(signature);
++
++	/* save session id */
++	if (kex->session_id == NULL) {
++		kex->session_id_len = hashlen;
++		kex->session_id = xmalloc(kex->session_id_len);
++		memcpy(kex->session_id, hash, kex->session_id_len);
++	}
++
++	kex_derive_keys(kex, hash, hashlen, shared_secret);
++	BN_clear_free(shared_secret);
++	memset(hash, 0, hashlen);
++	kex_finish(kex);
++}
++#else /* OPENSSL_HAS_ECC */
++void
++kexecdh_client(Kex *kex)
++{
++	fatal("ECC support is not enabled");
++}
++#endif /* OPENSSL_HAS_ECC */
+diff --git a/kexecdhs.c b/kexecdhs.c
+new file mode 100644
+index 0000000..a4cf6e1
+--- /dev/null
++++ b/kexecdhs.c
+@@ -0,0 +1,174 @@
++/* $OpenBSD: kexecdhs.c,v 1.2 2010/09/22 05:01:29 djm Exp $ */
++/*
++ * Copyright (c) 2001 Markus Friedl.  All rights reserved.
++ * Copyright (c) 2010 Damien Miller.  All rights reserved.
++ *
++ * Redistribution and use in source and binary forms, with or without
++ * modification, are permitted provided that the following conditions
++ * are met:
++ * 1. Redistributions of source code must retain the above copyright
++ *    notice, this list of conditions and the following disclaimer.
++ * 2. Redistributions in binary form must reproduce the above copyright
++ *    notice, this list of conditions and the following disclaimer in the
++ *    documentation and/or other materials provided with the distribution.
++ *
++ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
++ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
++ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
++ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
++ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
++ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
++ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
++ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
++ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
++ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
++ */
++
++#include "includes.h"
++
++#include <sys/types.h>
++#include <string.h>
++#include <signal.h>
++
++#include "xmalloc.h"
++#include "buffer.h"
++#include "key.h"
++#include "cipher.h"
++#include "kex.h"
++#include "log.h"
++#include "packet.h"
++#include "dh.h"
++#include "ssh2.h"
++#ifdef GSSAPI
++#include "ssh-gss.h"
++#endif
++#include "monitor_wrap.h"
++
++#ifdef OPENSSL_HAS_ECC
++
++#include <openssl/ecdh.h>
++
++void
++kexecdh_server(Kex *kex)
++{
++	EC_POINT *client_public;
++	EC_KEY *server_key;
++	const EC_GROUP *group;
++	BIGNUM *shared_secret;
++	Key *server_host_private, *server_host_public;
++	u_char *server_host_key_blob = NULL, *signature = NULL;
++	u_char *kbuf, *hash;
++	u_int klen, slen, sbloblen, hashlen;
++	int curve_nid;
++
++	if ((curve_nid = kex_ecdh_name_to_nid(kex->name)) == -1)
++		fatal("%s: unsupported ECDH curve \"%s\"", __func__, kex->name);
++	if ((server_key = EC_KEY_new_by_curve_name(curve_nid)) == NULL)
++		fatal("%s: EC_KEY_new_by_curve_name failed", __func__);
++	if (EC_KEY_generate_key(server_key) != 1)
++		fatal("%s: EC_KEY_generate_key failed", __func__);
++	group = EC_KEY_get0_group(server_key);
++
++#ifdef DEBUG_KEXECDH
++	fputs("server private key:\n", stderr);
++	key_dump_ec_key(server_key);
++#endif
++
++	if (kex->load_host_public_key == NULL ||
++	    kex->load_host_private_key == NULL)
++		fatal("Cannot load hostkey");
++	server_host_public = kex->load_host_public_key(kex->hostkey_type);
++	if (server_host_public == NULL)
++		fatal("Unsupported hostkey type %d", kex->hostkey_type);
++	server_host_private = kex->load_host_private_key(kex->hostkey_type);
++	if (server_host_private == NULL)
++		fatal("Missing private key for hostkey type %d",
++		    kex->hostkey_type);
++
++	debug("expecting SSH2_MSG_KEX_ECDH_INIT");
++	packet_read_expect(SSH2_MSG_KEX_ECDH_INIT);
++	if ((client_public = EC_POINT_new(group)) == NULL)
++		fatal("%s: EC_POINT_new failed", __func__);
++	packet_get_ecpoint(group, client_public);
++	packet_check_eom();
++
++	if (key_ec_validate_public(group, client_public) != 0)
++		fatal("%s: invalid client public key", __func__);
++
++#ifdef DEBUG_KEXECDH
++	fputs("client public key:\n", stderr);
++	key_dump_ec_point(group, client_public);
++#endif
++
++	/* Calculate shared_secret */
++	klen = (EC_GROUP_get_degree(group) + 7) / 8;
++	kbuf = xmalloc(klen);
++	if (ECDH_compute_key(kbuf, klen, client_public,
++	    server_key, NULL) != (int)klen)
++		fatal("%s: ECDH_compute_key failed", __func__);
++
++#ifdef DEBUG_KEXDH
++	dump_digest("shared secret", kbuf, klen);
++#endif
++	if ((shared_secret = BN_new()) == NULL)
++		fatal("%s: BN_new failed", __func__);
++	if (BN_bin2bn(kbuf, klen, shared_secret) == NULL)
++		fatal("%s: BN_bin2bn failed", __func__);
++	memset(kbuf, 0, klen);
++	xfree(kbuf);
++
++	/* calc H */
++	key_to_blob(server_host_public, &server_host_key_blob, &sbloblen);
++	kex_ecdh_hash(
++	    kex->evp_md,
++	    group,
++	    kex->client_version_string,
++	    kex->server_version_string,
++	    buffer_ptr(&kex->peer), buffer_len(&kex->peer),
++	    buffer_ptr(&kex->my), buffer_len(&kex->my),
++	    server_host_key_blob, sbloblen,
++	    client_public,
++	    EC_KEY_get0_public_key(server_key),
++	    shared_secret,
++	    &hash, &hashlen
++	);
++	EC_POINT_clear_free(client_public);
++
++	/* save session id := H */
++	if (kex->session_id == NULL) {
++		kex->session_id_len = hashlen;
++		kex->session_id = xmalloc(kex->session_id_len);
++		memcpy(kex->session_id, hash, kex->session_id_len);
++	}
++
++	/* sign H */
++	if (PRIVSEP(key_sign(server_host_private, &signature, &slen,
++	    hash, hashlen)) < 0)
++		fatal("kexdh_server: key_sign failed");
++
++	/* destroy_sensitive_data(); */
++
++	/* send server hostkey, ECDH pubkey 'Q_S' and signed H */
++	packet_start(SSH2_MSG_KEX_ECDH_REPLY);
++	packet_put_string(server_host_key_blob, sbloblen);
++	packet_put_ecpoint(group, EC_KEY_get0_public_key(server_key));
++	packet_put_string(signature, slen);
++	packet_send();
++
++	xfree(signature);
++	xfree(server_host_key_blob);
++	/* have keys, free server key */
++	EC_KEY_free(server_key);
++
++	kex_derive_keys(kex, hash, hashlen, shared_secret);
++	BN_clear_free(shared_secret);
++	memset(hash, 0, hashlen);
++	kex_finish(kex);
++}
++#else /* OPENSSL_HAS_ECC */
++void
++kexecdh_server(Kex *kex)
++{
++	fatal("ECC support is not enabled");
++}
++#endif /* OPENSSL_HAS_ECC */
+diff --git a/key.c b/key.c
+index ad15ce9..dbc960b 100644
+--- a/key.c
++++ b/key.c
+@@ -78,6 +78,8 @@ key_new(int type)
+ 	DSA *dsa;
+ 	k = xcalloc(1, sizeof(*k));
+ 	k->type = type;
++	k->ecdsa = NULL;
++	k->ecdsa_nid = -1;
+ 	k->dsa = NULL;
+ 	k->rsa = NULL;
+ 	k->cert = NULL;
+@@ -109,6 +111,12 @@ key_new(int type)
+ 			fatal("key_new: BN_new failed");
+ 		k->dsa = dsa;
+ 		break;
++#ifdef OPENSSL_HAS_ECC
++	case KEY_ECDSA:
++	case KEY_ECDSA_CERT:
++		/* Cannot do anything until we know the group */
++		break;
++#endif
+ 	case KEY_UNSPEC:
+ 		break;
+ 	default:
+@@ -197,6 +205,10 @@ key_add_private(Key *k)
+ 		if ((k->dsa->priv_key = BN_new()) == NULL)
+ 			fatal("key_new_private: BN_new failed");
+ 		break;
++	case KEY_ECDSA:
++	case KEY_ECDSA_CERT:
++		/* Cannot do anything until we know the group */
++		break;
+ 	case KEY_UNSPEC:
+ 		break;
+ 	default:
+@@ -252,6 +264,14 @@ key_free(Key *k)
+ 			DSA_free(k->dsa);
+ 		k->dsa = NULL;
+ 		break;
++#ifdef OPENSSL_HAS_ECC
++	case KEY_ECDSA:
++	case KEY_ECDSA_CERT:
++		if (k->ecdsa != NULL)
++			EC_KEY_free(k->ecdsa);
++		k->ecdsa = NULL;
++		break;
++#endif
+ 	case KEY_UNSPEC:
+ 		break;
+ 	default:
+@@ -302,6 +322,10 @@ cert_compare(struct KeyCert *a, struct KeyCert *b)
+ int
+ key_equal_public(const Key *a, const Key *b)
+ {
++#ifdef OPENSSL_HAS_ECC
++	BN_CTX *bnctx;
++#endif
++
+ 	if (a == NULL || b == NULL ||
+ 	    key_type_plain(a->type) != key_type_plain(b->type))
+ 		return 0;
+@@ -322,6 +346,26 @@ key_equal_public(const Key *a, const Key *b)
+ 		    BN_cmp(a->dsa->q, b->dsa->q) == 0 &&
+ 		    BN_cmp(a->dsa->g, b->dsa->g) == 0 &&
+ 		    BN_cmp(a->dsa->pub_key, b->dsa->pub_key) == 0;
++#ifdef OPENSSL_HAS_ECC
++	case KEY_ECDSA_CERT:
++	case KEY_ECDSA:
++		if (a->ecdsa == NULL || b->ecdsa == NULL ||
++		    EC_KEY_get0_public_key(a->ecdsa) == NULL ||
++		    EC_KEY_get0_public_key(b->ecdsa) == NULL)
++			return 0;
++		if ((bnctx = BN_CTX_new()) == NULL)
++			fatal("%s: BN_CTX_new failed", __func__);
++		if (EC_GROUP_cmp(EC_KEY_get0_group(a->ecdsa),
++		    EC_KEY_get0_group(b->ecdsa), bnctx) != 0 ||
++		    EC_POINT_cmp(EC_KEY_get0_group(a->ecdsa),
++		    EC_KEY_get0_public_key(a->ecdsa),
++		    EC_KEY_get0_public_key(b->ecdsa), bnctx) != 0) {
++			BN_CTX_free(bnctx);
++			return 0;
++		}
++		BN_CTX_free(bnctx);
++		return 1;
++#endif /* OPENSSL_HAS_ECC */
+ 	default:
+ 		fatal("key_equal: bad key type %d", a->type);
+ 	}
+@@ -373,12 +417,14 @@ key_fingerprint_raw(Key *k, enum fp_type dgst_type, u_int *dgst_raw_length)
+ 		BN_bn2bin(k->rsa->e, blob + nlen);
+ 		break;
+ 	case KEY_DSA:
++	case KEY_ECDSA:
+ 	case KEY_RSA:
+ 		key_to_blob(k, &blob, &len);
+ 		break;
+ 	case KEY_DSA_CERT_V00:
+ 	case KEY_RSA_CERT_V00:
+ 	case KEY_DSA_CERT:
++	case KEY_ECDSA_CERT:
+ 	case KEY_RSA_CERT:
+ 		/* We want a fingerprint of the _key_ not of the cert */
+ 		otype = k->type;
+@@ -676,6 +722,9 @@ key_read(Key *ret, char **cpp)
+ 	int len, n, type;
+ 	u_int bits;
+ 	u_char *blob;
++#ifdef OPENSSL_HAS_ECC
++	int curve_nid = -1;
++#endif
+ 
+ 	cp = *cpp;
+ 
+@@ -699,9 +748,11 @@ key_read(Key *ret, char **cpp)
+ 	case KEY_UNSPEC:
+ 	case KEY_RSA:
+ 	case KEY_DSA:
++	case KEY_ECDSA:
+ 	case KEY_DSA_CERT_V00:
+ 	case KEY_RSA_CERT_V00:
+ 	case KEY_DSA_CERT:
++	case KEY_ECDSA_CERT:
+ 	case KEY_RSA_CERT:
+ 		space = strchr(cp, ' ');
+ 		if (space == NULL) {
+@@ -710,6 +761,13 @@ key_read(Key *ret, char **cpp)
+ 		}
+ 		*space = '\0';
+ 		type = key_type_from_name(cp);
++#ifdef OPENSSL_HAS_ECC
++		if (key_type_plain(type) == KEY_ECDSA &&
++		    (curve_nid = key_ecdsa_nid_from_name(cp)) == -1) {
++			debug("key_read: invalid curve");
++			return -1;
++		}
++#endif
+ 		*space = ' ';
+ 		if (type == KEY_UNSPEC) {
+ 			debug3("key_read: missing keytype");
+@@ -746,6 +804,14 @@ key_read(Key *ret, char **cpp)
+ 			key_free(k);
+ 			return -1;
+ 		}
++#ifdef OPENSSL_HAS_ECC
++		if (key_type_plain(type) == KEY_ECDSA &&
++		    curve_nid != k->ecdsa_nid) {
++			error("key_read: type mismatch: EC curve mismatch");
++			key_free(k);
++			return -1;
++		}
++#endif
+ /*XXXX*/
+ 		if (key_is_cert(ret)) {
+ 			if (!key_is_cert(k)) {
+@@ -776,6 +842,19 @@ key_read(Key *ret, char **cpp)
+ 			DSA_print_fp(stderr, ret->dsa, 8);
+ #endif
+ 		}
++#ifdef OPENSSL_HAS_ECC
++		if (key_type_plain(ret->type) == KEY_ECDSA) {
++			if (ret->ecdsa != NULL)
++				EC_KEY_free(ret->ecdsa);
++			ret->ecdsa = k->ecdsa;
++			ret->ecdsa_nid = k->ecdsa_nid;
++			k->ecdsa = NULL;
++			k->ecdsa_nid = -1;
++#ifdef DEBUG_PK
++			key_dump_ec_key(ret->ecdsa);
++#endif
++		}
++#endif
+ 		success = 1;
+ /*XXXX*/
+ 		key_free(k);
+@@ -832,6 +911,13 @@ key_write(const Key *key, FILE *f)
+ 		if (key->dsa == NULL)
+ 			return 0;
+ 		break;
++#ifdef OPENSSL_HAS_ECC
++	case KEY_ECDSA:
++	case KEY_ECDSA_CERT:
++		if (key->ecdsa == NULL)
++			return 0;
++		break;
++#endif
+ 	case KEY_RSA:
+ 	case KEY_RSA_CERT_V00:
+ 	case KEY_RSA_CERT:
+@@ -865,6 +951,10 @@ key_type(const Key *k)
+ 		return "RSA";
+ 	case KEY_DSA:
+ 		return "DSA";
++#ifdef OPENSSL_HAS_ECC
++	case KEY_ECDSA:
++		return "ECDSA";
++#endif
+ 	case KEY_RSA_CERT_V00:
+ 		return "RSA-CERT-V00";
+ 	case KEY_DSA_CERT_V00:
+@@ -873,6 +963,10 @@ key_type(const Key *k)
+ 		return "RSA-CERT";
+ 	case KEY_DSA_CERT:
+ 		return "DSA-CERT";
++#ifdef OPENSSL_HAS_ECC
++	case KEY_ECDSA_CERT:
++		return "ECDSA-CERT";
++#endif
+ 	}
+ 	return "unknown";
+ }
+@@ -890,10 +984,10 @@ key_cert_type(const Key *k)
+ 	}
+ }
+ 
+-const char *
+-key_ssh_name(const Key *k)
++static const char *
++key_ssh_name_from_type_nid(int type, int nid)
+ {
+-	switch (k->type) {
++	switch (type) {
+ 	case KEY_RSA:
+ 		return "ssh-rsa";
+ 	case KEY_DSA:
+@@ -906,10 +1000,53 @@ key_ssh_name(const Key *k)
+ 		return "ssh-rsa-cert-v01 at openssh.com";
+ 	case KEY_DSA_CERT:
+ 		return "ssh-dss-cert-v01 at openssh.com";
++#ifdef OPENSSL_HAS_ECC
++	case KEY_ECDSA:
++		switch (nid) {
++		case NID_X9_62_prime256v1:
++			return "ecdsa-sha2-nistp256";
++		case NID_secp384r1:
++			return "ecdsa-sha2-nistp384";
++# ifdef OPENSSL_HAS_NISTP521
++		case NID_secp521r1:
++			return "ecdsa-sha2-nistp521";
++# endif
++		default:
++			break;
++		}
++		break;
++	case KEY_ECDSA_CERT:
++		switch (nid) {
++		case NID_X9_62_prime256v1:
++			return "ecdsa-sha2-nistp256-cert-v01 at openssh.com";
++		case NID_secp384r1:
++			return "ecdsa-sha2-nistp384-cert-v01 at openssh.com";
++# ifdef OPENSSL_HAS_NISTP521
++		case NID_secp521r1:
++			return "ecdsa-sha2-nistp521-cert-v01 at openssh.com";
++# endif
++		default:
++			break;
++		}
++		break;
++#endif /* OPENSSL_HAS_ECC */
+ 	}
+ 	return "ssh-unknown";
+ }
+ 
++const char *
++key_ssh_name(const Key *k)
++{
++	return key_ssh_name_from_type_nid(k->type, k->ecdsa_nid);
++}
++
++const char *
++key_ssh_name_plain(const Key *k)
++{
++	return key_ssh_name_from_type_nid(key_type_plain(k->type),
++	    k->ecdsa_nid);
++}
++
+ u_int
+ key_size(const Key *k)
+ {
+@@ -923,6 +1060,11 @@ key_size(const Key *k)
+ 	case KEY_DSA_CERT_V00:
+ 	case KEY_DSA_CERT:
+ 		return BN_num_bits(k->dsa->p);
++#ifdef OPENSSL_HAS_ECC
++	case KEY_ECDSA:
++	case KEY_ECDSA_CERT:
++		return key_curve_nid_to_bits(k->ecdsa_nid);
++#endif
+ 	}
+ 	return 0;
+ }
+@@ -955,6 +1097,77 @@ dsa_generate_private_key(u_int bits)
+ 	return private;
+ }
+ 
++int
++key_ecdsa_bits_to_nid(int bits)
++{
++	switch (bits) {
++#ifdef OPENSSL_HAS_ECC
++	case 256:
++		return NID_X9_62_prime256v1;
++	case 384:
++		return NID_secp384r1;
++# ifdef OPENSSL_HAS_NISTP521
++	case 521:
++		return NID_secp521r1;
++# endif
++#endif
++	default:
++		return -1;
++	}
++}
++
++#ifdef OPENSSL_HAS_ECC
++/*
++ * This is horrid, but OpenSSL's PEM_read_PrivateKey seems not to restore
++ * the EC_GROUP nid when loading a key...
++ */
++int
++key_ecdsa_group_to_nid(const EC_GROUP *g)
++{
++	EC_GROUP *eg;
++	int nids[] = {
++		NID_X9_62_prime256v1,
++		NID_secp384r1,
++# ifdef OPENSSL_HAS_NISTP521
++		NID_secp521r1,
++# endif
++		-1
++	};
++	u_int i;
++	BN_CTX *bnctx;
++
++	if ((bnctx = BN_CTX_new()) == NULL)
++		fatal("%s: BN_CTX_new() failed", __func__);
++	for (i = 0; nids[i] != -1; i++) {
++		if ((eg = EC_GROUP_new_by_curve_name(nids[i])) == NULL)
++			fatal("%s: EC_GROUP_new_by_curve_name failed",
++			    __func__);
++		if (EC_GROUP_cmp(g, eg, bnctx) == 0) {
++			EC_GROUP_free(eg);
++			break;
++		}
++		EC_GROUP_free(eg);
++	}
++	BN_CTX_free(bnctx);
++	debug3("%s: nid = %d", __func__, nids[i]);
++	return nids[i];
++}
++
++static EC_KEY*
++ecdsa_generate_private_key(u_int bits, int *nid)
++{
++	EC_KEY *private;
++
++	if ((*nid = key_ecdsa_bits_to_nid(bits)) == -1)
++		fatal("%s: invalid key length", __func__);
++	if ((private = EC_KEY_new_by_curve_name(*nid)) == NULL)
++		fatal("%s: EC_KEY_new_by_curve_name failed", __func__);
++	if (EC_KEY_generate_key(private) != 1)
++		fatal("%s: EC_KEY_generate_key failed", __func__);
++	return private;
++}
++#endif /* OPENSSL_HAS_ECC */
++
+ Key *
+ key_generate(int type, u_int bits)
+ {
+@@ -963,6 +1176,11 @@ key_generate(int type, u_int bits)
+ 	case KEY_DSA:
+ 		k->dsa = dsa_generate_private_key(bits);
+ 		break;
++#ifdef OPENSSL_HAS_ECC
++	case KEY_ECDSA:
++		k->ecdsa = ecdsa_generate_private_key(bits, &k->ecdsa_nid);
++		break;
++#endif
+ 	case KEY_RSA:
+ 	case KEY_RSA1:
+ 		k->rsa = rsa_generate_private_key(bits);
+@@ -1039,6 +1257,18 @@ key_from_private(const Key *k)
+ 		    (BN_copy(n->dsa->pub_key, k->dsa->pub_key) == NULL))
+ 			fatal("key_from_private: BN_copy failed");
+ 		break;
++#ifdef OPENSSL_HAS_ECC
++	case KEY_ECDSA:
++	case KEY_ECDSA_CERT:
++		n = key_new(k->type);
++		n->ecdsa_nid = k->ecdsa_nid;
++		if ((n->ecdsa = EC_KEY_new_by_curve_name(k->ecdsa_nid)) == NULL)
++			fatal("%s: EC_KEY_new_by_curve_name failed", __func__);
++		if (EC_KEY_set_public_key(n->ecdsa,
++		    EC_KEY_get0_public_key(k->ecdsa)) != 1)
++			fatal("%s: EC_KEY_set_public_key failed", __func__);
++		break;
++#endif
+ 	case KEY_RSA:
+ 	case KEY_RSA1:
+ 	case KEY_RSA_CERT_V00:
+@@ -1070,6 +1300,16 @@ key_type_from_name(char *name)
+ 		return KEY_RSA;
+ 	} else if (strcmp(name, "ssh-dss") == 0) {
+ 		return KEY_DSA;
++#ifdef OPENSSL_HAS_ECC
++	} else if (strcmp(name, "ecdsa") == 0 ||
++	    strcmp(name, "ecdsa-sha2-nistp256") == 0 ||
++	    strcmp(name, "ecdsa-sha2-nistp384") == 0
++# ifdef OPENSSL_HAS_NISTP521
++	    || strcmp(name, "ecdsa-sha2-nistp521") == 0
++# endif
++	    ) {
++		return KEY_ECDSA;
++#endif
+ 	} else if (strcmp(name, "ssh-rsa-cert-v00 at openssh.com") == 0) {
+ 		return KEY_RSA_CERT_V00;
+ 	} else if (strcmp(name, "ssh-dss-cert-v00 at openssh.com") == 0) {
+@@ -1078,6 +1318,15 @@ key_type_from_name(char *name)
+ 		return KEY_RSA_CERT;
+ 	} else if (strcmp(name, "ssh-dss-cert-v01 at openssh.com") == 0) {
+ 		return KEY_DSA_CERT;
++#ifdef OPENSSL_HAS_ECC
++	} else if (strcmp(name, "ecdsa-sha2-nistp256-cert-v01 at openssh.com") == 0 ||
++	    strcmp(name, "ecdsa-sha2-nistp384-cert-v01 at openssh.com") == 0
++# ifdef OPENSSL_HAS_NISTP521
++	    || strcmp(name, "ecdsa-sha2-nistp521-cert-v01 at openssh.com") == 0
++# endif
++	    ) {
++		return KEY_ECDSA_CERT;
++#endif
+ 	} else if (strcmp(name, "null") == 0) {
+ 		return KEY_NULL;
+ 	}
+@@ -1086,6 +1335,27 @@ key_type_from_name(char *name)
+ }
+ 
+ int
++key_ecdsa_nid_from_name(const char *name)
++{
++#ifdef OPENSSL_HAS_ECC
++	if (strcmp(name, "ecdsa-sha2-nistp256") == 0 ||
++	    strcmp(name, "ecdsa-sha2-nistp256-cert-v01 at openssh.com") == 0)
++		return NID_X9_62_prime256v1;
++	if (strcmp(name, "ecdsa-sha2-nistp384") == 0 ||
++	    strcmp(name, "ecdsa-sha2-nistp384-cert-v01 at openssh.com") == 0)
++		return NID_secp384r1;
++# ifdef OPENSSL_HAS_NISTP521
++	if (strcmp(name, "ecdsa-sha2-nistp521") == 0 ||
++	    strcmp(name, "ecdsa-sha2-nistp521-cert-v01 at openssh.com") == 0)
++		return NID_secp521r1;
++# endif
++#endif /* OPENSSL_HAS_ECC */
++
++	debug2("%s: unknown/non-ECDSA key type '%s'", __func__, name);
++	return -1;
++}
++
++int
+ key_names_valid2(const char *names)
+ {
+ 	char *s, *cp, *p;
+@@ -1211,7 +1481,8 @@ cert_parse(Buffer *b, Key *key, const u_char *blob, u_int blen)
+ 		goto out;
+ 	}
+ 	if (key->cert->signature_key->type != KEY_RSA &&
+-	    key->cert->signature_key->type != KEY_DSA) {
++	    key->cert->signature_key->type != KEY_DSA &&
++	    key->cert->signature_key->type != KEY_ECDSA) {
+ 		error("%s: Invalid signature key type %s (%d)", __func__,
+ 		    key_type(key->cert->signature_key),
+ 		    key->cert->signature_key->type);
+@@ -1252,8 +1523,12 @@ key_from_blob(const u_char *blob, u_int blen)
+ {
+ 	Buffer b;
+ 	int rlen, type;
+-	char *ktype = NULL;
++	char *ktype = NULL, *curve = NULL;
+ 	Key *key = NULL;
++#ifdef OPENSSL_HAS_ECC
++	EC_POINT *q = NULL;
++	int nid = -1;
++#endif
+ 
+ #ifdef DEBUG_PK
+ 	dump_base64(stderr, blob, blen);
+@@ -1266,6 +1541,10 @@ key_from_blob(const u_char *blob, u_int blen)
+ 	}
+ 
+ 	type = key_type_from_name(ktype);
++#ifdef OPENSSL_HAS_ECC
++	if (key_type_plain(type) == KEY_ECDSA)
++		nid = key_ecdsa_nid_from_name(ktype);
++#endif
+ 
+ 	switch (type) {
+ 	case KEY_RSA_CERT:
+@@ -1303,6 +1582,43 @@ key_from_blob(const u_char *blob, u_int blen)
+ 		DSA_print_fp(stderr, key->dsa, 8);
+ #endif
+ 		break;
++#ifdef OPENSSL_HAS_ECC
++	case KEY_ECDSA_CERT:
++		(void)buffer_get_string_ptr_ret(&b, NULL); /* Skip nonce */
++		/* FALLTHROUGH */
++	case KEY_ECDSA:
++		key = key_new(type);
++		key->ecdsa_nid = nid;
++		if ((curve = buffer_get_string_ret(&b, NULL)) == NULL) {
++			error("key_from_blob: can't read ecdsa curve");
++			goto badkey;
++		}
++		if (key->ecdsa_nid != key_curve_name_to_nid(curve)) {
++			error("key_from_blob: ecdsa curve doesn't match type");
++			goto badkey;
++		}
++		if (key->ecdsa != NULL)
++			EC_KEY_free(key->ecdsa);
++		if ((key->ecdsa = EC_KEY_new_by_curve_name(key->ecdsa_nid))
++		    == NULL)
++			fatal("key_from_blob: EC_KEY_new_by_curve_name failed");
++		if ((q = EC_POINT_new(EC_KEY_get0_group(key->ecdsa))) == NULL)
++			fatal("key_from_blob: EC_POINT_new failed");
++		if (buffer_get_ecpoint_ret(&b, EC_KEY_get0_group(key->ecdsa),
++		    q) == -1) {
++			error("key_from_blob: can't read ecdsa key point");
++			goto badkey;
++		}
++		if (key_ec_validate_public(EC_KEY_get0_group(key->ecdsa),
++		    q) != 0)
++			goto badkey;
++		if (EC_KEY_set_public_key(key->ecdsa, q) != 1)
++			fatal("key_from_blob: EC_KEY_set_public_key failed");
++#ifdef DEBUG_PK
++		key_dump_ec_point(EC_KEY_get0_group(key->ecdsa), q);
++#endif
++		break;
++#endif /* OPENSSL_HAS_ECC */
+ 	case KEY_UNSPEC:
+ 		key = key_new(type);
+ 		break;
+@@ -1320,6 +1636,12 @@ key_from_blob(const u_char *blob, u_int blen)
+  out:
+ 	if (ktype != NULL)
+ 		xfree(ktype);
++	if (curve != NULL)
++		xfree(curve);
++#ifdef OPENSSL_HAS_ECC
++	if (q != NULL)
++		EC_POINT_free(q);
++#endif
+ 	buffer_free(&b);
+ 	return key;
+ }
+@@ -1339,6 +1661,7 @@ key_to_blob(const Key *key, u_char **blobp, u_int *lenp)
+ 	case KEY_DSA_CERT_V00:
+ 	case KEY_RSA_CERT_V00:
+ 	case KEY_DSA_CERT:
++	case KEY_ECDSA_CERT:
+ 	case KEY_RSA_CERT:
+ 		/* Use the existing blob */
+ 		buffer_append(&b, buffer_ptr(&key->cert->certblob),
+@@ -1351,6 +1674,14 @@ key_to_blob(const Key *key, u_char **blobp, u_int *lenp)
+ 		buffer_put_bignum2(&b, key->dsa->g);
+ 		buffer_put_bignum2(&b, key->dsa->pub_key);
+ 		break;
++#ifdef OPENSSL_HAS_ECC
++	case KEY_ECDSA:
++		buffer_put_cstring(&b, key_ssh_name(key));
++		buffer_put_cstring(&b, key_curve_nid_to_name(key->ecdsa_nid));
++		buffer_put_ecpoint(&b, EC_KEY_get0_group(key->ecdsa),
++		    EC_KEY_get0_public_key(key->ecdsa));
++		break;
++#endif
+ 	case KEY_RSA:
+ 		buffer_put_cstring(&b, key_ssh_name(key));
+ 		buffer_put_bignum2(&b, key->rsa->e);
+@@ -1384,6 +1715,11 @@ key_sign(
+ 	case KEY_DSA_CERT:
+ 	case KEY_DSA:
+ 		return ssh_dss_sign(key, sigp, lenp, data, datalen);
++#ifdef OPENSSL_HAS_ECC
++	case KEY_ECDSA_CERT:
++	case KEY_ECDSA:
++		return ssh_ecdsa_sign(key, sigp, lenp, data, datalen);
++#endif
+ 	case KEY_RSA_CERT_V00:
+ 	case KEY_RSA_CERT:
+ 	case KEY_RSA:
+@@ -1412,6 +1748,11 @@ key_verify(
+ 	case KEY_DSA_CERT:
+ 	case KEY_DSA:
+ 		return ssh_dss_verify(key, signature, signaturelen, data, datalen);
++#ifdef OPENSSL_HAS_ECC
++	case KEY_ECDSA_CERT:
++	case KEY_ECDSA:
++		return ssh_ecdsa_verify(key, signature, signaturelen, data, datalen);
++#endif
+ 	case KEY_RSA_CERT_V00:
+ 	case KEY_RSA_CERT:
+ 	case KEY_RSA:
+@@ -1431,7 +1772,9 @@ key_demote(const Key *k)
+ 	pk = xcalloc(1, sizeof(*pk));
+ 	pk->type = k->type;
+ 	pk->flags = k->flags;
++	pk->ecdsa_nid = k->ecdsa_nid;
+ 	pk->dsa = NULL;
++	pk->ecdsa = NULL;
+ 	pk->rsa = NULL;
+ 
+ 	switch (k->type) {
+@@ -1464,6 +1807,18 @@ key_demote(const Key *k)
+ 		if ((pk->dsa->pub_key = BN_dup(k->dsa->pub_key)) == NULL)
+ 			fatal("key_demote: BN_dup failed");
+ 		break;
++#ifdef OPENSSL_HAS_ECC
++	case KEY_ECDSA_CERT:
++		key_cert_copy(k, pk);
++		/* FALLTHROUGH */
++	case KEY_ECDSA:
++		if ((pk->ecdsa = EC_KEY_new_by_curve_name(pk->ecdsa_nid)) == NULL)
++			fatal("key_demote: EC_KEY_new_by_curve_name failed");
++		if (EC_KEY_set_public_key(pk->ecdsa,
++		    EC_KEY_get0_public_key(k->ecdsa)) != 1)
++			fatal("key_demote: EC_KEY_set_public_key failed");
++		break;
++#endif
+ 	default:
+ 		fatal("key_free: bad key type %d", k->type);
+ 		break;
+@@ -1502,6 +1857,7 @@ key_is_cert(const Key *k)
+ 	case KEY_DSA_CERT_V00:
+ 	case KEY_RSA_CERT:
+ 	case KEY_DSA_CERT:
++	case KEY_ECDSA_CERT:
+ 		return 1;
+ 	default:
+ 		return 0;
+@@ -1519,6 +1875,8 @@ key_type_plain(int type)
+ 	case KEY_DSA_CERT_V00:
+ 	case KEY_DSA_CERT:
+ 		return KEY_DSA;
++	case KEY_ECDSA_CERT:
++		return KEY_ECDSA;
+ 	default:
+ 		return type;
+ 	}
+@@ -1537,6 +1895,10 @@ key_to_certified(Key *k, int legacy)
+ 		k->cert = cert_new();
+ 		k->type = legacy ? KEY_DSA_CERT_V00 : KEY_DSA_CERT;
+ 		return 0;
++	case KEY_ECDSA:
++		k->cert = cert_new();
++		k->type = KEY_ECDSA_CERT;
++		return 0;
+ 	default:
+ 		error("%s: key has incorrect type %s", __func__, key_type(k));
+ 		return -1;
+@@ -1558,13 +1920,20 @@ key_drop_cert(Key *k)
+ 		cert_free(k->cert);
+ 		k->type = KEY_DSA;
+ 		return 0;
++	case KEY_ECDSA_CERT:
++		cert_free(k->cert);
++		k->type = KEY_ECDSA;
++		return 0;
+ 	default:
+ 		error("%s: key has incorrect type %s", __func__, key_type(k));
+ 		return -1;
+ 	}
+ }
+ 
+-/* Sign a KEY_RSA_CERT or KEY_DSA_CERT, (re-)generating the signed certblob */
++/*
++ * Sign a KEY_RSA_CERT, KEY_DSA_CERT or KEY_ECDSA_CERT, (re-)generating
++ * the signed certblob
++ */
+ int
+ key_certify(Key *k, Key *ca)
+ {
+@@ -1583,7 +1952,8 @@ key_certify(Key *k, Key *ca)
+ 		return -1;
+ 	}
+ 
+-	if (ca->type != KEY_RSA && ca->type != KEY_DSA) {
++	if (ca->type != KEY_RSA && ca->type != KEY_DSA &&
++	    ca->type != KEY_ECDSA) {
+ 		error("%s: CA key has unsupported type %s", __func__,
+ 		    key_type(ca));
+ 		return -1;
+@@ -1595,7 +1965,7 @@ key_certify(Key *k, Key *ca)
+ 	buffer_put_cstring(&k->cert->certblob, key_ssh_name(k));
+ 
+ 	/* -v01 certs put nonce first */
+-	if (k->type == KEY_DSA_CERT || k->type == KEY_RSA_CERT) {
++	if (!key_cert_is_legacy(k)) {
+ 		arc4random_buf(&nonce, sizeof(nonce));
+ 		buffer_put_string(&k->cert->certblob, nonce, sizeof(nonce));
+ 	}
+@@ -1608,6 +1978,15 @@ key_certify(Key *k, Key *ca)
+ 		buffer_put_bignum2(&k->cert->certblob, k->dsa->g);
+ 		buffer_put_bignum2(&k->cert->certblob, k->dsa->pub_key);
+ 		break;
++#ifdef OPENSSL_HAS_ECC
++	case KEY_ECDSA_CERT:
++		buffer_put_cstring(&k->cert->certblob,
++		    key_curve_nid_to_name(k->ecdsa_nid));
++		buffer_put_ecpoint(&k->cert->certblob,
++		    EC_KEY_get0_group(k->ecdsa),
++		    EC_KEY_get0_public_key(k->ecdsa));
++		break;
++#endif
+ 	case KEY_RSA_CERT_V00:
+ 	case KEY_RSA_CERT:
+ 		buffer_put_bignum2(&k->cert->certblob, k->rsa->e);
+@@ -1621,7 +2000,7 @@ key_certify(Key *k, Key *ca)
+ 	}
+ 
+ 	/* -v01 certs have a serial number next */
+-	if (k->type == KEY_DSA_CERT || k->type == KEY_RSA_CERT)
++	if (!key_cert_is_legacy(k))
+ 		buffer_put_int64(&k->cert->certblob, k->cert->serial);
+ 
+ 	buffer_put_int(&k->cert->certblob, k->cert->type);
+@@ -1640,14 +2019,14 @@ key_certify(Key *k, Key *ca)
+ 	    buffer_ptr(&k->cert->critical), buffer_len(&k->cert->critical));
+ 
+ 	/* -v01 certs have non-critical options here */
+-	if (k->type == KEY_DSA_CERT || k->type == KEY_RSA_CERT) {
++	if (!key_cert_is_legacy(k)) {
+ 		buffer_put_string(&k->cert->certblob,
+ 		    buffer_ptr(&k->cert->extensions),
+ 		    buffer_len(&k->cert->extensions));
+ 	}
+ 
+ 	/* -v00 certs put the nonce at the end */
+-	if (k->type == KEY_DSA_CERT_V00 || k->type == KEY_RSA_CERT_V00)
++	if (key_cert_is_legacy(k))
+ 		buffer_put_string(&k->cert->certblob, nonce, sizeof(nonce));
+ 
+ 	buffer_put_string(&k->cert->certblob, NULL, 0); /* reserved */
+@@ -1732,3 +2111,246 @@ key_cert_is_legacy(Key *k)
+ 		return 0;
+ 	}
+ }
++
++/* XXX: these are really begging for a table-driven approach */
++int
++key_curve_name_to_nid(const char *name)
++{
++#ifdef OPENSSL_HAS_ECC
++	if (strcmp(name, "nistp256") == 0)
++		return NID_X9_62_prime256v1;
++	else if (strcmp(name, "nistp384") == 0)
++		return NID_secp384r1;
++# ifdef OPENSSL_HAS_NISTP521
++	else if (strcmp(name, "nistp521") == 0)
++		return NID_secp521r1;
++# endif
++#endif
++
++	debug("%s: unsupported EC curve name \"%.100s\"", __func__, name);
++	return -1;
++}
++
++u_int
++key_curve_nid_to_bits(int nid)
++{
++	switch (nid) {
++#ifdef OPENSSL_HAS_ECC
++	case NID_X9_62_prime256v1:
++		return 256;
++	case NID_secp384r1:
++		return 384;
++# ifdef OPENSSL_HAS_NISTP521
++	case NID_secp521r1:
++		return 521;
++# endif
++#endif
++	default:
++		error("%s: unsupported EC curve nid %d", __func__, nid);
++		return 0;
++	}
++}
++
++const char *
++key_curve_nid_to_name(int nid)
++{
++#ifdef OPENSSL_HAS_ECC
++	if (nid == NID_X9_62_prime256v1)
++		return "nistp256";
++	else if (nid == NID_secp384r1)
++		return "nistp384";
++# ifdef OPENSSL_HAS_NISTP521
++	else if (nid == NID_secp521r1)
++		return "nistp521";
++# endif
++#endif
++	error("%s: unsupported EC curve nid %d", __func__, nid);
++	return NULL;
++}
++
++#ifdef OPENSSL_HAS_ECC
++const EVP_MD *
++key_ec_nid_to_evpmd(int nid)
++{
++	int kbits = key_curve_nid_to_bits(nid);
++
++	if (kbits == 0)
++		fatal("%s: invalid nid %d", __func__, nid);
++	/* RFC5656 section 6.2.1 */
++	if (kbits <= 256)
++		return EVP_sha256();
++	else if (kbits <= 384)
++		return EVP_sha384();
++	else
++		return EVP_sha512();
++}
++
++int
++key_ec_validate_public(const EC_GROUP *group, const EC_POINT *public)
++{
++	BN_CTX *bnctx;
++	EC_POINT *nq = NULL;
++	BIGNUM *order, *x, *y, *tmp;
++	int ret = -1;
++
++	if ((bnctx = BN_CTX_new()) == NULL)
++		fatal("%s: BN_CTX_new failed", __func__);
++	BN_CTX_start(bnctx);
++
++	/*
++	 * We shouldn't ever hit this case because bignum_get_ecpoint()
++	 * refuses to load GF2m points.
++	 */
++	if (EC_METHOD_get_field_type(EC_GROUP_method_of(group)) !=
++	    NID_X9_62_prime_field) {
++		error("%s: group is not a prime field", __func__);
++		goto out;
++	}
++
++	/* Q != infinity */
++	if (EC_POINT_is_at_infinity(group, public)) {
++		error("%s: received degenerate public key (infinity)",
++		    __func__);
++		goto out;
++	}
++
++	if ((x = BN_CTX_get(bnctx)) == NULL ||
++	    (y = BN_CTX_get(bnctx)) == NULL ||
++	    (order = BN_CTX_get(bnctx)) == NULL ||
++	    (tmp = BN_CTX_get(bnctx)) == NULL)
++		fatal("%s: BN_CTX_get failed", __func__);
++
++	/* log2(x) > log2(order)/2, log2(y) > log2(order)/2 */
++	if (EC_GROUP_get_order(group, order, bnctx) != 1)
++		fatal("%s: EC_GROUP_get_order failed", __func__);
++	if (EC_POINT_get_affine_coordinates_GFp(group, public,
++	    x, y, bnctx) != 1)
++		fatal("%s: EC_POINT_get_affine_coordinates_GFp", __func__);
++	if (BN_num_bits(x) <= BN_num_bits(order) / 2) {
++		error("%s: public key x coordinate too small: "
++		    "bits(x) = %d, bits(order)/2 = %d", __func__,
++		    BN_num_bits(x), BN_num_bits(order) / 2);
++		goto out;
++	}
++	if (BN_num_bits(y) <= BN_num_bits(order) / 2) {
++		error("%s: public key y coordinate too small: "
++		    "bits(y) = %d, bits(order)/2 = %d", __func__,
++		    BN_num_bits(x), BN_num_bits(order) / 2);
++		goto out;
++	}
++
++	/* nQ == infinity (n == order of subgroup) */
++	if ((nq = EC_POINT_new(group)) == NULL)
++		fatal("%s: BN_CTX_tmp failed", __func__);
++	if (EC_POINT_mul(group, nq, NULL, public, order, bnctx) != 1)
++		fatal("%s: EC_GROUP_mul failed", __func__);
++	if (EC_POINT_is_at_infinity(group, nq) != 1) {
++		error("%s: received degenerate public key (nQ != infinity)",
++		    __func__);
++		goto out;
++	}
++
++	/* x < order - 1, y < order - 1 */
++	if (!BN_sub(tmp, order, BN_value_one()))
++		fatal("%s: BN_sub failed", __func__);
++	if (BN_cmp(x, tmp) >= 0) {
++		error("%s: public key x coordinate >= group order - 1",
++		    __func__);
++		goto out;
++	}
++	if (BN_cmp(y, tmp) >= 0) {
++		error("%s: public key y coordinate >= group order - 1",
++		    __func__);
++		goto out;
++	}
++	ret = 0;
++ out:
++	BN_CTX_free(bnctx);
++	EC_POINT_free(nq);
++	return ret;
++}
++
++int
++key_ec_validate_private(const EC_KEY *key)
++{
++	BN_CTX *bnctx;
++	BIGNUM *order, *tmp;
++	int ret = -1;
++
++	if ((bnctx = BN_CTX_new()) == NULL)
++		fatal("%s: BN_CTX_new failed", __func__);
++	BN_CTX_start(bnctx);
++
++	if ((order = BN_CTX_get(bnctx)) == NULL ||
++	    (tmp = BN_CTX_get(bnctx)) == NULL)
++		fatal("%s: BN_CTX_get failed", __func__);
++
++	/* log2(private) > log2(order)/2 */
++	if (EC_GROUP_get_order(EC_KEY_get0_group(key), order, bnctx) != 1)
++		fatal("%s: EC_GROUP_get_order failed", __func__);
++	if (BN_num_bits(EC_KEY_get0_private_key(key)) <=
++	    BN_num_bits(order) / 2) {
++		error("%s: private key too small: "
++		    "bits(y) = %d, bits(order)/2 = %d", __func__,
++		    BN_num_bits(EC_KEY_get0_private_key(key)),
++		    BN_num_bits(order) / 2);
++		goto out;
++	}
++
++	/* private < order - 1 */
++	if (!BN_sub(tmp, order, BN_value_one()))
++		fatal("%s: BN_sub failed", __func__);
++	if (BN_cmp(EC_KEY_get0_private_key(key), tmp) >= 0) {
++		error("%s: private key >= group order - 1", __func__);
++		goto out;
++	}
++	ret = 0;
++ out:
++	BN_CTX_free(bnctx);
++	return ret;
++}
++
++#if defined(DEBUG_KEXECDH) || defined(DEBUG_PK)
++void
++key_dump_ec_point(const EC_GROUP *group, const EC_POINT *point)
++{
++	BIGNUM *x, *y;
++	BN_CTX *bnctx;
++
++	if (point == NULL) {
++		fputs("point=(NULL)\n", stderr);
++		return;
++	}
++	if ((bnctx = BN_CTX_new()) == NULL)
++		fatal("%s: BN_CTX_new failed", __func__);
++	BN_CTX_start(bnctx);
++	if ((x = BN_CTX_get(bnctx)) == NULL || (y = BN_CTX_get(bnctx)) == NULL)
++		fatal("%s: BN_CTX_get failed", __func__);
++	if (EC_METHOD_get_field_type(EC_GROUP_method_of(group)) !=
++	    NID_X9_62_prime_field)
++		fatal("%s: group is not a prime field", __func__);
++	if (EC_POINT_get_affine_coordinates_GFp(group, point, x, y, bnctx) != 1)
++		fatal("%s: EC_POINT_get_affine_coordinates_GFp", __func__);
++	fputs("x=", stderr);
++	BN_print_fp(stderr, x);
++	fputs("\ny=", stderr);
++	BN_print_fp(stderr, y);
++	fputs("\n", stderr);
++	BN_CTX_free(bnctx);
++}
++
++void
++key_dump_ec_key(const EC_KEY *key)
++{
++	const BIGNUM *exponent;
++
++	key_dump_ec_point(EC_KEY_get0_group(key), EC_KEY_get0_public_key(key));
++	fputs("exponent=", stderr);
++	if ((exponent = EC_KEY_get0_private_key(key)) == NULL)
++		fputs("(NULL)", stderr);
++	else
++		BN_print_fp(stderr, EC_KEY_get0_private_key(key));
++	fputs("\n", stderr);
++}
++#endif /* defined(DEBUG_KEXECDH) || defined(DEBUG_PK) */
++#endif /* OPENSSL_HAS_ECC */
+diff --git a/key.h b/key.h
+index acc5357..b8f2a65 100644
+--- a/key.h
++++ b/key.h
+@@ -1,4 +1,4 @@
+-/* $OpenBSD: key.h,v 1.30 2010/04/16 01:47:26 djm Exp $ */
++/* $OpenBSD: key.h,v 1.32 2010/09/09 10:45:45 djm Exp $ */
+ 
+ /*
+  * Copyright (c) 2000, 2001 Markus Friedl.  All rights reserved.
+@@ -29,6 +29,9 @@
+ #include "buffer.h"
+ #include <openssl/rsa.h>
+ #include <openssl/dsa.h>
++#ifdef OPENSSL_HAS_ECC
++#include <openssl/ec.h>
++#endif
+ 
+ #ifdef HAVE_LIBNSS
+ #include <nss.h>
+@@ -40,9 +43,11 @@ enum types {
+ 	KEY_RSA1,
+ 	KEY_RSA,
+ 	KEY_DSA,
++	KEY_ECDSA,
+ 	KEY_NSS,
+ 	KEY_RSA_CERT,
+ 	KEY_DSA_CERT,
++	KEY_ECDSA_CERT,
+ 	KEY_RSA_CERT_V00,
+ 	KEY_DSA_CERT_V00,
+ 	KEY_NULL,
+@@ -89,6 +94,12 @@ struct Key {
+ 	int	 flags;
+ 	RSA	*rsa;
+ 	DSA	*dsa;
++	int	 ecdsa_nid;	/* NID of curve */
++#ifdef OPENSSL_HAS_ECC
++	EC_KEY	*ecdsa;
++#else
++	void	*ecdsa;
++#endif
+ 	struct KeyCert *cert;
+ #ifdef HAVE_LIBNSS
+ 	NSSKey  *nss;
+@@ -125,9 +136,22 @@ int	 key_cert_check_authority(const Key *, int, int, const char *,
+ 	    const char **);
+ int	 key_cert_is_legacy(Key *);
+ 
++int		 key_ecdsa_nid_from_name(const char *);
++int		 key_curve_name_to_nid(const char *);
++const char *	 key_curve_nid_to_name(int);
++u_int		 key_curve_nid_to_bits(int);
++int		 key_ecdsa_bits_to_nid(int);
++#ifdef OPENSSL_HAS_ECC
++int		 key_ecdsa_group_to_nid(const EC_GROUP *);
++const EVP_MD *	 key_ec_nid_to_evpmd(int nid);
++int		 key_ec_validate_public(const EC_GROUP *, const EC_POINT *);
++int		 key_ec_validate_private(const EC_KEY *);
++#endif
++
+ Key		*key_from_blob(const u_char *, u_int);
+ int		 key_to_blob(const Key *, u_char **, u_int *);
+ const char	*key_ssh_name(const Key *);
++const char	*key_ssh_name_plain(const Key *);
+ int		 key_names_valid2(const char *);
+ 
+ int	 key_sign(const Key *, u_char **, u_int *, const u_char *, u_int);
+@@ -135,8 +159,16 @@ int	 key_verify(const Key *, const u_char *, u_int, const u_char *, u_int);
+ 
+ int	 ssh_dss_sign(const Key *, u_char **, u_int *, const u_char *, u_int);
+ int	 ssh_dss_verify(const Key *, const u_char *, u_int, const u_char *, u_int);
++int	 ssh_ecdsa_sign(const Key *, u_char **, u_int *, const u_char *, u_int);
++int	 ssh_ecdsa_verify(const Key *, const u_char *, u_int, const u_char *, u_int);
+ int	 ssh_rsa_sign(const Key *, u_char **, u_int *, const u_char *, u_int);
+ int	 ssh_rsa_verify(const Key *, const u_char *, u_int, const u_char *, u_int);
+ 
+ int	 key_is_private(const Key *k);
++
++#if defined(OPENSSL_HAS_ECC) && (defined(DEBUG_KEXECDH) || defined(DEBUG_PK))
++void	key_dump_ec_point(const EC_GROUP *, const EC_POINT *);
++void	key_dump_ec_key(const EC_KEY *);
++#endif
++
+ #endif
+diff --git a/monitor.c b/monitor.c
+index 197601b..13f5f55 100644
+--- a/monitor.c
++++ b/monitor.c
+@@ -655,10 +655,10 @@ mm_answer_sign(int sock, Buffer *m)
+ 	p = buffer_get_string(m, &datlen);
+ 
+ 	/*
+-	 * Supported KEX types will only return SHA1 (20 byte) or
+-	 * SHA256 (32 byte) hashes
++	 * Supported KEX types use SHA1 (20 bytes), SHA256 (32 bytes),
++	 * SHA384 (48 bytes) and SHA512 (64 bytes).
+ 	 */
+-	if (datlen != 20 && datlen != 32)
++	if (datlen != 20 && datlen != 32 && datlen != 48 && datlen != 64)
+ 		fatal("%s: data length incorrect: %u", __func__, datlen);
+ 
+ 	/* save session id, it will be passed on the first call */
+@@ -1869,6 +1869,7 @@ mm_get_kex(Buffer *m)
+ 	kex->kex[KEX_DH_GRP14_SHA1] = kexdh_server;
+ 	kex->kex[KEX_DH_GEX_SHA1] = kexgex_server;
+ 	kex->kex[KEX_DH_GEX_SHA256] = kexgex_server;
++	kex->kex[KEX_ECDH_SHA2] = kexecdh_server;
+ #ifdef GSSAPI
+ 	if (options.gss_keyex) {
+ 		kex->kex[KEX_GSS_GRP1_SHA1] = kexgss_server;
+diff --git a/monitor_wrap.c b/monitor_wrap.c
+index 546c73a..0b355fb 100644
+--- a/monitor_wrap.c
++++ b/monitor_wrap.c
+@@ -73,6 +73,7 @@
+ #include "misc.h"
+ #include "schnorr.h"
+ #include "jpake.h"
++#include "uuencode.h"
+ 
+ #include "channels.h"
+ #include "session.h"
+diff --git a/myproposal.h b/myproposal.h
+index b7fb4a1..b71938b 100644
+--- a/myproposal.h
++++ b/myproposal.h
+@@ -1,4 +1,4 @@
+-/* $OpenBSD: myproposal.h,v 1.25 2010/04/16 01:47:26 djm Exp $ */
++/* $OpenBSD: myproposal.h,v 1.26 2010/08/31 11:54:45 djm Exp $ */
+ 
+ /*
+  * Copyright (c) 2000 Markus Friedl.  All rights reserved.
+diff --git a/packet.c b/packet.c
+index 03e62dd..78dd445 100644
+--- a/packet.c
++++ b/packet.c
+@@ -658,6 +658,14 @@ packet_put_bignum2(BIGNUM * value)
+ 	buffer_put_bignum2(&active_state->outgoing_packet, value);
+ }
+ 
++#ifdef OPENSSL_HAS_ECC
++void
++packet_put_ecpoint(const EC_GROUP *curve, const EC_POINT *point)
++{
++	buffer_put_ecpoint(&active_state->outgoing_packet, curve, point);
++}
++#endif
++
+ /*
+  * Finalizes and sends the packet.  If the encryption key has been set,
+  * encrypts the packet before sending.
+@@ -1536,6 +1544,14 @@ packet_get_bignum2(BIGNUM * value)
+ 	buffer_get_bignum2(&active_state->incoming_packet, value);
+ }
+ 
++#ifdef OPENSSL_HAS_ECC
++void
++packet_get_ecpoint(const EC_GROUP *curve, EC_POINT *point)
++{
++	buffer_get_ecpoint(&active_state->incoming_packet, curve, point);
++}
++#endif
++
+ void *
+ packet_get_raw(u_int *length_ptr)
+ {
+diff --git a/packet.h b/packet.h
+index 7c6587b..5db8b58 100644
+--- a/packet.h
++++ b/packet.h
+@@ -19,6 +19,9 @@
+ #include <termios.h>
+ 
+ #include <openssl/bn.h>
++#ifdef OPENSSL_HAS_ECC
++#include <openssl/ec.h>
++#endif
+ 
+ int	 packet_is_active(void);
+ void     packet_set_connection(int, int);
+@@ -43,6 +46,9 @@ void     packet_put_int(u_int value);
+ void     packet_put_int64(u_int64_t value);
+ void     packet_put_bignum(BIGNUM * value);
+ void     packet_put_bignum2(BIGNUM * value);
++#ifdef OPENSSL_HAS_ECC
++void     packet_put_ecpoint(const EC_GROUP *, const EC_POINT *);
++#endif
+ void     packet_put_string(const void *buf, u_int len);
+ void     packet_put_cstring(const char *str);
+ void     packet_put_raw(const void *buf, u_int len);
+@@ -60,6 +66,9 @@ u_int	 packet_get_int(void);
+ u_int64_t packet_get_int64(void);
+ void     packet_get_bignum(BIGNUM * value);
+ void     packet_get_bignum2(BIGNUM * value);
++#ifdef OPENSSL_HAS_ECC
++void	 packet_get_ecpoint(const EC_GROUP *, EC_POINT *);
++#endif
+ void	*packet_get_raw(u_int *length_ptr);
+ void	*packet_get_string(u_int *length_ptr);
+ void	*packet_get_string_ptr(u_int *length_ptr);
+diff --git a/pathnames.h b/pathnames.h
+index 32b9e06..61711f4 100644
+--- a/pathnames.h
++++ b/pathnames.h
+@@ -38,6 +38,7 @@
+ #define _PATH_HOST_CONFIG_FILE		SSHDIR "/ssh_config"
+ #define _PATH_HOST_KEY_FILE		SSHDIR "/ssh_host_key"
+ #define _PATH_HOST_DSA_KEY_FILE		SSHDIR "/ssh_host_dsa_key"
++#define _PATH_HOST_ECDSA_KEY_FILE	SSHDIR "/ssh_host_ecdsa_key"
+ #define _PATH_HOST_RSA_KEY_FILE		SSHDIR "/ssh_host_rsa_key"
+ #define _PATH_DH_MODULI			SSHDIR "/moduli"
+ /* Backwards compatibility */
+@@ -74,6 +75,7 @@
+  */
+ #define _PATH_SSH_CLIENT_IDENTITY	".ssh/identity"
+ #define _PATH_SSH_CLIENT_ID_DSA		".ssh/id_dsa"
++#define _PATH_SSH_CLIENT_ID_ECDSA	".ssh/id_ecdsa"
+ #define _PATH_SSH_CLIENT_ID_RSA		".ssh/id_rsa"
+ 
+ /*
+diff --git a/readconf.c b/readconf.c
+index 8525808..c63bd1a 100644
+--- a/readconf.c
++++ b/readconf.c
+@@ -1251,6 +1251,13 @@ fill_default_options(Options * options)
+ 			    xmalloc(len);
+ 			snprintf(options->identity_files[options->num_identity_files++],
+ 			    len, "~/%.100s", _PATH_SSH_CLIENT_ID_DSA);
++#ifdef OPENSSL_HAS_ECC
++			len = 2 + strlen(_PATH_SSH_CLIENT_ID_ECDSA) + 1;
++			options->identity_files[options->num_identity_files] =
++			    xmalloc(len);
++			snprintf(options->identity_files[options->num_identity_files++],
++			    len, "~/%.100s", _PATH_SSH_CLIENT_ID_ECDSA);
++#endif
+ 		}
+ 	}
+ 	if (options->escape_char == -1)
+diff --git a/regress/cert-hostkey.sh b/regress/cert-hostkey.sh
+index 1c45f05..dd2d816 100644
+--- a/regress/cert-hostkey.sh
++++ b/regress/cert-hostkey.sh
+@@ -1,4 +1,4 @@
+-#	$OpenBSD: cert-hostkey.sh,v 1.4 2010/04/16 01:58:45 djm Exp $
++#	$OpenBSD: cert-hostkey.sh,v 1.5 2010/08/31 12:24:09 djm Exp $
+ #	Placed in the Public Domain.
+ 
+ tid="certified host keys"
+@@ -18,7 +18,7 @@ ${SSHKEYGEN} -q -N '' -t rsa  -f $OBJ/host_ca_key ||\
+ ) > $OBJ/known_hosts-cert
+ 
+ # Generate and sign host keys
+-for ktype in rsa dsa ; do 
++for ktype in rsa dsa ecdsa ; do 
+ 	verbose "$tid: sign host ${ktype} cert"
+ 	# Generate and sign a host key
+ 	${SSHKEYGEN} -q -N '' -t ${ktype} \
+@@ -28,6 +28,8 @@ for ktype in rsa dsa ; do
+ 	    -I "regress host key for $USER" \
+ 	    -Z $HOSTS $OBJ/cert_host_key_${ktype} ||
+ 		fail "couldn't sign cert_host_key_${ktype}"
++	# v00 ecdsa certs do not exist
++	test "{ktype}" = "ecdsa" && continue
+ 	cp $OBJ/cert_host_key_${ktype} $OBJ/cert_host_key_${ktype}_v00
+ 	cp $OBJ/cert_host_key_${ktype}.pub $OBJ/cert_host_key_${ktype}_v00.pub
+ 	${SSHKEYGEN} -t v00 -h -q -s $OBJ/host_ca_key \
+@@ -36,9 +38,12 @@ for ktype in rsa dsa ; do
+ 		fail "couldn't sign cert_host_key_${ktype}_v00"
+ done
+ 
++# ECDSA is not enabled by default to avoid some problems with old servers
++ECDSA_HOSTKEYALGORITHMS="-oHostKeyAlgorithms=ecdsa-sha2-nistp256-cert-v01 at openssh.com,ecdsa-sha2-nistp384-cert-v01 at openssh.com,ecdsa-sha2-nistp521-cert-v01 at openssh.com,ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,ssh-rsa-cert-v01 at openssh.com,ssh-dss-cert-v01 at openssh.com,ssh-rsa-cert-v00 at openssh.com,ssh-dss-cert-v00 at openssh.com,ssh-rsa,ssh-dss"
++
+ # Basic connect tests
+ for privsep in yes no ; do
+-	for ktype in rsa dsa rsa_v00 dsa_v00; do 
++	for ktype in rsa dsa ecdsa rsa_v00 dsa_v00; do 
+ 		verbose "$tid: host ${ktype} cert connect privsep $privsep"
+ 		(
+ 			cat $OBJ/sshd_proxy_bak
+@@ -47,8 +52,14 @@ for privsep in yes no ; do
+ 			echo UsePrivilegeSeparation $privsep
+ 		) > $OBJ/sshd_proxy
+ 
++		case $ktype in
++		ecdsa) HOSTKEYALGORITHMS=$ECDSA_HOSTKEYALGORITHMS;;
++		*) HOSTKEYALGORITHMS="" ;;
++		esac
++
+ 		${SSH} -2 -oUserKnownHostsFile=$OBJ/known_hosts-cert \
+ 		    -oGlobalKnownHostsFile=$OBJ/known_hosts-cert \
++		    $HOSTKEYALGORITHMS \
+ 			-F $OBJ/ssh_proxy somehost true
+ 		if [ $? -ne 0 ]; then
+ 			fail "ssh cert connect failed"
+@@ -66,6 +77,9 @@ done
+ 	cat $OBJ/cert_host_key_rsa.pub
+ 	echon '@revoked '
+ 	echon "* "
++	cat $OBJ/cert_host_key_ecdsa.pub
++	echon '@revoked '
++	echon "* "
+ 	cat $OBJ/cert_host_key_dsa.pub
+ 	echon '@revoked '
+ 	echon "* "
+@@ -75,7 +89,7 @@ done
+ 	cat $OBJ/cert_host_key_dsa_v00.pub
+ ) > $OBJ/known_hosts-cert
+ for privsep in yes no ; do
+-	for ktype in rsa dsa rsa_v00 dsa_v00; do 
++	for ktype in rsa dsa ecdsa rsa_v00 dsa_v00; do 
+ 		verbose "$tid: host ${ktype} revoked cert privsep $privsep"
+ 		(
+ 			cat $OBJ/sshd_proxy_bak
+@@ -84,8 +98,14 @@ for privsep in yes no ; do
+ 			echo UsePrivilegeSeparation $privsep
+ 		) > $OBJ/sshd_proxy
+ 
++		case $ktype in
++		ecdsa) HOSTKEYALGORITHMS=$ECDSA_HOSTKEYALGORITHMS;;
++		*) HOSTKEYALGORITHMS="" ;;
++		esac
++
+ 		${SSH} -2 -oUserKnownHostsFile=$OBJ/known_hosts-cert \
+ 		    -oGlobalKnownHostsFile=$OBJ/known_hosts-cert \
++		    $HOSTKEYALGORITHMS \
+ 			-F $OBJ/ssh_proxy somehost true >/dev/null 2>&1
+ 		if [ $? -eq 0 ]; then
+ 			fail "ssh cert connect succeeded unexpectedly"
+@@ -102,15 +122,22 @@ done
+ 	echon "* "
+ 	cat $OBJ/host_ca_key.pub
+ ) > $OBJ/known_hosts-cert
+-for ktype in rsa dsa rsa_v00 dsa_v00 ; do 
++for ktype in rsa dsa ecdsa rsa_v00 dsa_v00 ; do 
+ 	verbose "$tid: host ${ktype} revoked cert"
+ 	(
+ 		cat $OBJ/sshd_proxy_bak
+ 		echo HostKey $OBJ/cert_host_key_${ktype}
+ 		echo HostCertificate $OBJ/cert_host_key_${ktype}-cert.pub
+ 	) > $OBJ/sshd_proxy
++
++	case $ktype in
++	ecdsa) HOSTKEYALGORITHMS=$ECDSA_HOSTKEYALGORITHMS;;
++	*) HOSTKEYALGORITHMS="" ;;
++	esac
++
+ 	${SSH} -2 -oUserKnownHostsFile=$OBJ/known_hosts-cert \
+ 	    -oGlobalKnownHostsFile=$OBJ/known_hosts-cert \
++	    $HOSTKEYALGORITHMS \
+ 		-F $OBJ/ssh_proxy somehost true >/dev/null 2>&1
+ 	if [ $? -eq 0 ]; then
+ 		fail "ssh cert connect succeeded unexpectedly"
+@@ -147,8 +174,14 @@ test_one() {
+ 			echo HostCertificate $OBJ/cert_host_key_${kt}-cert.pub
+ 		) > $OBJ/sshd_proxy
+ 	
++		case $ktype in
++		ecdsa) HOSTKEYALGORITHMS=$ECDSA_HOSTKEYALGORITHMS;;
++		*) HOSTKEYALGORITHMS="" ;;
++		esac
++
+ 		${SSH} -2 -oUserKnownHostsFile=$OBJ/known_hosts-cert \
+ 		    -oGlobalKnownHostsFile=$OBJ/known_hosts-cert \
++		    $HOSTKEYALGORITHMS \
+ 		    -F $OBJ/ssh_proxy somehost true >/dev/null 2>&1
+ 		rc=$?
+ 		if [ "x$result" = "xsuccess" ] ; then
+@@ -173,7 +206,9 @@ test_one "cert has constraints"	failure "-h -Oforce-command=false"
+ 
+ # Check downgrade of cert to raw key when no CA found
+ for v in v01 v00 ;  do 
+-	for ktype in rsa dsa ; do 
++	for ktype in rsa dsa ecdsa ; do 
++		# v00 ecdsa certs do not exist.
++		test "${v}${ktype}" = "v00ecdsa" && continue
+ 		rm -f $OBJ/known_hosts-cert $OBJ/cert_host_key*
+ 		verbose "$tid: host ${ktype} ${v} cert downgrade to raw key"
+ 		# Generate and sign a host key
+@@ -194,8 +229,14 @@ for v in v01 v00 ;  do
+ 			echo HostCertificate $OBJ/cert_host_key_${ktype}-cert.pub
+ 		) > $OBJ/sshd_proxy
+ 		
++		case $ktype in
++		ecdsa) HOSTKEYALGORITHMS=$ECDSA_HOSTKEYALGORITHMS;;
++		*) HOSTKEYALGORITHMS="" ;;
++		esac
++
+ 		${SSH} -2 -oUserKnownHostsFile=$OBJ/known_hosts-cert \
+ 		    -oGlobalKnownHostsFile=$OBJ/known_hosts-cert \
++		    $HOSTKEYALGORITHMS \
+ 			-F $OBJ/ssh_proxy somehost true
+ 		if [ $? -ne 0 ]; then
+ 			fail "ssh cert connect failed"
+@@ -210,7 +251,9 @@ done
+ 	cat $OBJ/host_ca_key.pub
+ ) > $OBJ/known_hosts-cert
+ for v in v01 v00 ;  do 
+-	for kt in rsa dsa ; do 
++	for kt in rsa dsa ecdsa ; do 
++		# v00 ecdsa certs do not exist.
++		test "${v}${ktype}" = "v00ecdsa" && continue
+ 		rm -f $OBJ/cert_host_key*
+ 		# Self-sign key
+ 		${SSHKEYGEN} -q -N '' -t ${kt} \
+diff --git a/regress/cert-userkey.sh b/regress/cert-userkey.sh
+index 89fe9dc..4b3d17f 100644
+--- a/regress/cert-userkey.sh
++++ b/regress/cert-userkey.sh
+@@ -11,7 +11,7 @@ ${SSHKEYGEN} -q -N '' -t rsa  -f $OBJ/user_ca_key ||\
+ 	fail "ssh-keygen of user_ca_key failed"
+ 
+ # Generate and sign user keys
+-for ktype in rsa dsa ; do 
++for ktype in rsa dsa ecdsa ; do 
+ 	verbose "$tid: sign user ${ktype} cert"
+ 	${SSHKEYGEN} -q -N '' -t ${ktype} \
+ 	    -f $OBJ/cert_user_key_${ktype} || \
+@@ -20,6 +20,8 @@ for ktype in rsa dsa ; do
+ 	    "regress user key for $USER" \
+ 	    -Z ${USER},mekmitasdigoat $OBJ/cert_user_key_${ktype} ||
+ 		fail "couldn't sign cert_user_key_${ktype}"
++	# v00 ecdsa certs do not exist
++	test "{ktype}" = "ecdsa" && continue
+ 	cp $OBJ/cert_user_key_${ktype} $OBJ/cert_user_key_${ktype}_v00
+ 	cp $OBJ/cert_user_key_${ktype}.pub $OBJ/cert_user_key_${ktype}_v00.pub
+ 	${SSHKEYGEN} -q -t v00 -s $OBJ/user_ca_key -I \
+@@ -29,7 +31,7 @@ for ktype in rsa dsa ; do
+ done
+ 
+ # Test explicitly-specified principals
+-for ktype in rsa dsa rsa_v00 dsa_v00 ; do 
++for ktype in rsa dsa ecdsa rsa_v00 dsa_v00 ; do 
+ 	for privsep in yes no ; do
+ 		_prefix="${ktype} privsep $privsep"
+ 
+@@ -125,7 +127,7 @@ basic_tests() {
+ 		extra_sshd="TrustedUserCAKeys $OBJ/user_ca_key.pub"
+ 	fi
+ 	
+-	for ktype in rsa dsa rsa_v00 dsa_v00 ; do 
++	for ktype in rsa dsa ecdsa rsa_v00 dsa_v00 ; do 
+ 		for privsep in yes no ; do
+ 			_prefix="${ktype} privsep $privsep $auth"
+ 			# Simple connect
+@@ -200,6 +202,11 @@ test_one() {
+ 
+ 	for auth in $auth_choice ; do
+ 		for ktype in rsa rsa_v00 ; do
++			case $ktype in
++			*_v00) keyv="-t v00" ;;
++			*) keyv="" ;;
++			esac
++
+ 			cat $OBJ/sshd_proxy_bak > $OBJ/sshd_proxy
+ 			if test "x$auth" = "xauthorized_keys" ; then
+ 				# Add CA to authorized_keys
+@@ -219,7 +226,7 @@ test_one() {
+ 			verbose "$tid: $ident auth $auth expect $result $ktype"
+ 			${SSHKEYGEN} -q -s $OBJ/user_ca_key \
+ 			    -I "regress user key for $USER" \
+-			    $sign_opts \
++			    $sign_opts $keyv \
+ 			    $OBJ/cert_user_key_${ktype} ||
+ 				fail "couldn't sign cert_user_key_${ktype}"
+ 
+@@ -272,7 +279,7 @@ test_one "principals key option no principals" failure "" \
+ 
+ # Wrong certificate
+ cat $OBJ/sshd_proxy_bak > $OBJ/sshd_proxy
+-for ktype in rsa dsa rsa_v00 dsa_v00 ; do 
++for ktype in rsa dsa ecdsa rsa_v00 dsa_v00 ; do 
+ 	case $ktype in
+ 	*_v00) args="-t v00" ;;
+ 	*) args="" ;;
+diff --git a/ssh-add.1 b/ssh-add.1
+index f940930..a483691 100644
+--- a/ssh-add.1
++++ b/ssh-add.1
+@@ -42,7 +42,7 @@
+ .Os
+ .Sh NAME
+ .Nm ssh-add
+-.Nd adds RSA or DSA identities to the authentication agent
++.Nd adds private key identities to the authentication agent
+ .Sh SYNOPSIS
+ .Nm ssh-add
+ .Op Fl cDdLlXx
+@@ -57,11 +57,12 @@
+ .Op Fl T Ar token
+ .Sh DESCRIPTION
+ .Nm
+-adds RSA or DSA identities to the authentication agent,
++adds private key identities to the authentication agent,
+ .Xr ssh-agent 1 .
+ When run without arguments, it adds the files
+ .Pa ~/.ssh/id_rsa ,
+-.Pa ~/.ssh/id_dsa
++.Pa ~/.ssh/id_dsa ,
++.Pa ~/.ssh/id_ecdsa
+ and
+ .Pa ~/.ssh/identity .
+ After loading a private key,
+@@ -183,6 +184,8 @@ be blocked until enough entropy is available.
+ Contains the protocol version 1 RSA authentication identity of the user.
+ .It Pa ~/.ssh/id_dsa
+ Contains the protocol version 2 DSA authentication identity of the user.
++.It Pa ~/.ssh/id_ecdsa
++Contains the protocol version 2 ECDSA authentication identity of the user.
+ .It Pa ~/.ssh/id_rsa
+ Contains the protocol version 2 RSA authentication identity of the user.
+ .El
+diff --git a/ssh-add.c b/ssh-add.c
+index 230e3ed..d268cdd 100644
+--- a/ssh-add.c
++++ b/ssh-add.c
+@@ -80,6 +80,7 @@ extern char *__progname;
+ static char *default_files[] = {
+ 	_PATH_SSH_CLIENT_ID_RSA,
+ 	_PATH_SSH_CLIENT_ID_DSA,
++	_PATH_SSH_CLIENT_ID_ECDSA,
+ 	_PATH_SSH_CLIENT_IDENTITY,
+ 	NULL
+ };
+diff --git a/ssh-agent.1 b/ssh-agent.1
+index 4bf0d50..a6533f5 100644
+--- a/ssh-agent.1
++++ b/ssh-agent.1
+@@ -53,7 +53,7 @@
+ .Sh DESCRIPTION
+ .Nm
+ is a program to hold private keys used for public key authentication
+-(RSA, DSA).
++(RSA, DSA, ECDSA).
+ The idea is that
+ .Nm
+ is started in the beginning of an X-session or a login session, and
+@@ -112,7 +112,8 @@ When executed without arguments,
+ .Xr ssh-add 1
+ adds the files
+ .Pa ~/.ssh/id_rsa ,
+-.Pa ~/.ssh/id_dsa
++.Pa ~/.ssh/id_dsa ,
++.Pa ~/.ssh/id_ecdsa
+ and
+ .Pa ~/.ssh/identity .
+ If the identity has a passphrase,
+@@ -183,6 +184,8 @@ line terminates.
+ Contains the protocol version 1 RSA authentication identity of the user.
+ .It Pa ~/.ssh/id_dsa
+ Contains the protocol version 2 DSA authentication identity of the user.
++.It Pa ~/.ssh/id_ecdsa
++Contains the protocol version 2 ECDSA authentication identity of the user.
+ .It Pa ~/.ssh/id_rsa
+ Contains the protocol version 2 RSA authentication identity of the user.
+ .It Pa /tmp/ssh-XXXXXXXXXX/agent.\*(Ltppid\*(Gt
+diff --git a/ssh-agent.c b/ssh-agent.c
+index 73d3de3..0062235 100644
+--- a/ssh-agent.c
++++ b/ssh-agent.c
+@@ -473,6 +473,11 @@ process_add_identity(SocketEntry *e, int version)
+ 	int type, success = 0, death = 0, confirm = 0;
+ 	char *type_name, *comment;
+ 	Key *k = NULL;
++#ifdef OPENSSL_HAS_ECC
++	BIGNUM *exponent;
++	EC_POINT *q;
++	int *curve;
++#endif
+ 	u_char *cert;
+ 	u_int len;
+ 
+@@ -495,7 +500,6 @@ process_add_identity(SocketEntry *e, int version)
+ 	case 2:
+ 		type_name = buffer_get_string(&e->request, NULL);
+ 		type = key_type_from_name(type_name);
+-		xfree(type_name);
+ 		switch (type) {
+ 		case KEY_DSA:
+ 			k = key_new_private(type);
+@@ -514,6 +518,59 @@ process_add_identity(SocketEntry *e, int version)
+ 			key_add_private(k);
+ 			buffer_get_bignum2(&e->request, k->dsa->priv_key);
+ 			break;
++#ifdef OPENSSL_HAS_ECC
++		case KEY_ECDSA:
++			k = key_new_private(type);
++			k->ecdsa_nid = key_ecdsa_nid_from_name(type_name);
++			curve = buffer_get_string(&e->request, NULL);
++			if (k->ecdsa_nid != key_curve_name_to_nid(curve))
++				fatal("%s: curve names mismatch", __func__);
++			xfree(curve);
++			k->ecdsa = EC_KEY_new_by_curve_name(k->ecdsa_nid);
++			if (k->ecdsa == NULL)
++				fatal("%s: EC_KEY_new_by_curve_name failed",
++				    __func__);
++			q = EC_POINT_new(EC_KEY_get0_group(k->ecdsa));
++			if (q == NULL)
++				fatal("%s: BN_new failed", __func__);
++			if ((exponent = BN_new()) == NULL)
++				fatal("%s: BN_new failed", __func__);
++			buffer_get_ecpoint(&e->request,
++				EC_KEY_get0_group(k->ecdsa), q);
++			buffer_get_bignum2(&e->request, exponent);
++			if (EC_KEY_set_public_key(k->ecdsa, q) != 1)
++				fatal("%s: EC_KEY_set_public_key failed",
++				    __func__);
++			if (EC_KEY_set_private_key(k->ecdsa, exponent) != 1)
++				fatal("%s: EC_KEY_set_private_key failed",
++				    __func__);
++			if (key_ec_validate_public(EC_KEY_get0_group(k->ecdsa),
++			    EC_KEY_get0_public_key(k->ecdsa)) != 0)
++				fatal("%s: bad ECDSA public key", __func__);
++			if (key_ec_validate_private(k->ecdsa) != 0)
++				fatal("%s: bad ECDSA private key", __func__);
++			BN_clear_free(exponent);
++			EC_POINT_free(q);
++			break;
++		case KEY_ECDSA_CERT:
++			cert = buffer_get_string(&e->request, &len);
++			if ((k = key_from_blob(cert, len)) == NULL)
++				fatal("Certificate parse failed");
++			xfree(cert);
++			key_add_private(k);
++			if ((exponent = BN_new()) == NULL)
++				fatal("%s: BN_new failed", __func__);
++			buffer_get_bignum2(&e->request, exponent);
++			if (EC_KEY_set_private_key(k->ecdsa, exponent) != 1)
++				fatal("%s: EC_KEY_set_private_key failed",
++				    __func__);
++			if (key_ec_validate_public(EC_KEY_get0_group(k->ecdsa),
++			    EC_KEY_get0_public_key(k->ecdsa)) != 0 ||
++			    key_ec_validate_private(k->ecdsa) != 0)
++				fatal("%s: bad ECDSA key", __func__);
++			BN_clear_free(exponent);
++			break;
++#endif /* OPENSSL_HAS_ECC */
+ 		case KEY_RSA:
+ 			k = key_new_private(type);
+ 			buffer_get_bignum2(&e->request, k->rsa->n);
+@@ -539,9 +596,11 @@ process_add_identity(SocketEntry *e, int version)
+ 			buffer_get_bignum2(&e->request, k->rsa->q);
+ 			break;
+ 		default:
++			xfree(type_name);
+ 			buffer_clear(&e->request);
+ 			goto send;
+ 		}
++		xfree(type_name);
+ 		break;
+ 	}
+ 	/* enable blinding */
+diff --git a/ssh-ecdsa.c b/ssh-ecdsa.c
+new file mode 100644
+index 0000000..f179ec5
+--- /dev/null
++++ b/ssh-ecdsa.c
+@@ -0,0 +1,168 @@
++/* $OpenBSD */
++/*
++ * Copyright (c) 2000 Markus Friedl.  All rights reserved.
++ * Copyright (c) 2010 Damien Miller.  All rights reserved.
++ *
++ * Redistribution and use in source and binary forms, with or without
++ * modification, are permitted provided that the following conditions
++ * are met:
++ * 1. Redistributions of source code must retain the above copyright
++ *    notice, this list of conditions and the following disclaimer.
++ * 2. Redistributions in binary form must reproduce the above copyright
++ *    notice, this list of conditions and the following disclaimer in the
++ *    documentation and/or other materials provided with the distribution.
++ *
++ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
++ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
++ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
++ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
++ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
++ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
++ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
++ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
++ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
++ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
++ */
++
++#include "includes.h"
++
++#ifdef OPENSSL_HAS_ECC
++
++#include <sys/types.h>
++
++#include <openssl/bn.h>
++#include <openssl/ec.h>
++#include <openssl/ecdsa.h>
++#include <openssl/evp.h>
++
++#include <string.h>
++
++#include "xmalloc.h"
++#include "buffer.h"
++#include "compat.h"
++#include "log.h"
++#include "key.h"
++
++int
++ssh_ecdsa_sign(const Key *key, u_char **sigp, u_int *lenp,
++    const u_char *data, u_int datalen)
++{
++	ECDSA_SIG *sig;
++	const EVP_MD *evp_md;
++	EVP_MD_CTX md;
++	u_char digest[EVP_MAX_MD_SIZE];
++	u_int len, dlen;
++	Buffer b, bb;
++
++	if (key == NULL || key->ecdsa == NULL ||
++	    (key->type != KEY_ECDSA && key->type != KEY_ECDSA_CERT)) {
++		error("%s: no ECDSA key", __func__);
++		return -1;
++	}
++	evp_md = key_ec_nid_to_evpmd(key->ecdsa_nid);
++	EVP_DigestInit(&md, evp_md);
++	EVP_DigestUpdate(&md, data, datalen);
++	EVP_DigestFinal(&md, digest, &dlen);
++
++	sig = ECDSA_do_sign(digest, dlen, key->ecdsa);
++	memset(digest, 'd', sizeof(digest));
++
++	if (sig == NULL) {
++		error("%s: sign failed", __func__);
++		return -1;
++	}
++
++	buffer_init(&bb);
++	buffer_put_bignum2(&bb, sig->r);
++	buffer_put_bignum2(&bb, sig->s);
++	ECDSA_SIG_free(sig);
++
++	buffer_init(&b);
++	buffer_put_cstring(&b, key_ssh_name_plain(key));
++	buffer_put_string(&b, buffer_ptr(&bb), buffer_len(&bb));
++	buffer_free(&bb);
++	len = buffer_len(&b);
++	if (lenp != NULL)
++		*lenp = len;
++	if (sigp != NULL) {
++		*sigp = xmalloc(len);
++		memcpy(*sigp, buffer_ptr(&b), len);
++	}
++	buffer_free(&b);
++
++	return 0;
++}
++int
++ssh_ecdsa_verify(const Key *key, const u_char *signature, u_int signaturelen,
++    const u_char *data, u_int datalen)
++{
++	ECDSA_SIG *sig;
++	const EVP_MD *evp_md;
++	EVP_MD_CTX md;
++	u_char digest[EVP_MAX_MD_SIZE], *sigblob;
++	u_int len, dlen;
++	int rlen, ret;
++	Buffer b, bb;
++	char *ktype;
++
++	if (key == NULL || key->ecdsa == NULL ||
++	    (key->type != KEY_ECDSA && key->type != KEY_ECDSA_CERT)) {
++		error("%s: no ECDSA key", __func__);
++		return -1;
++	}
++	evp_md = key_ec_nid_to_evpmd(key->ecdsa_nid);
++
++	/* fetch signature */
++	buffer_init(&b);
++	buffer_append(&b, signature, signaturelen);
++	ktype = buffer_get_string(&b, NULL);
++	if (strcmp(key_ssh_name_plain(key), ktype) != 0) {
++		error("%s: cannot handle type %s", __func__, ktype);
++		buffer_free(&b);
++		xfree(ktype);
++		return -1;
++	}
++	xfree(ktype);
++	sigblob = buffer_get_string(&b, &len);
++	rlen = buffer_len(&b);
++	buffer_free(&b);
++	if (rlen != 0) {
++		error("%s: remaining bytes in signature %d", __func__, rlen);
++		xfree(sigblob);
++		return -1;
++	}
++
++	/* parse signature */
++	if ((sig = ECDSA_SIG_new()) == NULL)
++		fatal("%s: ECDSA_SIG_new failed", __func__);
++	if ((sig->r = BN_new()) == NULL ||
++	    (sig->s = BN_new()) == NULL)
++		fatal("%s: BN_new failed", __func__);
++
++	buffer_init(&bb);
++	buffer_append(&bb, sigblob, len);
++	buffer_get_bignum2(&bb, sig->r);
++	buffer_get_bignum2(&bb, sig->s);
++	if (buffer_len(&bb) != 0)
++		fatal("%s: remaining bytes in inner sigblob", __func__);
++
++	/* clean up */
++	memset(sigblob, 0, len);
++	xfree(sigblob);
++
++	/* hash the data */
++	EVP_DigestInit(&md, evp_md);
++	EVP_DigestUpdate(&md, data, datalen);
++	EVP_DigestFinal(&md, digest, &dlen);
++
++	ret = ECDSA_do_verify(digest, dlen, sig, key->ecdsa);
++	memset(digest, 'd', sizeof(digest));
++
++	ECDSA_SIG_free(sig);
++
++	debug("%s: signature %s", __func__,
++	    ret == 1 ? "correct" : ret == 0 ? "incorrect" : "error");
++	return ret;
++}
++
++#endif /* OPENSSL_HAS_ECC */
+diff --git a/ssh-keygen.1 b/ssh-keygen.1
+index 8dfa302..8a693af 100644
+--- a/ssh-keygen.1
++++ b/ssh-keygen.1
+@@ -127,7 +127,7 @@
+ generates, manages and converts authentication keys for
+ .Xr ssh 1 .
+ .Nm
+-can create RSA keys for use by SSH protocol version 1 and RSA or DSA
++can create RSA keys for use by SSH protocol version 1 and DSA, ECDSA or RSA
+ keys for use by SSH protocol version 2.
+ The type of key to be generated is specified with the
+ .Fl t
+@@ -144,9 +144,10 @@ See the
+ section for details.
+ .Pp
+ Normally each user wishing to use SSH
+-with RSA or DSA authentication runs this once to create the authentication
++with public key authentication runs this once to create the authentication
+ key in
+ .Pa ~/.ssh/identity ,
++.Pa ~/.ssh/id_ecdsa ,
+ .Pa ~/.ssh/id_dsa
+ or
+ .Pa ~/.ssh/id_rsa .
+@@ -408,9 +409,10 @@ Specifies the type of key to create.
+ The possible values are
+ .Dq rsa1
+ for protocol version 1 and
+-.Dq rsa
++.Dq dsa ,
++.Dq ecdsa
+ or
+-.Dq dsa
++.Dq rsa
+ for protocol version 2.
+ .It Fl V Ar validity_interval
+ Specify a validity interval when signing a certificate.
+@@ -576,7 +578,7 @@ or
+ .Xr ssh 1 .
+ Please refer to those manual pages for details.
+ .Sh FILES
+-.Bl -tag -width Ds
++.Bl -tag -width Ds -compact
+ .It Pa ~/.ssh/identity
+ Contains the protocol version 1 RSA authentication identity of the user.
+ This file should not be readable by anyone but the user.
+@@ -595,26 +597,11 @@ The contents of this file should be added to
+ on all machines
+ where the user wishes to log in using RSA authentication.
+ There is no need to keep the contents of this file secret.
++.Pp
+ .It Pa ~/.ssh/id_dsa
+-Contains the protocol version 2 DSA authentication identity of the user.
+-This file should not be readable by anyone but the user.
+-It is possible to
+-specify a passphrase when generating the key; that passphrase will be
+-used to encrypt the private part of this file using 3DES.
+-This file is not automatically accessed by
+-.Nm
+-but it is offered as the default file for the private key.
+-.Xr ssh 1
+-will read this file when a login attempt is made.
+-.It Pa ~/.ssh/id_dsa.pub
+-Contains the protocol version 2 DSA public key for authentication.
+-The contents of this file should be added to
+-.Pa ~/.ssh/authorized_keys
+-on all machines
+-where the user wishes to log in using public key authentication.
+-There is no need to keep the contents of this file secret.
++.It Pa ~/.ssh/id_ecdsa
+ .It Pa ~/.ssh/id_rsa
+-Contains the protocol version 2 RSA authentication identity of the user.
++Contains the protocol version 2 DSA, ECDSA or RSA authentication identity of the user.
+ This file should not be readable by anyone but the user.
+ It is possible to
+ specify a passphrase when generating the key; that passphrase will be
+@@ -624,13 +611,17 @@ This file is not automatically accessed by
+ but it is offered as the default file for the private key.
+ .Xr ssh 1
+ will read this file when a login attempt is made.
++.Pp
++.It Pa ~/.ssh/id_dsa.pub
++.It Pa ~/.ssh/id_ecdsa.pub
+ .It Pa ~/.ssh/id_rsa.pub
+-Contains the protocol version 2 RSA public key for authentication.
++Contains the protocol version 2 DSA, ECDSA or RSA public key for authentication.
+ The contents of this file should be added to
+ .Pa ~/.ssh/authorized_keys
+ on all machines
+ where the user wishes to log in using public key authentication.
+ There is no need to keep the contents of this file secret.
++.Pp
+ .It Pa /etc/moduli
+ Contains Diffie-Hellman groups used for DH-GEX.
+ The file format is described in
+diff --git a/ssh-keygen.c b/ssh-keygen.c
+index c9eefb3..c3dad3b 100644
+--- a/ssh-keygen.c
++++ b/ssh-keygen.c
+@@ -63,6 +63,7 @@
+ /* Number of bits in the RSA/DSA key.  This value can be set on the command line. */
+ #define DEFAULT_BITS		2048
+ #define DEFAULT_BITS_DSA	1024
++#define DEFAULT_BITS_ECDSA	256
+ u_int32_t bits = 0;
+ 
+ /*
+@@ -174,6 +175,10 @@ ask_filename(struct passwd *pw, const char *prompt)
+ 		case KEY_DSA:
+ 			name = _PATH_SSH_CLIENT_ID_DSA;
+ 			break;
++		case KEY_ECDSA_CERT:
++		case KEY_ECDSA:
++			name = _PATH_SSH_CLIENT_ID_ECDSA;
++			break;
+ 		case KEY_RSA_CERT:
+ 		case KEY_RSA_CERT_V00:
+ 		case KEY_RSA:
+@@ -460,11 +465,29 @@ do_convert_from_ssh2(struct passwd *pw)
+ 		fprintf(stderr, "decode blob failed.\n");
+ 		exit(1);
+ 	}
+-	ok = private ?
+-	    (k->type == KEY_DSA ?
+-		 PEM_write_DSAPrivateKey(stdout, k->dsa, NULL, NULL, 0, NULL, NULL) :
+-		 PEM_write_RSAPrivateKey(stdout, k->rsa, NULL, NULL, 0, NULL, NULL)) :
+-	    key_write(k, stdout);
++	if (!private)
++		ok = key_write(k, stdout);
++	else {
++		switch (k->type) {
++		case KEY_DSA:
++			ok = PEM_write_DSAPrivateKey(stdout, k->dsa, NULL, NULL, 0, NULL, NULL);
++			break;
++#ifdef OPENSSL_HAS_ECC
++		case KEY_ECDSA:
++			ok = PEM_write_ECPrivateKey(stdout, k->ecdsa, NULL,
++			    NULL, 0, NULL, NULL);
++			break;
++#endif
++		case KEY_RSA:
++			ok = PEM_write_RSAPrivateKey(stdout, k->rsa, NULL,
++			    NULL, 0, NULL, NULL);
++			break;
++		default:
++			fatal("%s: unsupported key type %s", __func__,
++			    key_type(k));
++		}
++	}
++
+ 	if (!ok) {
+ 		fprintf(stderr, "key write failed\n");
+ 		exit(1);
+@@ -1234,7 +1257,8 @@ do_ca_sign(struct passwd *pw, int argc, char **argv)
+ 		tmp = tilde_expand_filename(argv[i], pw->pw_uid);
+ 		if ((public = key_load_public(tmp, &comment)) == NULL)
+ 			fatal("%s: unable to open \"%s\"", __func__, tmp);
+-		if (public->type != KEY_RSA && public->type != KEY_DSA)
++		if (public->type != KEY_RSA && public->type != KEY_DSA &&
++		    public->type != KEY_ECDSA)
+ 			fatal("%s: key \"%s\" type %s cannot be certified",
+ 			    __func__, tmp, key_type(public));
+ 
+@@ -1623,7 +1647,7 @@ main(int argc, char **argv)
+ 	    "degiqpclnBHLhvxXyF:b:f:t:D:I:N:P:O:C:r:g:R:T:G:M:S:Z:s:a:V:W:z:")) != -1) {
+ 		switch (opt) {
+ 		case 'b':
+-			bits = (u_int32_t)strtonum(optarg, 768, 32768, &errstr);
++			bits = (u_int32_t)strtonum(optarg, 256, 32768, &errstr);
+ 			if (errstr)
+ 				fatal("Bits has bad value %s (%s)",
+ 					optarg, errstr);
+@@ -1909,10 +1933,21 @@ main(int argc, char **argv)
+ 	}
+ 	if (type == KEY_DSA && FIPS_mode())
+ 		fatal("DSA keys are not allowed in FIPS mode");
+-	if (bits == 0)
+-		bits = (type == KEY_DSA) ? DEFAULT_BITS_DSA : DEFAULT_BITS;
++	if (bits == 0) {
++		if (type == KEY_DSA)
++			bits = DEFAULT_BITS_DSA;
++		else if (type == KEY_ECDSA)
++			bits = DEFAULT_BITS_ECDSA;
++		else
++			bits = DEFAULT_BITS;
++	}
+ 	if (type == KEY_DSA && bits != 1024)
+ 		fatal("DSA keys must be 1024 bits");
++	else if (type != KEY_ECDSA && bits < 768)
++		fatal("Key must at least be 768 bits");
++	else if (type == KEY_ECDSA && key_ecdsa_bits_to_nid(bits) == -1)
++		fatal("Invalid ECDSA key length - valid lengths are "
++		    "256, 384 or 521 bits");
+ 	if (!quiet)
+ 		printf("Generating public/private %s key pair.\n", key_type_name);
+ 	private = key_generate(type, bits);
+diff --git a/ssh-keyscan.1 b/ssh-keyscan.1
+index 4a58645..6685fea 100644
+--- a/ssh-keyscan.1
++++ b/ssh-keyscan.1
+@@ -88,9 +88,10 @@ Specifies the type of the key to fetch from the scanned hosts.
+ The possible values are
+ .Dq rsa1
+ for protocol version 1 and
+-.Dq rsa
++.Dq dsa ,
++.Dq ecdsa
+ or
+-.Dq dsa
++.Dq rsa
+ for protocol version 2.
+ Multiple values may be specified by separating them with commas.
+ The default is
+@@ -122,7 +123,7 @@ attacks which have begun after the ssh_known_hosts file was created.
+ host-or-namelist bits exponent modulus
+ .Ed
+ .Pp
+-.Pa Output format for rsa and dsa keys:
++.Pa Output format for rsa, dsa and ecdsa keys:
+ .Bd -literal
+ host-or-namelist keytype base64-encoded-key
+ .Ed
+@@ -130,9 +131,12 @@ host-or-namelist keytype base64-encoded-key
+ Where
+ .Pa keytype
+ is either
+-.Dq ssh-rsa
++.Dq ecdsa-sha2-nistp256 ,
++.Dq ecdsa-sha2-nistp384 ,
++.Dq ecdsa-sha2-nistp521 ,
++.Dq ssh-dss
+ or
+-.Dq ssh-dss .
++.Dq ssh-rsa .
+ .Pp
+ .Pa /etc/ssh/ssh_known_hosts
+ .Sh EXAMPLES
+@@ -149,7 +153,7 @@ Find all hosts from the file
+ which have new or different keys from those in the sorted file
+ .Pa ssh_known_hosts :
+ .Bd -literal
+-$ ssh-keyscan -t rsa,dsa -f ssh_hosts | \e
++$ ssh-keyscan -t rsa,dsa,ecdsa -f ssh_hosts | \e
+ 	sort -u - ssh_known_hosts | diff ssh_known_hosts -
+ .Ed
+ .Sh SEE ALSO
+diff --git a/ssh-keyscan.c b/ssh-keyscan.c
+index 9a91be4..c98f44f 100644
+--- a/ssh-keyscan.c
++++ b/ssh-keyscan.c
+@@ -52,9 +52,10 @@ int IPv4or6 = AF_UNSPEC;
+ 
+ int ssh_port = SSH_DEFAULT_PORT;
+ 
+-#define KT_RSA1	1
+-#define KT_DSA	2
+-#define KT_RSA	4
++#define KT_RSA1		1
++#define KT_DSA		2
++#define KT_RSA		4
++#define KT_ECDSA	8
+ 
+ int get_keytypes = KT_RSA;	/* Get only RSA keys by default */
+ 
+@@ -367,6 +368,7 @@ keygrab_ssh2(con *c)
+ 	c->c_kex->kex[KEX_DH_GRP14_SHA1] = kexdh_client;
+ 	c->c_kex->kex[KEX_DH_GEX_SHA1] = kexgex_client;
+ 	c->c_kex->kex[KEX_DH_GEX_SHA256] = kexgex_client;
++	c->c_kex->kex[KEX_ECDH_SHA2] = kexecdh_client;
+ 	c->c_kex->verify_host_key = hostjump;
+ 
+ 	if (!(j = setjmp(kexjmp))) {
+@@ -787,6 +789,9 @@ main(int argc, char **argv)
+ 				case KEY_DSA:
+ 					get_keytypes |= KT_DSA;
+ 					break;
++				case KEY_ECDSA:
++					get_keytypes |= KT_ECDSA;
++					break;
+ 				case KEY_RSA:
+ 					get_keytypes |= KT_RSA;
+ 					break;
+diff --git a/ssh-keysign.8 b/ssh-keysign.8
+index 2c1093c..2c6b56d 100644
+--- a/ssh-keysign.8
++++ b/ssh-keysign.8
+@@ -60,7 +60,7 @@ for more information about host-based authentication.
+ Controls whether
+ .Nm
+ is enabled.
+-.It Pa /etc/ssh/ssh_host_dsa_key, /etc/ssh/ssh_host_rsa_key
++.It Pa /etc/ssh/ssh_host_dsa_key, /etc/ssh/ssh_host_ecdsa_key, /etc/ssh/ssh_host_rsa_key
+ These files contain the private parts of the host keys used to
+ generate the digital signature.
+ They should be owned by root, readable only by root, and not
+diff --git a/ssh.1 b/ssh.1
+index c8327cf..b4ebf2b 100644
+--- a/ssh.1
++++ b/ssh.1
+@@ -293,13 +293,14 @@ This option is only available if support for PKCS#11
+ is compiled in (default is no support).
+ .It Fl i Ar identity_file
+ Selects a file from which the identity (private key) for
+-RSA or DSA authentication is read.
++public key authentication is read.
+ The default is
+ .Pa ~/.ssh/identity
+ for protocol version 1, and
+-.Pa ~/.ssh/id_rsa
++.Pa ~/.ssh/id_dsa ,
++.Pa ~/.ssh/id_ecdsa
+ and
+-.Pa ~/.ssh/id_dsa
++.Pa ~/.ssh/id_rsa
+ for protocol version 2.
+ Identity files may also be specified on
+ a per-host basis in the configuration file.
+@@ -757,9 +758,9 @@ key pair for authentication purposes.
+ The server knows the public key, and only the user knows the private key.
+ .Nm
+ implements public key authentication protocol automatically,
+-using either the RSA or DSA algorithms.
++using one of the DSA, ECDSA or RSA algorithms.
+ Protocol 1 is restricted to using only RSA keys,
+-but protocol 2 may use either.
++but protocol 2 may use any.
+ The
+ .Sx HISTORY
+ section of
+@@ -784,6 +785,8 @@ This stores the private key in
+ (protocol 1),
+ .Pa ~/.ssh/id_dsa
+ (protocol 2 DSA),
++.Pa ~/.ssh/id_ecdsa
++(protocol 2 ECDSA),
+ or
+ .Pa ~/.ssh/id_rsa
+ (protocol 2 RSA)
+@@ -792,6 +795,8 @@ and stores the public key in
+ (protocol 1),
+ .Pa ~/.ssh/id_dsa.pub
+ (protocol 2 DSA),
++.Pa ~/.ssh/id_ecdsa.pub
++(protocol 2 ECDSA),
+ or
+ .Pa ~/.ssh/id_rsa.pub
+ (protocol 2 RSA)
+@@ -1330,7 +1335,8 @@ secret, but the recommended permissions are read/write/execute for the user,
+ and not accessible by others.
+ .Pp
+ .It ~/.ssh/authorized_keys
+-Lists the public keys (RSA/DSA) that can be used for logging in as this user.
++Lists the public keys (RSA/ECDSA/DSA) that can be used for logging in as
++this user.
+ The format of this file is described in the
+ .Xr sshd 8
+ manual page.
+@@ -1351,6 +1357,7 @@ above.
+ .Pp
+ .It ~/.ssh/identity
+ .It ~/.ssh/id_dsa
++.It ~/.ssh/id_ecdsa
+ .It ~/.ssh/id_rsa
+ Contains the private key for authentication.
+ These files
+@@ -1364,6 +1371,7 @@ sensitive part of this file using 3DES.
+ .Pp
+ .It ~/.ssh/identity.pub
+ .It ~/.ssh/id_dsa.pub
++.It ~/.ssh/id_ecdsa.pub
+ .It ~/.ssh/id_rsa.pub
+ Contains the public key for authentication.
+ These files are not
+@@ -1402,6 +1410,7 @@ The file format and configuration options are described in
+ .Pp
+ .It /etc/ssh/ssh_host_key
+ .It /etc/ssh/ssh_host_dsa_key
++.It /etc/ssh/ssh_host_ecdsa_key
+ .It /etc/ssh/ssh_host_rsa_key
+ These three files contain the private parts of the host keys
+ and are used for host-based authentication.
+@@ -1513,6 +1522,11 @@ IPv6 address can be used everywhere where IPv4 address. In all entries must be t
+ .%D 2006
+ .Re
+ .Rs
++.%R RFC 5656
++.%T "Elliptic Curve Algorithm Integration in the Secure Shell Transport Layer"
++.%D 2009
++.Re
++.Rs
+ .%T "Hash Visualization: a New Technique to improve Real-World Security"
+ .%A A. Perrig
+ .%A D. Song
+diff --git a/ssh.c b/ssh.c
+index 5153d53..26a0d75 100644
+--- a/ssh.c
++++ b/ssh.c
+@@ -778,26 +778,37 @@ main(int ac, char **av)
+ 	sensitive_data.external_keysign = 0;
+ 	if (options.rhosts_rsa_authentication ||
+ 	    options.hostbased_authentication) {
+-		sensitive_data.nkeys = 3;
++		sensitive_data.nkeys = 4;
+ 		sensitive_data.keys = xcalloc(sensitive_data.nkeys,
+ 		    sizeof(Key));
++		for (i = 0; i < sensitive_data.nkeys; i++)
++			sensitive_data.keys[i] = NULL;
+ 
+ 		PRIV_START;
+ 		sensitive_data.keys[0] = key_load_private_type(KEY_RSA1,
+ 		    _PATH_HOST_KEY_FILE, "", NULL, NULL);
+ 		sensitive_data.keys[1] = key_load_private_type(KEY_DSA,
+ 		    _PATH_HOST_DSA_KEY_FILE, "", NULL, NULL);
+-		sensitive_data.keys[2] = key_load_private_type(KEY_RSA,
++#ifdef OPENSSL_HAS_ECC
++		sensitive_data.keys[2] = key_load_private_type(KEY_ECDSA,
++		    _PATH_HOST_ECDSA_KEY_FILE, "", NULL, NULL);
++#endif
++		sensitive_data.keys[3] = key_load_private_type(KEY_RSA,
+ 		    _PATH_HOST_RSA_KEY_FILE, "", NULL, NULL);
+ 		PRIV_END;
+ 
+ 		if (options.hostbased_authentication == 1 &&
+ 		    sensitive_data.keys[0] == NULL &&
+ 		    sensitive_data.keys[1] == NULL &&
+-		    sensitive_data.keys[2] == NULL) {
++		    sensitive_data.keys[2] == NULL &&
++		    sensitive_data.keys[3] == NULL) {
+ 			sensitive_data.keys[1] = key_load_public(
+ 			    _PATH_HOST_DSA_KEY_FILE, NULL);
++#ifdef OPENSSL_HAS_ECC
+ 			sensitive_data.keys[2] = key_load_public(
++			    _PATH_HOST_ECDSA_KEY_FILE, NULL);
++#endif
++			sensitive_data.keys[3] = key_load_public(
+ 			    _PATH_HOST_RSA_KEY_FILE, NULL);
+ 			sensitive_data.external_keysign = 1;
+ 		}
+diff --git a/ssh2.h b/ssh2.h
+index e21a188..4436029 100644
+--- a/ssh2.h
++++ b/ssh2.h
+@@ -98,6 +98,10 @@
+ #define SSH2_MSG_KEX_DH_GEX_REPLY			33
+ #define SSH2_MSG_KEX_DH_GEX_REQUEST			34
+ 
++/* ecdh */
++#define SSH2_MSG_KEX_ECDH_INIT				30
++#define SSH2_MSG_KEX_ECDH_REPLY				31
++
+ /* user authentication: generic */
+ 
+ #define SSH2_MSG_USERAUTH_REQUEST			50
+diff --git a/ssh_config.5 b/ssh_config.5
+index b6b1092..0dd1178 100644
+--- a/ssh_config.5
++++ b/ssh_config.5
+@@ -543,7 +543,11 @@ is similar to
+ Specifies the protocol version 2 host key algorithms
+ that the client wants to use in order of preference.
+ The default for this option is:
+-.Dq ssh-rsa,ssh-dss .
++.Bd -literal -offset 3n
++ssh-rsa-cert-v01 at openssh.com,ssh-dss-cert-v01 at openssh.com,
++ssh-rsa-cert-v00 at openssh.com,ssh-dss-cert-v00 at openssh.com,
++ssh-rsa,ssh-dss
++.Ed
+ .It Cm HostKeyAlias
+ Specifies an alias that should be used instead of the
+ real host name when looking up or saving the host key
+@@ -575,14 +579,15 @@ offers many different identities.
+ The default is
+ .Dq no .
+ .It Cm IdentityFile
+-Specifies a file from which the user's RSA or DSA authentication identity
+-is read.
++Specifies a file from which the user's DSA, ECDSA or DSA authentication
++identity is read.
+ The default is
+ .Pa ~/.ssh/identity
+ for protocol version 1, and
+-.Pa ~/.ssh/id_rsa
++.Pa ~/.ssh/id_dsa ,
++.Pa ~/.ssh/id_ecdsa
+ and
+-.Pa ~/.ssh/id_dsa
++.Pa ~/.ssh/id_rsa
+ for protocol version 2.
+ Additionally, any identities represented by the authentication agent
+ will be used for authentication.
+@@ -633,9 +638,6 @@ and
+ Specifies the available KEX (Key Exchange) algorithms.
+ Multiple algorithms must be comma-separated.
+ The default is
+-.Dq ecdh-sha2-nistp256 ,
+-.Dq ecdh-sha2-nistp384 ,
+-.Dq ecdh-sha2-nistp521 ,
+ .Dq diffie-hellman-group-exchange-sha256 , 
+ .Dq diffie-hellman-group-exchange-sha1 ,
+ .Dq diffie-hellman-group14-sha1 ,
+diff --git a/sshconnect.c b/sshconnect.c
+index 11965ab..8b601fd 100644
+--- a/sshconnect.c
++++ b/sshconnect.c
+@@ -1178,7 +1178,7 @@ show_key_from_file(const char *file, const char *host, int keytype)
+ static int
+ show_other_keys(const char *host, Key *key)
+ {
+-	int type[] = { KEY_RSA1, KEY_RSA, KEY_DSA, -1};
++	int type[] = { KEY_RSA1, KEY_RSA, KEY_DSA, KEY_ECDSA, -1};
+ 	int i, found = 0;
+ 
+ 	for (i = 0; type[i] != -1; i++) {
+diff --git a/sshconnect2.c b/sshconnect2.c
+index b311d01..54c3248 100644
+--- a/sshconnect2.c
++++ b/sshconnect2.c
+@@ -198,6 +198,7 @@ ssh_kex2(char *host, struct sockaddr *hostaddr)
+ 	kex->kex[KEX_DH_GRP14_SHA1] = kexdh_client;
+ 	kex->kex[KEX_DH_GEX_SHA1] = kexgex_client;
+ 	kex->kex[KEX_DH_GEX_SHA256] = kexgex_client;
++	kex->kex[KEX_ECDH_SHA2] = kexecdh_client;
+ #ifdef GSSAPI
+ 	if (options.gss_keyex) {
+ 		kex->kex[KEX_GSS_GRP1_SHA1] = kexgss_client;
+diff --git a/sshd.8 b/sshd.8
+index bff4d96..65ea8d2 100644
+--- a/sshd.8
++++ b/sshd.8
+@@ -170,9 +170,10 @@ host key files are normally not readable by anyone but root).
+ The default is
+ .Pa /etc/ssh/ssh_host_key
+ for protocol version 1, and
+-.Pa /etc/ssh/ssh_host_rsa_key
++.Pa /etc/ssh/ssh_host_dsa_key ,
++.Pa /etc/ssh/ssh_host_ecdsa_key
+ and
+-.Pa /etc/ssh/ssh_host_dsa_key
++.Pa /etc/ssh/ssh_host_rsa_key
+ for protocol version 2.
+ It is possible to have multiple host key files for
+ the different protocol versions and host key algorithms.
+@@ -275,7 +276,7 @@ though this can be changed via the
+ .Cm Protocol
+ option in
+ .Xr sshd_config 5 .
+-Protocol 2 supports both RSA and DSA keys;
++Protocol 2 supports DSA, ECDSA and RSA keys;
+ protocol 1 only supports RSA keys.
+ For both protocols,
+ each host has a host-specific key,
+@@ -484,6 +485,9 @@ protocol version 1; the
+ comment field is not used for anything (but may be convenient for the
+ user to identify the key).
+ For protocol version 2 the keytype is
++.Dq ecdsa-sha2-nistp256 ,
++.Dq ecdsa-sha2-nistp384 ,
++.Dq ecdsa-sha2-nistp521 ,
+ .Dq ssh-dss
+ or
+ .Dq ssh-rsa .
+@@ -495,6 +499,7 @@ keys up to 16 kilobits.
+ You don't want to type them in; instead, copy the
+ .Pa identity.pub ,
+ .Pa id_dsa.pub ,
++.Pa id_ecdsa.pub ,
+ or the
+ .Pa id_rsa.pub
+ file and edit it.
+@@ -794,7 +799,7 @@ secret, but the recommended permissions are read/write/execute for the user,
+ and not accessible by others.
+ .Pp
+ .It ~/.ssh/authorized_keys
+-Lists the public keys (RSA/DSA) that can be used for logging in as this user.
++Lists the public keys (RSA/ECDSA/DSA) that can be used for logging in as this user.
+ The format of this file is described above.
+ The content of the file is not highly sensitive, but the recommended
+ permissions are read/write for the user, and not accessible by others.
+@@ -873,6 +878,7 @@ rlogin/rsh.
+ .Pp
+ .It /etc/ssh/ssh_host_key
+ .It /etc/ssh/ssh_host_dsa_key
++.It /etc/ssh/ssh_host_ecdsa_key
+ .It /etc/ssh/ssh_host_rsa_key
+ These three files contain the private parts of the host keys.
+ These files should only be owned by root, readable only by root, and not
+@@ -883,6 +889,7 @@ does not start if these files are group/world-accessible.
+ .Pp
+ .It /etc/ssh/ssh_host_key.pub
+ .It /etc/ssh/ssh_host_dsa_key.pub
++.It /etc/ssh/ssh_host_ecdsa_key.pub
+ .It /etc/ssh/ssh_host_rsa_key.pub
+ These three files contain the public parts of the host keys.
+ These files should be world-readable but writable only by
+diff --git a/sshd.c b/sshd.c
+index 106b494..e7d5033 100644
+--- a/sshd.c
++++ b/sshd.c
+@@ -800,6 +800,7 @@ list_hostkey_types(void)
+ 		switch (key->type) {
+ 		case KEY_RSA:
+ 		case KEY_DSA:
++		case KEY_ECDSA:
+ 			if (buffer_len(&b) > 0)
+ 				buffer_append(&b, ",", 1);
+ 			p = key_ssh_name(key);
+@@ -815,6 +816,7 @@ list_hostkey_types(void)
+ 		case KEY_DSA_CERT_V00:
+ 		case KEY_RSA_CERT:
+ 		case KEY_DSA_CERT:
++		case KEY_ECDSA_CERT:
+ 			if (buffer_len(&b) > 0)
+ 				buffer_append(&b, ",", 1);
+ 			p = key_ssh_name(key);
+@@ -841,6 +843,7 @@ get_hostkey_by_type(int type, int need_private)
+ 		case KEY_DSA_CERT_V00:
+ 		case KEY_RSA_CERT:
+ 		case KEY_DSA_CERT:
++		case KEY_ECDSA_CERT:
+ 			key = sensitive_data.host_certificates[i];
+ 			break;
+ 		default:
+@@ -1657,6 +1660,7 @@ main(int ac, char **av)
+ 			break;
+ 		case KEY_RSA:
+ 		case KEY_DSA:
++		case KEY_ECDSA:
+ 			sensitive_data.have_ssh2_key = 1;
+ 			break;
+ 		}
+@@ -2518,6 +2522,7 @@ do_ssh2_kex(void)
+ 	kex->kex[KEX_DH_GRP14_SHA1] = kexdh_server;
+ 	kex->kex[KEX_DH_GEX_SHA1] = kexgex_server;
+ 	kex->kex[KEX_DH_GEX_SHA256] = kexgex_server;
++	kex->kex[KEX_ECDH_SHA2] = kexecdh_server;
+ #ifdef GSSAPI
+ 	if (options.gss_keyex) {
+ 		kex->kex[KEX_GSS_GRP1_SHA1] = kexgss_server;
+diff --git a/sshd_config.5 b/sshd_config.5
+index 035dea9..9d76bd0 100644
+--- a/sshd_config.5
++++ b/sshd_config.5
+@@ -489,9 +489,10 @@ used by SSH.
+ The default is
+ .Pa /etc/ssh/ssh_host_key
+ for protocol version 1, and
+-.Pa /etc/ssh/ssh_host_rsa_key
++.Pa /etc/ssh/ssh_host_dsa_key ,
++.Pa /etc/ssh/ssh_host_ecdsa_key
+ and
+-.Pa /etc/ssh/ssh_host_dsa_key
++.Pa /etc/ssh/ssh_host_rsa_key
+ for protocol version 2.
+ Note that
+ .Xr sshd 8
+@@ -499,7 +500,8 @@ will refuse to use a file if it is group/world-accessible.
+ It is possible to have multiple host key files.
+ .Dq rsa1
+ keys are used for version 1 and
+-.Dq dsa
++.Dq dsa ,
++.Dq ecdsa
+ or
+ .Dq rsa
+ are used for version 2 of the SSH protocol.
+diff --git a/uuencode.c b/uuencode.c
+index b9e57e9..09d80d2 100644
+--- a/uuencode.c
++++ b/uuencode.c
+@@ -1,4 +1,4 @@
+-/* $OpenBSD: uuencode.c,v 1.25 2009/03/05 11:30:50 djm Exp $ */
++/* $OpenBSD: uuencode.c,v 1.26 2010/08/31 11:54:45 djm Exp $ */
+ /*
+  * Copyright (c) 2000 Markus Friedl.  All rights reserved.
+  *
+@@ -72,7 +72,7 @@ uudecode(const char *src, u_char *target, size_t targsize)
+ }
+ 
+ void
+-dump_base64(FILE *fp, u_char *data, u_int len)
++dump_base64(FILE *fp, const u_char *data, u_int len)
+ {
+ 	char *buf;
+ 	int i, n;
+diff --git a/uuencode.h b/uuencode.h
+index fec55b4..4d98881 100644
+--- a/uuencode.h
++++ b/uuencode.h
+@@ -1,4 +1,4 @@
+-/* $OpenBSD: uuencode.h,v 1.13 2006/08/03 03:34:42 deraadt Exp $ */
++/* $OpenBSD: uuencode.h,v 1.14 2010/08/31 11:54:45 djm Exp $ */
+ 
+ /*
+  * Copyright (c) 2000 Markus Friedl.  All rights reserved.
+@@ -26,4 +26,4 @@
+ 
+ int	 uuencode(const u_char *, u_int, char *, size_t);
+ int	 uudecode(const char *, u_char *, size_t);
+-void	 dump_base64(FILE *, u_char *, u_int);
++void	 dump_base64(FILE *, const u_char *, u_int);
+-- 
+1.8.3.1
+
diff --git a/openssh-5.3p1-fips-dont-load-rsa1-keys.patch b/openssh-5.3p1-fips-dont-load-rsa1-keys.patch
new file mode 100644
index 0000000..275a558
--- /dev/null
+++ b/openssh-5.3p1-fips-dont-load-rsa1-keys.patch
@@ -0,0 +1,16 @@
+diff --git a/authfile.c b/authfile.c
+index d275296..7cf1c6d 100644
+--- a/authfile.c
++++ b/authfile.c
+@@ -652,7 +652,10 @@ key_load_private(const char *filename, const char *passphrase,
+ 		/* it's a SSH v1 key if the public key part is readable */
+ 		key_free(pub);
+ 		/* closes fd */
+-		prv = key_load_private_rsa1(fd, filename, passphrase, NULL);
++		if ( ! FIPS_mode())
++			prv = key_load_private_rsa1(fd, filename, passphrase, NULL);
++		else
++			close(fd);
+ 	}
+ 	return prv;
+ }
diff --git a/openssh-5.3p1-fips-syslog.patch b/openssh-5.3p1-fips-syslog.patch
new file mode 100644
index 0000000..148f11f
--- /dev/null
+++ b/openssh-5.3p1-fips-syslog.patch
@@ -0,0 +1,24 @@
+diff -up openssh-5.3p1/sshd.c.fips-syslog openssh-5.3p1/sshd.c
+--- openssh-5.3p1/sshd.c.fips-syslog	2014-03-20 08:06:34.779972343 +0100
++++ openssh-5.3p1/sshd.c	2014-03-20 08:08:55.468263792 +0100
+@@ -1383,11 +1383,15 @@ main(int ac, char **av)
+ 	__progname = ssh_get_progname(av[0]);
+ 
+         SSLeay_add_all_algorithms();
+-	if (access("/etc/system-fips", F_OK) == 0 && (fips = FIPSCHECK_verify(NULL, NULL)) == 0)
+-		if (FIPS_mode())
+-			fatal("FIPS integrity verification test failed.");
+-		else
+-			logit("FIPS integrity verification test failed.");
++	if (access("/etc/system-fips", F_OK) == 0 && (fips = FIPSCHECK_verify(NULL, NULL)) == 0) {
++		openlog(__progname, LOG_PID, LOG_AUTHPRIV);
++		if (FIPS_mode()) {
++			syslog(LOG_CRIT, "FIPS integrity verification test failed.");
++			cleanup_exit(255);
++		} else
++			syslog(LOG_INFO, "FIPS integrity verification test failed.");
++		closelog();
++	}
+ 
+ 	init_rng();
+ 
diff --git a/openssh-5.3p1-fips.patch b/openssh-5.3p1-fips.patch
index 1cec453..001bcb7 100644
--- a/openssh-5.3p1-fips.patch
+++ b/openssh-5.3p1-fips.patch
@@ -56,8 +56,8 @@ diff -up openssh-5.3p1/authfile.c.fips openssh-5.3p1/authfile.c
  	    buffer_ptr(&buffer), buffer_len(&buffer));
  	cipher_cleanup(&ciphercontext);
 diff -up openssh-5.3p1/cipher.c.fips openssh-5.3p1/cipher.c
---- openssh-5.3p1/cipher.c.fips	2009-10-02 13:44:03.000000000 +0200
-+++ openssh-5.3p1/cipher.c	2009-10-02 14:12:00.000000000 +0200
+--- openssh-5.3p1/cipher.c.fips	2009-10-02 15:23:26.000000000 +0200
++++ openssh-5.3p1/cipher.c	2014-03-20 08:37:06.542752102 +0100
 @@ -40,6 +40,7 @@
  #include <sys/types.h>
  
@@ -66,13 +66,12 @@ diff -up openssh-5.3p1/cipher.c.fips openssh-5.3p1/cipher.c
  
  #include <string.h>
  #include <stdarg.h>
-@@ -93,6 +94,22 @@ struct Cipher {
+@@ -93,6 +94,21 @@ struct Cipher {
  	{ NULL,			SSH_CIPHER_INVALID, 0, 0, 0, 0, NULL }
  };
  
 +struct Cipher fips_ciphers[] = {
 +	{ "none",		SSH_CIPHER_NONE, 8, 0, 0, 0, EVP_enc_null },
-+	{ "3des",		SSH_CIPHER_3DES, 8, 16, 0, 1, evp_ssh1_3des },
 +
 +	{ "3des-cbc",		SSH_CIPHER_SSH2, 8, 24, 0, 1, EVP_des_ede3_cbc },
 +	{ "aes128-cbc",		SSH_CIPHER_SSH2, 16, 16, 0, 1, EVP_aes_128_cbc },
@@ -89,7 +88,7 @@ diff -up openssh-5.3p1/cipher.c.fips openssh-5.3p1/cipher.c
  /*--*/
  
  u_int
-@@ -135,7 +152,7 @@ Cipher *
+@@ -135,7 +151,7 @@ Cipher *
  cipher_by_name(const char *name)
  {
  	Cipher *c;
@@ -98,7 +97,7 @@ diff -up openssh-5.3p1/cipher.c.fips openssh-5.3p1/cipher.c
  		if (strcmp(c->name, name) == 0)
  			return c;
  	return NULL;
-@@ -145,7 +162,7 @@ Cipher *
+@@ -145,7 +161,7 @@ Cipher *
  cipher_by_number(int id)
  {
  	Cipher *c;
@@ -107,7 +106,7 @@ diff -up openssh-5.3p1/cipher.c.fips openssh-5.3p1/cipher.c
  		if (c->number == id)
  			return c;
  	return NULL;
-@@ -189,7 +206,7 @@ cipher_number(const char *name)
+@@ -189,7 +205,7 @@ cipher_number(const char *name)
  	Cipher *c;
  	if (name == NULL)
  		return -1;
@@ -116,7 +115,7 @@ diff -up openssh-5.3p1/cipher.c.fips openssh-5.3p1/cipher.c
  		if (strcasecmp(c->name, name) == 0)
  			return c->number;
  	return -1;
-@@ -296,14 +313,15 @@ cipher_cleanup(CipherContext *cc)
+@@ -296,14 +312,15 @@ cipher_cleanup(CipherContext *cc)
   * passphrase and using the resulting 16 bytes as the key.
   */
  
@@ -134,7 +133,7 @@ diff -up openssh-5.3p1/cipher.c.fips openssh-5.3p1/cipher.c
  	MD5_Update(&md, (const u_char *)passphrase, strlen(passphrase));
  	MD5_Final(digest, &md);
  
-@@ -311,6 +329,7 @@ cipher_set_key_string(CipherContext *cc,
+@@ -311,6 +328,7 @@ cipher_set_key_string(CipherContext *cc,
  
  	memset(digest, 0, sizeof(digest));
  	memset(&md, 0, sizeof(md));
@@ -393,7 +392,7 @@ diff -up openssh-5.3p1/ssh.c.fips openssh-5.3p1/ssh.c
  	extern char *optarg;
  	struct servent *sp;
  	Forward fwd;
-+	int fips;
++	int fips = 0;
  
  	/* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
  	sanitise_stdfd();
@@ -401,11 +400,11 @@ diff -up openssh-5.3p1/ssh.c.fips openssh-5.3p1/ssh.c
  	__progname = ssh_get_progname(av[0]);
 +
 +        SSLeay_add_all_algorithms();
-+	fips = 0;
-+	if (access("/usr/share/dracut/modules.d/01fips", F_OK) == 0)
-+		fips = FIPSCHECK_verify(NULL, NULL);
-+	if (FIPS_mode() && !fips)
-+		fatal("FIPS integrity verification test failed.");
++	if (access("/etc/system-fips", F_OK) == 0 && (fips = FIPSCHECK_verify(NULL, NULL)) == 0)
++		if (FIPS_mode())
++			fatal("FIPS integrity verification test failed.");
++		else
++			logit("FIPS integrity verification test failed.");
 +
  	init_rng();
  
@@ -432,7 +431,7 @@ diff -up openssh-5.3p1/ssh.c.fips openssh-5.3p1/ssh.c
  
  	seed_rng();
  
-+	if (FIPS_mode()) {
++	if (FIPS_mode() && fips) {
 +		logit("FIPS mode initialized");
 +	}
 +
@@ -603,7 +602,7 @@ diff -up openssh-5.3p1/sshd.c.fips openssh-5.3p1/sshd.c
  	mode_t new_umask;
  	Key *key;
  	Authctxt *authctxt;
-+	int fips;
++	int fips = 0;
  
  #ifdef HAVE_SECUREWARE
  	(void)set_auth_parameters(ac, av);
@@ -611,11 +610,11 @@ diff -up openssh-5.3p1/sshd.c.fips openssh-5.3p1/sshd.c
  	__progname = ssh_get_progname(av[0]);
 +
 +        SSLeay_add_all_algorithms();
-+	fips = 0;
-+	if (access("/usr/share/dracut/modules.d/01fips", F_OK) == 0)
-+		fips = FIPSCHECK_verify(NULL, NULL);
-+	if (FIPS_mode() && !fips)
-+		fatal("FIPS integrity verification test failed.");
++	if (access("/etc/system-fips", F_OK) == 0 && (fips = FIPSCHECK_verify(NULL, NULL)) == 0)
++		if (FIPS_mode())
++			fatal("FIPS integrity verification test failed.");
++		else
++			logit("FIPS integrity verification test failed.");
 +
  	init_rng();
  
@@ -644,7 +643,7 @@ diff -up openssh-5.3p1/sshd.c.fips openssh-5.3p1/sshd.c
  	/* Initialize the random number generator. */
  	arc4random_stir();
  
-+	if (FIPS_mode()) {
++	if (FIPS_mode() && fips) {
 +		logit("FIPS mode initialized");
 +	}
 +
diff --git a/openssh-5.3p1-fix-several-coverity-issues.patch b/openssh-5.3p1-fix-several-coverity-issues.patch
new file mode 100644
index 0000000..402b01d
--- /dev/null
+++ b/openssh-5.3p1-fix-several-coverity-issues.patch
@@ -0,0 +1,93 @@
+diff --git a/authfile.c b/authfile.c
+index 7cf1c6d..b855020 100644
+--- a/authfile.c
++++ b/authfile.c
+@@ -629,7 +629,7 @@ Key *
+ key_load_private(const char *filename, const char *passphrase,
+     char **commentp)
+ {
+-	Key *pub, *prv;
++	Key *pub, *prv = NULL;
+ 	int fd;
+ 
+ 	fd = open(filename, O_RDONLY);
+diff --git a/mux.c b/mux.c
+index b1d282b..87d28ae 100644
+--- a/mux.c
++++ b/mux.c
+@@ -205,7 +205,7 @@ mux_master_control_cleanup_cb(int cid, void *unused)
+ 		fatal("%s: channel_by_id(%i) == NULL", __func__, cid);
+ 	if (c->remote_id != -1) {
+ 		if ((sc = channel_by_id(c->remote_id)) == NULL)
+-			debug2("%s: channel %d n session channel %d",
++			fatal("%s: channel %d missing session channel %d",
+ 			    __func__, c->self, c->remote_id);
+ 		c->remote_id = -1;
+ 		sc->ctl_chan = -1;
+@@ -392,6 +392,7 @@ process_mux_new_session(u_int rid, Channel *c, Buffer *m, Buffer *r)
+ 			xfree(cctx->env);
+ 		}
+ 		buffer_free(&cctx->cmd);
++		xfree(cctx);
+ 		return 0;
+ 	}
+ 
+@@ -840,7 +841,7 @@ mux_master_read_cb(Channel *c)
+ 
+ 	/* Setup ctx and  */
+ 	if (c->mux_ctx == NULL) {
+-		state = xcalloc(1, sizeof(state));
++		state = xcalloc(1, sizeof(*state));
+ 		c->mux_ctx = state;
+ 		channel_register_cleanup(c->self,
+ 		    mux_master_control_cleanup_cb, 0);
+diff --git a/ssh-agent.c b/ssh-agent.c
+index 0062235..0078919 100644
+--- a/ssh-agent.c
++++ b/ssh-agent.c
+@@ -476,7 +476,7 @@ process_add_identity(SocketEntry *e, int version)
+ #ifdef OPENSSL_HAS_ECC
+ 	BIGNUM *exponent;
+ 	EC_POINT *q;
+-	int *curve;
++	char *curve;
+ #endif
+ 	u_char *cert;
+ 	u_int len;
+diff --git a/ssh.c b/ssh.c
+index 0ee8efb..acc522a 100644
+--- a/ssh.c
++++ b/ssh.c
+@@ -242,11 +242,12 @@ main(int ac, char **av)
+ 	__progname = ssh_get_progname(av[0]);
+ 
+         SSLeay_add_all_algorithms();
+-	if (access("/etc/system-fips", F_OK) == 0 && (fips = FIPSCHECK_verify(NULL, NULL)) == 0)
++	if (access("/etc/system-fips", F_OK) == 0 && (fips = FIPSCHECK_verify(NULL, NULL)) == 0) {
+ 		if (FIPS_mode())
+ 			fatal("FIPS integrity verification test failed.");
+ 		else
+ 			logit("FIPS integrity verification test failed.");
++	}
+ 
+ 	init_rng();
+ 
+diff --git a/sshd.c b/sshd.c
+index a19b7d8..22b7638 100644
+--- a/sshd.c
++++ b/sshd.c
+@@ -2494,12 +2494,13 @@ do_ssh2_kex(void)
+ 	if (strlen(myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS]) == 0)
+ 		orig = NULL;
+ 
+-	if (options.gss_keyex)
++	if (options.gss_keyex) {
+ 		if (FIPS_mode()) {
+ 			logit("Disabling GSSAPIKeyExchange. Not usable in FIPS mode");
+ 			options.gss_keyex = 0;
+ 		} else
+ 			gss = ssh_gssapi_server_mechanisms();
++	}
+ 
+ 	if (gss && orig)
+ 		xasprintf(&newstr, "%s,%s", gss, orig);
diff --git a/openssh-5.3p1-gsissh.patch b/openssh-5.3p1-gsissh.patch
index c286e66..1adbb52 100644
--- a/openssh-5.3p1-gsissh.patch
+++ b/openssh-5.3p1-gsissh.patch
@@ -1282,7 +1282,7 @@ diff -Nur openssh-5.3p1.orig/Makefile.in openssh-5.3p1/Makefile.in
 --- openssh-5.3p1.orig/Makefile.in	2013-11-26 19:13:13.585341262 +0100
 +++ openssh-5.3p1/Makefile.in	2013-11-26 19:47:08.349685587 +0100
 @@ -93,8 +93,10 @@
- 	monitor_mm.o monitor.o monitor_wrap.o kexdhs.o kexgexs.o \
+ 	monitor_mm.o monitor.o monitor_wrap.o kexdhs.o kexgexs.o kexecdhs.o \
  	auth-krb5.o audit-linux.o \
   	auth2-gss.o gss-serv.o gss-serv-krb5.o kexgsss.o\
 +	gss-serv-gsi.o \
diff --git a/openssh-5.3p1-gsskex-fips.patch b/openssh-5.3p1-gsskex-fips.patch
new file mode 100644
index 0000000..ac306ee
--- /dev/null
+++ b/openssh-5.3p1-gsskex-fips.patch
@@ -0,0 +1,59 @@
+diff -up openssh-5.3p1/sshconnect2.c.gsskex-fips openssh-5.3p1/sshconnect2.c
+--- openssh-5.3p1/sshconnect2.c.gsskex-fips	2014-03-20 07:40:31.271855339 +0100
++++ openssh-5.3p1/sshconnect2.c	2014-03-20 07:47:55.207615134 +0100
+@@ -120,20 +120,25 @@ ssh_kex2(char *host, struct sockaddr *ho
+ 
+ #ifdef GSSAPI
+ 	if (options.gss_keyex) {
+-		/* Add the GSSAPI mechanisms currently supported on this 
+-		 * client to the key exchange algorithm proposal */
+-		orig = myproposal[PROPOSAL_KEX_ALGS];
++		if (FIPS_mode()) {
++			logit("Disabling GSSAPIKeyExchange. Not usable in FIPS mode");
++			options.gss_keyex = 0;
++		} else {
++			/* Add the GSSAPI mechanisms currently supported on this
++			 * client to the key exchange algorithm proposal */
++			orig = myproposal[PROPOSAL_KEX_ALGS];
+ 
+-		if (options.gss_trust_dns)
+-			gss_host = (char *)get_canonical_hostname(1);
+-		else
+-			gss_host = host;
++			if (options.gss_trust_dns)
++				gss_host = (char *)get_canonical_hostname(1);
++			else
++				gss_host = host;
+ 
+-		gss = ssh_gssapi_client_mechanisms(gss_host, options.gss_client_identity);
+-		if (gss) {
+-			debug("Offering GSSAPI proposal: %s", gss);
+-			xasprintf(&myproposal[PROPOSAL_KEX_ALGS],
+-			    "%s,%s", gss, orig);
++			gss = ssh_gssapi_client_mechanisms(gss_host, options.gss_client_identity);
++			if (gss) {
++				debug("Offering GSSAPI proposal: %s", gss);
++				xasprintf(&myproposal[PROPOSAL_KEX_ALGS],
++				    "%s,%s", gss, orig);
++			}
+ 		}
+ 	}
+ #endif
+diff -up openssh-5.3p1/sshd.c.gsskex-fips openssh-5.3p1/sshd.c
+--- openssh-5.3p1/sshd.c.gsskex-fips	2014-03-20 07:40:31.272855334 +0100
++++ openssh-5.3p1/sshd.c	2014-03-20 07:43:36.835918763 +0100
+@@ -2490,9 +2490,11 @@ do_ssh2_kex(void)
+ 		orig = NULL;
+ 
+ 	if (options.gss_keyex)
+-		gss = ssh_gssapi_server_mechanisms();
+-	else
+-		gss = NULL;
++		if (FIPS_mode()) {
++			logit("Disabling GSSAPIKeyExchange. Not usable in FIPS mode");
++			options.gss_keyex = 0;
++		} else
++			gss = ssh_gssapi_server_mechanisms();
+ 
+ 	if (gss && orig)
+ 		xasprintf(&newstr, "%s,%s", gss, orig);
diff --git a/openssh-5.3p1-ignore-SIGXFSZ.patch b/openssh-5.3p1-ignore-SIGXFSZ.patch
new file mode 100644
index 0000000..57e8c8a
--- /dev/null
+++ b/openssh-5.3p1-ignore-SIGXFSZ.patch
@@ -0,0 +1,14 @@
+diff --git a/monitor.c b/monitor.c
+index 13f5f55..333e415 100644
+--- a/monitor.c
++++ b/monitor.c
+@@ -496,6 +496,9 @@ monitor_child_postauth(struct monitor *pmonitor)
+ 	signal(SIGHUP, &monitor_child_handler);
+ 	signal(SIGTERM, &monitor_child_handler);
+ 	signal(SIGINT, &monitor_child_handler);
++#ifdef SIGXFSZ
++	signal(SIGXFSZ, SIG_IGN);
++#endif
+ 
+ 	if (compat20) {
+ 		mon_dispatch = mon_dispatch_postauth20;
diff --git a/openssh-5.3p1-ignore-bad-env-var.patch b/openssh-5.3p1-ignore-bad-env-var.patch
new file mode 100644
index 0000000..40aeab8
--- /dev/null
+++ b/openssh-5.3p1-ignore-bad-env-var.patch
@@ -0,0 +1,136 @@
+diff --git a/ChangeLog b/ChangeLog
+index b7a4563..c25fb32 100644
+--- a/ChangeLog
++++ b/ChangeLog
+@@ -1,3 +1,10 @@
++20140304
++ - OpenBSD CVS Sync
++   - djm at cvs.openbsd.org 2014/03/03 22:22:30
++     [session.c]
++     ignore enviornment variables with embedded '=' or '\0' characters;
++     spotted by Jann Horn; ok deraadt@
++
+ 20140420
+    - djm at cvs.openbsd.org 2014/04/01 03:34:10
+      [sshconnect.c]
+diff --git a/bufaux.c b/bufaux.c
+index 42ebb8a..52294ef 100644
+--- a/bufaux.c
++++ b/bufaux.c
+@@ -197,6 +197,39 @@ buffer_get_string(Buffer *buffer, u_int *length_ptr)
+ 	return (ret);
+ }
+ 
++char *
++buffer_get_cstring_ret(Buffer *buffer, u_int *length_ptr)
++{
++	u_int length;
++	char *cp, *ret = buffer_get_string_ret(buffer, &length);
++
++	if (ret == NULL)
++		return NULL;
++	if ((cp = memchr(ret, '\0', length)) != NULL) {
++		/* XXX allow \0 at end-of-string for a while, remove later */
++		if (cp == ret + length - 1)
++			error("buffer_get_cstring_ret: string contains \\0");
++		else {
++			bzero(ret, length);
++			free(ret);
++			return NULL;
++		}
++	}
++	if (length_ptr != NULL)
++		*length_ptr = length;
++	return ret;
++}
++
++char *
++buffer_get_cstring(Buffer *buffer, u_int *length_ptr)
++{
++	char *ret;
++
++	if ((ret = buffer_get_cstring_ret(buffer, length_ptr)) == NULL)
++		fatal("buffer_get_cstring: buffer error");
++	return ret;
++}
++
+ void *
+ buffer_get_string_ptr_ret(Buffer *buffer, u_int *length_ptr)
+ {
+diff --git a/buffer.h b/buffer.h
+index fd19405..c475e88 100644
+--- a/buffer.h
++++ b/buffer.h
+@@ -68,6 +68,7 @@ void    buffer_put_char(Buffer *, int);
+ void   *buffer_get_string(Buffer *, u_int *);
+ void   *buffer_get_string_ptr(Buffer *, u_int *);
+ void    buffer_put_string(Buffer *, const void *, u_int);
++char   *buffer_get_cstring(Buffer *, u_int *);
+ void	buffer_put_cstring(Buffer *, const char *);
+ 
+ #define buffer_skip_string(b) \
+@@ -81,6 +82,7 @@ int	buffer_get_short_ret(u_short *, Buffer *);
+ int	buffer_get_int_ret(u_int *, Buffer *);
+ int	buffer_get_int64_ret(u_int64_t *, Buffer *);
+ void	*buffer_get_string_ret(Buffer *, u_int *);
++char	*buffer_get_cstring_ret(Buffer *, u_int *);
+ void   *buffer_get_string_ptr_ret(Buffer *, u_int *);
+ int	buffer_get_char_ret(char *, Buffer *);
+ 
+diff --git a/packet.c b/packet.c
+index 78dd445..f82b050 100644
+--- a/packet.c
++++ b/packet.c
+@@ -1587,6 +1587,13 @@ packet_get_string_ptr(u_int *length_ptr)
+ 	return buffer_get_string_ptr(&active_state->incoming_packet, length_ptr);
+ }
+ 
++/* Ensures the returned string has no embedded \0 characters in it. */
++char *
++packet_get_cstring(u_int *length_ptr)
++{
++	return buffer_get_cstring(&active_state->incoming_packet, length_ptr);
++}
++
+ /*
+  * Sends a diagnostic message from the server to the client.  This message
+  * can be sent at any time (but not while constructing another message). The
+diff --git a/packet.h b/packet.h
+index 5db8b58..cde9ff7 100644
+--- a/packet.h
++++ b/packet.h
+@@ -71,6 +71,7 @@ void	 packet_get_ecpoint(const EC_GROUP *, EC_POINT *);
+ #endif
+ void	*packet_get_raw(u_int *length_ptr);
+ void	*packet_get_string(u_int *length_ptr);
++char	*packet_get_cstring(u_int *length_ptr);
+ void	*packet_get_string_ptr(u_int *length_ptr);
+ void     packet_disconnect(const char *fmt,...) __attribute__((format(printf, 1, 2)));
+ void     packet_send_debug(const char *fmt,...) __attribute__((format(printf, 1, 2)));
+diff --git a/session.c b/session.c
+index 97a50c4..f62e43a 100644
+--- a/session.c
++++ b/session.c
+@@ -956,6 +956,11 @@ child_set_env(char ***envp, u_int *envsizep, const char *name,
+ 	u_int envsize;
+ 	u_int i, namelen;
+ 
++	if (strchr(name, '=') != NULL) {
++		error("Invalid environment variable \"%.100s\"", name);
++		return;
++	}
++
+ 	/*
+ 	 * If we're passed an uninitialized list, allocate a single null
+ 	 * entry before continuing.
+@@ -2253,8 +2258,8 @@ session_env_req(Session *s)
+ 	char *name, *val;
+ 	u_int name_len, val_len, i;
+ 
+-	name = packet_get_string(&name_len);
+-	val = packet_get_string(&val_len);
++	name = packet_get_cstring(&name_len);
++	val = packet_get_cstring(&val_len);
+ 	packet_check_eom();
+ 
+ 	/* Don't set too many environment variables */
diff --git a/openssh-5.3p1-log-sftp-only-connections.patch b/openssh-5.3p1-log-sftp-only-connections.patch
new file mode 100644
index 0000000..997cfd5
--- /dev/null
+++ b/openssh-5.3p1-log-sftp-only-connections.patch
@@ -0,0 +1,12 @@
+diff --git a/session.c b/session.c
+index f62e43a..8b6db20 100644
+--- a/session.c
++++ b/session.c
+@@ -1822,6 +1822,7 @@ do_child(Session *s, const char *command)
+ 
+ 	if (s->is_subsystem == SUBSYSTEM_INT_SFTP_ERROR) {
+ 		printf("This service allows sftp connections only.\n");
++		logit("The session allows sftp connections only");
+ 		fflush(NULL);
+ 		exit(1);
+ 	} else if (s->is_subsystem == SUBSYSTEM_INT_SFTP) {
diff --git a/openssh-5.3p1-restore-oom-after-restart.patch b/openssh-5.3p1-restore-oom-after-restart.patch
new file mode 100644
index 0000000..ecb9b21
--- /dev/null
+++ b/openssh-5.3p1-restore-oom-after-restart.patch
@@ -0,0 +1,58 @@
+diff --git a/ChangeLog b/ChangeLog
+index 3304206..45e6942 100644
+--- a/ChangeLog
++++ b/ChangeLog
+@@ -22,6 +22,11 @@
+    latter actually works before using it.  Fedora (at least) has NID_secp521r1
+    that doesn't work (see https://bugzilla.redhat.com/show_bug.cgi?id=1021897).
+ 
++20130922
++ - (dtucker) [platform.c platform.h sshd.c] bz#2156: restore Linux oom_adj
++   setting when handling SIGHUP to maintain behaviour over retart.  Patch
++   from Matthew Ife.
++
+ 20111104
+  - (dtucker) OpenBSD CVS Sync
+    - djm at cvs.openbsd.org 2011/10/24 02:10:46
+diff --git a/platform.c b/platform.c
+index 0b02e81..d6b28f6 100644
+--- a/platform.c
++++ b/platform.c
+@@ -39,6 +39,14 @@ platform_pre_fork(void)
+ }
+ 
+ void
++platform_pre_restart(void)
++{
++#ifdef LINUX_OOM_ADJUST
++	oom_adjust_restore();
++#endif
++}
++
++void
+ platform_post_fork_parent(pid_t child_pid)
+ {
+ #ifdef USE_SOLARIS_PROCESS_CONTRACTS
+diff --git a/platform.h b/platform.h
+index 5e65cff..915b70b 100644
+--- a/platform.h
++++ b/platform.h
+@@ -20,5 +20,6 @@
+ 
+ void platform_pre_listen(void);
+ void platform_pre_fork(void);
++void platform_pre_restart(void);
+ void platform_post_fork_parent(pid_t child_pid);
+ void platform_post_fork_child(void);
+diff --git a/sshd.c b/sshd.c
+index a27a082..41835e3 100644
+--- a/sshd.c
++++ b/sshd.c
+@@ -323,6 +323,7 @@ static void
+ sighup_restart(void)
+ {
+ 	logit("Received SIGHUP; restarting.");
++	platform_pre_restart();
+ 	signal(SIGHUP, SIG_IGN); /* will be restored after exec */
+ 	close_listen_socks();
+ 	close_startup_pipes();
diff --git a/openssh-5.3p1-sigpipe.patch b/openssh-5.3p1-sigpipe.patch
new file mode 100644
index 0000000..ba52b3b
--- /dev/null
+++ b/openssh-5.3p1-sigpipe.patch
@@ -0,0 +1,13 @@
+diff --git a/ssh-keyscan.c b/ssh-keyscan.c
+index c98f44f..9dcee73 100644
+--- a/ssh-keyscan.c
++++ b/ssh-keyscan.c
+@@ -828,6 +828,8 @@ main(int argc, char **argv)
+ 		fdlim_set(maxfd);
+ 	fdcon = xcalloc(maxfd, sizeof(con));
+ 
++	signal(SIGPIPE, SIG_IGN);
++
+ 	read_wait_nfdset = howmany(maxfd, NFDBITS);
+ 	read_wait = xcalloc(read_wait_nfdset, sizeof(fd_mask));
+ 
diff --git a/openssh-5.3p1-skip-pin-for-ssh-add-e.patch b/openssh-5.3p1-skip-pin-for-ssh-add-e.patch
new file mode 100644
index 0000000..37e7d88
--- /dev/null
+++ b/openssh-5.3p1-skip-pin-for-ssh-add-e.patch
@@ -0,0 +1,55 @@
+diff --git a/ChangeLog b/ChangeLog
+index e9bc955..2a7a5d5 100644
+--- a/ChangeLog
++++ b/ChangeLog
+@@ -16,6 +16,13 @@
+      
+      Reported by mcv21 AT cam.ac.uk
+ 
++20131229
++ - (djm) OpenBSD CVS Sync
++   - djm at cvs.openbsd.org 2013/12/19 00:10:30
++     [ssh-add.c]
++     skip requesting smartcard PIN when removing keys from agent; bz#2187
++     patch from jay AT slushpupie.com; ok dtucker
++
+ 20131109
+  - (dtucker) [configure.ac kex.c key.c myproposal.h] Test for the presence of
+    NID_X9_62_prime256v1, NID_secp384r1 and NID_secp521r1 and test that the
+diff --git a/ssh-add.c b/ssh-add.c
+index d268cdd..a8144c8 100644
+--- a/ssh-add.c
++++ b/ssh-add.c
+@@ -257,14 +257,17 @@ add_file(AuthenticationConnection *ac, const char *filename)
+ static int
+ update_card(AuthenticationConnection *ac, int add, const char *id)
+ {
+-	char *pin;
++	char *pin = NULL;
+ 	int ret = -1;
+ 
+-	pin = read_passphrase("Enter passphrase for PKCS#11: ", RP_ALLOW_STDIN);
+-	if (pin == NULL)
+-		return -1;
++	if (add) {
++		if ((pin = read_passphrase("Enter passphrase for PKCS#11: ",
++		    RP_ALLOW_STDIN)) == NULL)
++			return -1;
++	}
+ 
+-	if (ssh_update_card(ac, add, id, pin, lifetime, confirm)) {
++	if (ssh_update_card(ac, add, id, pin == NULL ? "" : pin,
++	    lifetime, confirm)) {
+ 		fprintf(stderr, "Card %s: %s\n",
+ 		    add ? "added" : "removed", id);
+ 		ret = 0;
+@@ -273,7 +276,8 @@ update_card(AuthenticationConnection *ac, int add, const char *id)
+ 		    add ? "add" : "remove", id);
+ 		ret = -1;
+ 	}
+-	xfree(pin);
++	if (pin)
++		xfree(pin);
+ 	return ret;
+ }
+ 
diff --git a/openssh-5.3p1-ssh-certificates.patch b/openssh-5.3p1-ssh-certificates.patch
index 5f1a692..de360e1 100644
--- a/openssh-5.3p1-ssh-certificates.patch
+++ b/openssh-5.3p1-ssh-certificates.patch
@@ -4952,7 +4952,7 @@ diff -up openssh-5.3p1/ssh-keygen.1.certificates openssh-5.3p1/ssh-keygen.1
 +.Fl s Ar ca_key
 +.Fl I Ar certificate_identity
 +.Op Fl h
-+.Op Fl n Ar principals
++.Op Fl Z Ar principals
 +.Op Fl O Ar option
 +.Op Fl V Ar validity_interval
 +.Op Fl z Ar serial_number
@@ -4995,7 +4995,7 @@ diff -up openssh-5.3p1/ssh-keygen.1.certificates openssh-5.3p1/ssh-keygen.1
  Extract the public key from smartcard.
  .It Fl N Ar new_passphrase
  Provides the new passphrase.
-+.It Fl n Ar principals
++.It Fl Z Ar principals
 +Specify one or more principals (user or host names) to be included in
 +a certificate when signing a key.
 +Multiple principals may be specified, separated by commas.
@@ -5160,8 +5160,8 @@ diff -up openssh-5.3p1/ssh-keygen.1.certificates openssh-5.3p1/ssh-keygen.1
 +By default, generated certificates are valid for all users or hosts.
 +To generate a certificate for a specified set of principals:
 +.Pp
-+.Dl $ ssh-keygen -s ca_key -I key_id -n user1,user2 user_key.pub
-+.Dl "$ ssh-keygen -s ca_key -I key_id -h -n host.domain user_key.pub"
++.Dl $ ssh-keygen -s ca_key -I key_id -Z user1,user2 user_key.pub
++.Dl "$ ssh-keygen -s ca_key -I key_id -h -Z host.domain user_key.pub"
 +.Pp
 +Additional limitations on the validity and use of user certificates may
 +be specified through certificate options.
diff --git a/openssh-5.3p1-ssh-keygen-V-fix.patch b/openssh-5.3p1-ssh-keygen-V-fix.patch
new file mode 100644
index 0000000..724417b
--- /dev/null
+++ b/openssh-5.3p1-ssh-keygen-V-fix.patch
@@ -0,0 +1,32 @@
+diff --git a/ChangeLog b/ChangeLog
+index 45e6942..e9bc955 100644
+--- a/ChangeLog
++++ b/ChangeLog
+@@ -22,6 +22,14 @@
+    latter actually works before using it.  Fedora (at least) has NID_secp521r1
+    that doesn't work (see https://bugzilla.redhat.com/show_bug.cgi?id=1021897).
+ 
++20131023
++ - (djm) OpenBSD CVS Sync
++   - djm at cvs.openbsd.org 2013/10/23 04:16:22
++     [ssh-keygen.c]
++     Make code match documentation: relative-specified certificate expiry time
++     should be relative to current time and not the validity start time.
++     Reported by Petr Lautrbach; ok deraadt@
++
+ 20130922
+  - (dtucker) [platform.c platform.h sshd.c] bz#2156: restore Linux oom_adj
+    setting when handling SIGHUP to maintain behaviour over retart.  Patch
+diff --git a/ssh-keygen.c b/ssh-keygen.c
+index c3dad3b..25d8613 100644
+--- a/ssh-keygen.c
++++ b/ssh-keygen.c
+@@ -1385,7 +1385,7 @@ parse_cert_times(char *timespec)
+ 		cert_valid_from = parse_absolute_time(from);
+ 
+ 	if (*to == '-' || *to == '+')
+-		cert_valid_to = parse_relative_time(to, cert_valid_from);
++		cert_valid_to = parse_relative_time(to, now);
+ 	else
+ 		cert_valid_to = parse_absolute_time(to);
+ 
diff --git a/openssh-5.3p1-x11-for-non-linux-platforms.patch b/openssh-5.3p1-x11-for-non-linux-platforms.patch
new file mode 100644
index 0000000..d9fbbc7
--- /dev/null
+++ b/openssh-5.3p1-x11-for-non-linux-platforms.patch
@@ -0,0 +1,17 @@
+diff --git a/channels.c b/channels.c
+index 23faae0..9ecee35 100644
+--- a/channels.c
++++ b/channels.c
+@@ -3362,10 +3362,8 @@ static int
+ connect_local_xsocket(u_int dnr)
+ {
+ 	char buf[1024];
+-	int len;
+-#ifdef linux
+-	int ret;
+-#endif
++	int len, ret;
++
+ 	len = snprintf(buf + 1, sizeof (buf) - 1, _PATH_UNIX_X, dnr);
+ #ifdef linux
+ 	/* try abstract socket first */
diff --git a/openssh-5.3p1-x11-getaddrinfo.patch b/openssh-5.3p1-x11-getaddrinfo.patch
new file mode 100644
index 0000000..bd42fb3
--- /dev/null
+++ b/openssh-5.3p1-x11-getaddrinfo.patch
@@ -0,0 +1,22 @@
+diff --git a/channels.c b/channels.c
+index f60bf84..23faae0 100644
+--- a/channels.c
++++ b/channels.c
+@@ -3291,14 +3291,10 @@ x11_create_display_inet(int x11_display_offset, int x11_use_localhost,
+ 			if (x11_use_localhost)
+ 				channel_set_reuseaddr(sock);
+ 			if (bind(sock, ai->ai_addr, ai->ai_addrlen) < 0) {
+-				debug2("bind port %d: %.100s", port, strerror(errno));
++				debug2("bind port %d (%s): %.100s", port, (ai->ai_family == AF_INET ? "AF_INET": "AF_INET6"),
++						strerror(errno));
+ 				close(sock);
+-
+-				for (n = 0; n < num_socks; n++) {
+-					close(socks[n]);
+-				}
+-				num_socks = 0;
+-				break;
++				continue;
+ 			}
+ 			socks[num_socks++] = sock;
+ 			if (num_socks == NUM_SOCKS)


More information about the scm-commits mailing list