[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