Index: openssh-4.2p1/keystate.h =================================================================== --- openssh-4.2p1/keystate.h (revision 0) +++ openssh-4.2p1/keystate.h (revision 76) @@ -0,0 +1,41 @@ +/* $OpenBSD: monitor_wrap.c,v 1.40 2005/05/24 17:32:43 avsm Exp $ */ + +/* + * Copyright 2002 Niels Provos + * Copyright 2002 Markus Friedl + * 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. + */ +#ifndef _KEYSTATE_H_ +#define _KEYSTATE_H_ + +#include "buffer.h" +#include "key.h" +#include "kex.h" +#include "monitor_wrap.h" +#include "resilient.h" + +void monitor_apply_keystate(init_compression_fn *, struct mm_master *); +void mm_get_keystate(Buffer *, Kex **); +void mm_send_keystate(Buffer *, Kex *); + +#endif Index: openssh-4.2p1/sshconnect2.c =================================================================== --- openssh-4.2p1/sshconnect2.c (revision 14) +++ openssh-4.2p1/sshconnect2.c (revision 76) @@ -69,8 +69,6 @@ char *xxx_host; struct sockaddr *xxx_hostaddr; -Kex *xxx_kex = NULL; - static int verify_host_key_callback(Key *hostkey) { @@ -79,6 +77,29 @@ return 0; } +void +ssh_kex2_reconnect() +{ + Kex *kex = xxx_kex; + + myproposal[PROPOSAL_KEX_ALGS] = KEX_RESILIENT_KEX_ONLY; + kex_prop2buf(&kex->my, myproposal); + + /* start key exchange */ + kex_send_kexinit(kex); + + kex->kex[KEX_REUSE] = kexreuse_client; + kex->client_version_string=client_version_string; + kex->server_version_string=server_version_string; + kex->verify_host_key=&verify_host_key_callback; + + /* Wait and process the KEX_INIT. */ + packet_read_expect(SSH2_MSG_KEXINIT); + debug("got SSH2_MSG_KEXINIT."); + + kex_input_kexinit(SSH2_MSG_KEXINIT, 0, kex); +} + void ssh_kex2(char *host, struct sockaddr *hostaddr) { @@ -117,11 +138,16 @@ if (options.rekey_limit) packet_set_rekey_limit(options.rekey_limit); + if (options.resilient_connection_attempts) + myproposal[PROPOSAL_KEX_ALGS] = KEX_RESILIENT_KEX; + /* start key exchange */ kex = kex_setup(myproposal); kex->kex[KEX_DH_GRP1_SHA1] = kexdh_client; kex->kex[KEX_DH_GRP14_SHA1] = kexdh_client; kex->kex[KEX_DH_GEX_SHA1] = kexgex_client; + if (options.resilient_connection_attempts) + kex->kex[KEX_REUSE] = kexreuse_client; kex->client_version_string=client_version_string; kex->server_version_string=server_version_string; kex->verify_host_key=&verify_host_key_callback; Index: openssh-4.2p1/myproposal.h =================================================================== --- openssh-4.2p1/myproposal.h (revision 14) +++ openssh-4.2p1/myproposal.h (revision 76) @@ -40,6 +40,13 @@ #define KEX_DEFAULT_LANG "" +/* Resilient mobility extensions */ +#define KEX_RESILIENT_KEX "diffie-hellman-group-exchange-sha1," \ + "diffie-hellman-group14-sha1," \ + "diffie-hellman-group1-sha1," \ + "resilient-mobility-group@tkk.fi" +#define KEX_RESILIENT_KEX_ONLY "resilient-mobility-group@tkk.fi" + static char *myproposal[PROPOSAL_MAX] = { KEX_DEFAULT_KEX, KEX_DEFAULT_PK_ALG, Index: openssh-4.2p1/monitor_fdpass.c =================================================================== --- openssh-4.2p1/monitor_fdpass.c (revision 14) +++ openssh-4.2p1/monitor_fdpass.c (revision 76) @@ -31,13 +31,14 @@ #include "log.h" #include "monitor_fdpass.h" -void -mm_send_fd(int sock, int fd) +int passed_fd; + +ssize_t +write_fdpass(int sock, void *buf, size_t nbytes, int fd) { #if defined(HAVE_SENDMSG) && (defined(HAVE_ACCRIGHTS_IN_MSGHDR) || defined(HAVE_CONTROL_IN_MSGHDR)) struct msghdr msg; struct iovec vec; - char ch = '\0'; ssize_t n; #ifndef HAVE_ACCRIGHTS_IN_MSGHDR char tmp[CMSG_SPACE(sizeof(int))]; @@ -58,45 +59,42 @@ *(int *)CMSG_DATA(cmsg) = fd; #endif - vec.iov_base = &ch; - vec.iov_len = 1; + vec.iov_base = buf; + vec.iov_len = nbytes; msg.msg_iov = &vec; msg.msg_iovlen = 1; if ((n = sendmsg(sock, &msg, 0)) == -1) fatal("%s: sendmsg(%d): %s", __func__, fd, strerror(errno)); - if (n != 1) - fatal("%s: sendmsg: expected sent 1 got %ld", - __func__, (long)n); #else fatal("%s: UsePrivilegeSeparation=yes not supported", __func__); #endif + return n; } -int -mm_receive_fd(int sock) +ssize_t +read_fdpass(int sock, void *buf, size_t nbytes, int *fd) { #if defined(HAVE_RECVMSG) && (defined(HAVE_ACCRIGHTS_IN_MSGHDR) || defined(HAVE_CONTROL_IN_MSGHDR)) struct msghdr msg; struct iovec vec; ssize_t n; - char ch; - int fd; + #ifndef HAVE_ACCRIGHTS_IN_MSGHDR char tmp[CMSG_SPACE(sizeof(int))]; struct cmsghdr *cmsg; #endif memset(&msg, 0, sizeof(msg)); - vec.iov_base = &ch; - vec.iov_len = 1; + vec.iov_base = buf; + vec.iov_len = nbytes; msg.msg_iov = &vec; msg.msg_iovlen = 1; #ifdef HAVE_ACCRIGHTS_IN_MSGHDR - msg.msg_accrights = (caddr_t)&fd; - msg.msg_accrightslen = sizeof(fd); + msg.msg_accrights = (caddr_t)fd; + msg.msg_accrightslen = sizeof(*fd); #else msg.msg_control = tmp; msg.msg_controllen = sizeof(tmp); @@ -104,27 +102,74 @@ if ((n = recvmsg(sock, &msg, 0)) == -1) fatal("%s: recvmsg: %s", __func__, strerror(errno)); - if (n != 1) - fatal("%s: recvmsg: expected received 1 got %ld", - __func__, (long)n); #ifdef HAVE_ACCRIGHTS_IN_MSGHDR if (msg.msg_accrightslen != sizeof(fd)) fatal("%s: no fd", __func__); #else cmsg = CMSG_FIRSTHDR(&msg); - if (cmsg == NULL) - fatal("%s: no message header", __func__); + if (cmsg == NULL) { + debug("got %d bytes", n); + return n; + } #ifndef BROKEN_CMSG_TYPE - if (cmsg->cmsg_type != SCM_RIGHTS) - fatal("%s: expected type %d got %d", __func__, - SCM_RIGHTS, cmsg->cmsg_type); + //if (cmsg->cmsg_type != SCM_RIGHTS) + // fatal("%s: expected type %d got %d", __func__, + // SCM_RIGHTS, cmsg->cmsg_type); #endif - fd = (*(int *)CMSG_DATA(cmsg)); + if (cmsg->cmsg_type == SCM_RIGHTS) + *fd = (*(int *)CMSG_DATA(cmsg)); #endif - return fd; + return n; #else fatal("%s: UsePrivilegeSeparation=yes not supported", __func__); #endif } + +int +mm_send_fd(int sock, int fd) +{ + char ch = '\0'; + ssize_t n; + + n = write_fdpass(sock, &ch, sizeof(ch), fd); + if (n != 1) + fatal("%s: sendmsg: expected sent 1 got %ld", + __func__, (long)n); + return n; +} + +int +mm_receive_fd(int sock) +{ + char ch; + int fd = -1; + ssize_t n; + + n = read_fdpass(sock, &ch, sizeof(ch), &fd); + if (n != 1) + fatal("%s: recvmsg: expected received 1 got %ld", + __func__, (long)n); + if (fd == -1) + fatal("%s: no message header", __func__); + return fd; +} + +int write_fd(int sock, void *buf, size_t nbytes) +{ + ssize_t n; + + if (passed_fd != -1) { + n = write_fdpass(sock, buf, nbytes, passed_fd); + passed_fd = -1; + } else + n = write(sock, buf, nbytes); + + return n; +} + +int read_fd(int sock, void *buf, size_t nbytes) +{ + return read_fdpass(sock, buf, nbytes, &passed_fd); +} Index: openssh-4.2p1/monitor_fdpass.h =================================================================== --- openssh-4.2p1/monitor_fdpass.h (revision 14) +++ openssh-4.2p1/monitor_fdpass.h (revision 76) @@ -28,7 +28,14 @@ #ifndef _MM_FDPASS_H_ #define _MM_FDPASS_H_ -void mm_send_fd(int, int); +int mm_send_fd(int, int); int mm_receive_fd(int); +ssize_t write_fdpass(int, void *, size_t, int); +ssize_t read_fdpass(int, void *, size_t, int *); +int read_fd(int, void *, size_t); +int write_fd(int, void *, size_t); + +extern int passed_fd; + #endif /* _MM_FDPASS_H_ */ Index: openssh-4.2p1/ssh.c =================================================================== --- openssh-4.2p1/ssh.c (revision 14) +++ openssh-4.2p1/ssh.c (revision 76) @@ -72,6 +72,7 @@ #include "msg.h" #include "monitor_fdpass.h" #include "uidswap.h" +#include "clientloop.h" #ifdef SMARTCARD #include "scard.h" @@ -717,32 +718,37 @@ tilde_expand_filename(options.user_hostfile2, original_real_uid); signal(SIGPIPE, SIG_IGN); /* ignore SIGPIPE early */ + if (options.resilient_connection_attempts) + signal(SIGUSR2, client_reconnect_sighandler); /* Log into the remote system. This never returns if the login fails. */ ssh_login(&sensitive_data, host, (struct sockaddr *)&hostaddr, pw); - /* We no longer need the private host keys. Clear them now. */ - if (sensitive_data.nkeys != 0) { - for (i = 0; i < sensitive_data.nkeys; i++) { - if (sensitive_data.keys[i] != NULL) { - /* Destroys contents safely */ - debug3("clear hostkey %d", i); - key_free(sensitive_data.keys[i]); - sensitive_data.keys[i] = NULL; - } - } - xfree(sensitive_data.keys); - } - for (i = 0; i < options.num_identity_files; i++) { - if (options.identity_files[i]) { - xfree(options.identity_files[i]); - options.identity_files[i] = NULL; - } - if (options.identity_keys[i]) { - key_free(options.identity_keys[i]); - options.identity_keys[i] = NULL; - } - } + /* We might need the private keys later! */ + if (!options.resilient_connection_attempts) { + /* We no longer need the private host keys. Clear them now. */ + if (sensitive_data.nkeys != 0) { + for (i = 0; i < sensitive_data.nkeys; i++) { + if (sensitive_data.keys[i] != NULL) { + /* Destroys contents safely */ + debug3("clear hostkey %d", i); + key_free(sensitive_data.keys[i]); + sensitive_data.keys[i] = NULL; + } + } + xfree(sensitive_data.keys); + } + for (i = 0; i < options.num_identity_files; i++) { + if (options.identity_files[i]) { + xfree(options.identity_files[i]); + options.identity_files[i] = NULL; + } + if (options.identity_keys[i]) { + key_free(options.identity_keys[i]); + options.identity_keys[i] = NULL; + } + } + } exit_status = compat20 ? ssh_session2() : ssh_session(); packet_close(); @@ -992,7 +998,7 @@ { struct sockaddr_un addr; mode_t old_umask; - int addr_len; + int addr_len; if (options.control_path == NULL || options.control_master == SSHCTL_MASTER_NO) @@ -1351,7 +1357,7 @@ signal(SIGHUP, control_client_sighandler); signal(SIGINT, control_client_sighandler); signal(SIGTERM, control_client_sighandler); - signal(SIGWINCH, control_client_sigrelay); + signal(SIGWINCH, control_client_sigrelay); if (tty_flag) enter_raw_mode(); Index: openssh-4.2p1/kexreusec.c =================================================================== --- openssh-4.2p1/kexreusec.c (revision 0) +++ openssh-4.2p1/kexreusec.c (revision 76) @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2005 Teemu Koponen. 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" +RCSID("$OpenBSD: kexdhs.c,v 1.2 2004/06/13 12:53:24 djm Exp $"); + +#include + +#include "bufaux.h" +#include "kex.h" +#include "log.h" +#include "packet.h" +#include "ssh2.h" +#include "xmalloc.h" + +extern Newkeys *oldkeys[]; +extern u_int32_t old_read_seqnr; + +void +kexreuse_client(Kex *kex) +{ + Mac *mac = &oldkeys[MODE_OUT]->mac; + static u_char md[EVP_MAX_MD_SIZE]; + u_char *reply_mac; + const u_char one = 1, two = 2; + u_int mac_len; + Buffer my; + HMAC_CTX c; + + buffer_init(&my); + kex->done = 0; + packet_start(SSH2_MSG_KEXRECONNECT_INIT); + packet_put_string(kex->resilient_id, kex->session_id_len); + buffer_put_string(&my, kex->resilient_id, kex->session_id_len); + + /* MAC = f(k, "1" | V_C | V_S | I_C | I_S) */ + HMAC_Init(&c, mac->key, mac->key_len, mac->md); + HMAC_Update(&c, &one, sizeof(one)); + HMAC_Update(&c, (const unsigned char *)kex->client_version_string, strlen(kex->client_version_string)); + HMAC_Update(&c, (const unsigned char *)kex->server_version_string, strlen(kex->server_version_string)); + HMAC_Update(&c, buffer_ptr(&kex->my), buffer_len(&kex->my)); + HMAC_Update(&c, buffer_ptr(&kex->peer), buffer_len(&kex->peer)); + HMAC_Final(&c, md, NULL); + HMAC_cleanup(&c); + packet_put_string(md, mac->mac_len); + packet_send(); + + debug("sent SSH2_MSG_KEXRECONNECT_INIT for session %s, expecting SSH2_MSG_KEXRECONNECT_REPLY", + hex_dump(kex->resilient_id, 20)); + packet_read_expect(SSH2_MSG_KEXRECONNECT_REPLY); + + /* validate server MAC = f(k, "2" | V_C | V_S | I_C | I_S) */ + reply_mac = packet_get_string(&mac_len); + HMAC_Init(&c, mac->key, mac->key_len, mac->md); + HMAC_Update(&c, &two, sizeof(one)); + HMAC_Update(&c, (const unsigned char *)kex->client_version_string, strlen(kex->client_version_string)); + HMAC_Update(&c, (const unsigned char *)kex->server_version_string, strlen(kex->server_version_string)); + HMAC_Update(&c, buffer_ptr(&kex->my), buffer_len(&kex->my)); + HMAC_Update(&c, buffer_ptr(&kex->peer), buffer_len(&kex->peer)); + HMAC_Final(&c, md, NULL); + HMAC_cleanup(&c); + + if (mac->mac_len != mac_len || memcmp(md, reply_mac, mac_len)) + fatal("invalid MAC from server"); + + xfree(reply_mac); + + kex_derive_keys(xxx_kex, xxx_kex->session_id, xxx_kex->shared_secret); + kex_finish(xxx_kex, NULL); + + /* Synchronize the queues */ + packet_start(SSH2_MSG_SYNCHRONIZE); + packet_put_int(old_read_seqnr); + packet_send(); + + debug("expecting SSH2_MSG_SYNCHRONIZE"); + packet_read_expect(SSH2_MSG_SYNCHRONIZE); + p_send.seqnr = packet_get_int(); + p_read.seqnr = old_read_seqnr; + debug("got SSH2_MSG_SYNCHRONIZE, server needs next msg #%d", p_send.seqnr); +} Index: openssh-4.2p1/kexgexc.c =================================================================== --- openssh-4.2p1/kexgexc.c (revision 14) +++ openssh-4.2p1/kexgexc.c (revision 76) @@ -145,9 +145,10 @@ #ifdef DEBUG_KEXDH dump_digest("shared secret", kbuf, kout); #endif - if ((shared_secret = BN_new()) == NULL) + + if ((kex->shared_secret = BN_new()) == NULL) fatal("kexgex_client: BN_new failed"); - BN_bin2bn(kbuf, kout, shared_secret); + BN_bin2bn(kbuf, kout, kex->shared_secret); memset(kbuf, 0, klen); xfree(kbuf); @@ -165,26 +166,49 @@ dh->p, dh->g, dh->pub_key, dh_server_pub, - shared_secret + kex->shared_secret ); + + /* save session id */ + if (kex->session_id == NULL) { + kex->session_id_len = 20; + kex->session_id = xmalloc(kex->session_id_len); + memcpy(kex->session_id, hash, kex->session_id_len); + } + + /* calc resilient H */ + if (resilient) { + hash = kexgex_hash( + 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, + min, nbits, max, + dh->p, dh->g, + dh->pub_key, + dh_server_pub, + NULL + ); + + /* save resilient id := resilient H */ + /* XXX hashlen equals to session id len */ + if (kex->resilient_id == NULL) { + kex->resilient_id = xmalloc(kex->session_id_len); + memcpy(kex->resilient_id, hash, kex->session_id_len); + } + } + /* have keys, free DH */ DH_free(dh); xfree(server_host_key_blob); BN_clear_free(dh_server_pub); - if (key_verify(server_host_key, signature, slen, hash, 20) != 1) + if (key_verify(server_host_key, signature, slen, kex->session_id, 20) != 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 = 20; - kex->session_id = xmalloc(kex->session_id_len); - memcpy(kex->session_id, hash, kex->session_id_len); - } - kex_derive_keys(kex, hash, shared_secret); - BN_clear_free(shared_secret); - - kex_finish(kex); + kex_derive_keys(kex, kex->session_id, kex->shared_secret); + kex_finish(kex, NULL); } Index: openssh-4.2p1/kexdh.c =================================================================== --- openssh-4.2p1/kexdh.c (revision 14) +++ openssh-4.2p1/kexdh.c (revision 76) @@ -63,7 +63,8 @@ buffer_put_string(&b, serverhostkeyblob, sbloblen); buffer_put_bignum2(&b, client_dh_pub); buffer_put_bignum2(&b, server_dh_pub); - buffer_put_bignum2(&b, shared_secret); + if (shared_secret) + buffer_put_bignum2(&b, shared_secret); #ifdef DEBUG_KEX buffer_dump(&b); Index: openssh-4.2p1/kexreuses.c =================================================================== --- openssh-4.2p1/kexreuses.c (revision 0) +++ openssh-4.2p1/kexreuses.c (revision 76) @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2005 Teemu Koponen. 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" +RCSID("$OpenBSD: kexdhs.c,v 1.2 2004/06/13 12:53:24 djm Exp $"); + +#include "bufaux.h" +#include "kex.h" +#include "keystate.h" +#include "log.h" +#include "packet.h" +#include "ssh2.h" +#include "xmalloc.h" + +#define RESILIENT_ID_LEN 20 +void +kexreuse_server(Kex *kex) +{ + u_char *mac, *reply_mac; + u_int resilient_id_len, mac_len, reply_mac_len; + Buffer m, n; + + if (packet_get_connection_in() != packet_get_connection_out()) + fatal("Not supported."); + + debug("expecting SSH2_MSG_KEXRECONNECT_INIT"); + packet_read_expect(SSH2_MSG_KEXRECONNECT_INIT); + + kex->resilient_id = packet_get_string(&resilient_id_len); + if (resilient_id_len != RESILIENT_ID_LEN) + fatal("Hm, invalid kex reconnect packet (length was %u)", resilient_id_len); + kex->session_id_len = RESILIENT_ID_LEN; + + mac = packet_get_string(&mac_len); + + debug3("validating %s", hex_dump(kex->resilient_id, 20)); + + buffer_init(&m); + buffer_put_string(&m, kex->resilient_id, 20); + buffer_put_cstring(&m, (const char *)kex->client_version_string); + buffer_put_string(&m, buffer_ptr(&kex->my), buffer_len(&kex->my)); + buffer_put_string(&m, buffer_ptr(&kex->peer), buffer_len(&kex->peer)); + buffer_put_string(&m, mac, mac_len); + buffer_init(&n); + + PRIVSEP(validate_reconnect(&m, &n)); + if (!buffer_get_int(&n)) + fatal("Invalid kex reconnect."); + + reply_mac = buffer_get_string(&n, &reply_mac_len); + packet_start(SSH2_MSG_KEXRECONNECT_REPLY); + packet_put_string(reply_mac, reply_mac_len); + packet_send(); + + /* prepare slim rekey */ + kex->shared_secret = BN_new(); + kex->session_id = buffer_get_string(&n, &kex->session_id_len); + buffer_get_bignum2(&n, kex->shared_secret); + + /* rekey */ + kex_derive_keys(kex, kex->session_id, kex->shared_secret); + kex_finish(kex, NULL); + + /* run half-sync: send and receive sync, get over resilient bus + the re-sent packets, but do not process the incoming re-sent + packets. */ + u_int32_t old_read_seqnr = buffer_get_int(&n); + + packet_start(SSH2_MSG_SYNCHRONIZE); + packet_put_int(old_read_seqnr); + packet_send(); + + /* Process the SYNCHRONIZE message and sync the queue */ + packet_read_expect(SSH2_MSG_SYNCHRONIZE); + p_send.seqnr = packet_get_int(); + p_read.seqnr = old_read_seqnr; + debug("got SSH2_MSG_SYNCHRONIZE, client needs next msg #%d", p_send.seqnr); + + debug3("--------------------------------------------------"); + + /* serialize the key state */ + buffer_clear(&m); + buffer_clear(&n); + buffer_put_string(&m, kex->resilient_id, 20); + mm_send_keystate(&m, kex); + debug3("%s: passing keystate (%d bytes) and a fd to old process", __func__, + buffer_len(&m)); + + /* pass the socket fd before dying */ + PRIVSEP(pass_connection(packet_get_connection_in(), &m, &n)); + + buffer_free(&m); + buffer_free(&n); + + cleanup_exit(255); +} Index: openssh-4.2p1/kexgexs.c =================================================================== --- openssh-4.2p1/kexgexs.c (revision 14) +++ openssh-4.2p1/kexgexs.c (revision 76) @@ -39,12 +39,13 @@ void kexgex_server(Kex *kex) { - BIGNUM *shared_secret = NULL, *dh_client_pub = NULL; + BIGNUM *dh_client_pub = NULL; Key *server_host_key; DH *dh; u_char *kbuf, *hash, *signature = NULL, *server_host_key_blob = NULL; u_int sbloblen, klen, kout, slen; int min = -1, max = -1, nbits = -1, type; + kex_finished_fn *fn = NULL; if (kex->load_host_key == NULL) fatal("Cannot load hostkey"); @@ -126,9 +127,9 @@ #ifdef DEBUG_KEXDH dump_digest("shared secret", kbuf, kout); #endif - if ((shared_secret = BN_new()) == NULL) + if ((kex->shared_secret = BN_new()) == NULL) fatal("kexgex_server: BN_new failed"); - BN_bin2bn(kbuf, kout, shared_secret); + BN_bin2bn(kbuf, kout, kex->shared_secret); memset(kbuf, 0, klen); xfree(kbuf); @@ -148,9 +149,8 @@ dh->p, dh->g, dh_client_pub, dh->pub_key, - shared_secret + kex->shared_secret ); - BN_clear_free(dh_client_pub); /* save session id := H */ /* XXX hashlen depends on KEX */ @@ -160,9 +160,36 @@ memcpy(kex->session_id, hash, kex->session_id_len); } + /* calc resilient H */ + if (resilient) { + hash = kexgex_hash( + 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, + min, nbits, max, + dh->p, dh->g, + dh_client_pub, + dh->pub_key, + NULL + ); + + /* save resilient id := resilient H */ + /* XXX hashlen equals to session id len */ + if (kex->resilient_id == NULL) { + kex->resilient_id = xmalloc(kex->session_id_len); + memcpy(kex->resilient_id, hash, kex->session_id_len); + } + + fn = PRIVSEP(resilient_register); + } + + BN_clear_free(dh_client_pub); + /* sign H */ /* XXX hashlen depends on KEX */ - PRIVSEP(key_sign(server_host_key, &signature, &slen, hash, 20)); + PRIVSEP(key_sign(server_host_key, &signature, &slen, kex->session_id, 20)); /* destroy_sensitive_data(); */ @@ -179,8 +206,6 @@ /* have keys, free DH */ DH_free(dh); - kex_derive_keys(kex, hash, shared_secret); - BN_clear_free(shared_secret); - - kex_finish(kex); + kex_derive_keys(kex, kex->session_id, kex->shared_secret); + kex_finish(kex, fn); } Index: openssh-4.2p1/sshd.c =================================================================== --- openssh-4.2p1/sshd.c (revision 14) +++ openssh-4.2p1/sshd.c (revision 76) @@ -44,6 +44,8 @@ #include "includes.h" RCSID("$OpenBSD: sshd.c,v 1.312 2005/07/25 11:59:40 markus Exp $"); +#include "openbsd-compat/sys-queue.h" + #include #include #include @@ -80,11 +82,13 @@ #include "msg.h" #include "dispatch.h" #include "channels.h" +#include "resilient.h" #include "session.h" #include "monitor_mm.h" #include "monitor.h" #include "monitor_wrap.h" #include "monitor_fdpass.h" +#include "keystate.h" #ifdef LIBWRAP #include @@ -101,7 +105,8 @@ #define REEXEC_DEVCRYPTO_RESERVED_FD (STDERR_FILENO + 1) #define REEXEC_STARTUP_PIPE_FD (STDERR_FILENO + 2) #define REEXEC_CONFIG_PASS_FD (STDERR_FILENO + 3) -#define REEXEC_MIN_FREE_FD (STDERR_FILENO + 4) +#define REEXEC_RESILIENT_PIPE_FD (STDERR_FILENO + 4) +#define REEXEC_MIN_FREE_FD (STDERR_FILENO + 5) extern char *__progname; @@ -156,9 +161,6 @@ char *client_version_string = NULL; char *server_version_string = NULL; -/* for rekeying XXX fixme */ -Kex *xxx_kex; - /* * Any really sensitive data in the application is contained in this * structure. The idea is that this structure could be locked into memory so @@ -200,6 +202,8 @@ int *startup_pipes = NULL; int startup_pipe; /* in child */ +TAILQ_HEAD(,resilient_pipe) resilient_pipes; /* for the daemon process */ + /* variables used for privilege separation */ int use_privsep; struct monitor *pmonitor = NULL; @@ -241,6 +245,15 @@ close(startup_pipes[i]); } +static void +close_resilient_pipes(void) +{ + while (resilient_pipes.tqh_first) { + close(resilient_pipes.tqh_first->pipe); + TAILQ_REMOVE(&resilient_pipes, resilient_pipes.tqh_first, next); + } +} + /* * Signal handler for SIGHUP. Sshd execs itself when it receives SIGHUP; * the effect is to reread the configuration file (and to regenerate @@ -293,9 +306,8 @@ int status; while ((pid = waitpid(-1, &status, WNOHANG)) > 0 || - (pid < 0 && errno == EINTR)) - ; - + (pid < 0 && errno == EINTR)); + signal(SIGCHLD, main_sigchld_handler); errno = save_errno; } @@ -613,7 +625,7 @@ return (1); } else { /* child */ - + close(resilient_pipe); close(pmonitor->m_sendfd); /* Demote the child */ @@ -633,7 +645,9 @@ if (authctxt->pw->pw_uid == 0 || options.use_login) { #endif /* File descriptor passing is broken or root login */ - monitor_apply_keystate(pmonitor); + monitor_apply_keystate(options.compression ? + mm_init_compression : NULL, + pmonitor->m_zlib); use_privsep = 0; return; } @@ -670,7 +684,9 @@ do_setusercontext(authctxt->pw); /* It is safe now to apply the key state */ - monitor_apply_keystate(pmonitor); + monitor_apply_keystate(options.compression ? + mm_init_compression : NULL, + pmonitor->m_zlib); /* * Tell the packet layer that authentication was successful, since @@ -885,6 +901,7 @@ char *line; int listen_sock, maxfd; int startup_p[2] = { -1 , -1 }, config_s[2] = { -1 , -1 }; + int resilient_p[2] = { -1, -1 }; int startups = 0; Key *key; Authctxt *authctxt; @@ -1083,6 +1100,8 @@ exit(1); } + TAILQ_INIT(&resilient_pipes); + debug("sshd version %.100s", SSH_RELEASE); /* load private host keys */ @@ -1245,6 +1264,8 @@ if (rexeced_flag) { close(REEXEC_CONFIG_PASS_FD); sock_in = sock_out = dup(STDIN_FILENO); + resilient_pipe = dup(REEXEC_RESILIENT_PIPE_FD); + close(REEXEC_RESILIENT_PIPE_FD); if (!debug_flag) { startup_pipe = dup(REEXEC_STARTUP_PIPE_FD); close(REEXEC_STARTUP_PIPE_FD); @@ -1360,13 +1381,8 @@ fclose(f); } } - - /* setup fd set for listen */ - fdset = NULL; - maxfd = 0; - for (i = 0; i < num_listen_socks; i++) - if (listen_socks[i] > maxfd) - maxfd = listen_socks[i]; + + fdset = NULL; /* pipes connected to unauthenticated childs */ startup_pipes = xmalloc(options.max_startups * sizeof(int)); for (i = 0; i < options.max_startups; i++) @@ -1381,6 +1397,15 @@ sighup_restart(); if (fdset != NULL) xfree(fdset); + /* setup fd set for listen */ + maxfd = 0; + for (i = 0; i < num_listen_socks; i++) + if (listen_socks[i] > maxfd) + maxfd = listen_socks[i]; + struct resilient_pipe *r_pipe; + TAILQ_FOREACH(r_pipe, &resilient_pipes, next) + if (r_pipe->pipe > maxfd) + maxfd = r_pipe->pipe; fdsetsz = howmany(maxfd+1, NFDBITS) * sizeof(fd_mask); fdset = (fd_set *)xmalloc(fdsetsz); memset(fdset, 0, fdsetsz); @@ -1390,6 +1415,9 @@ for (i = 0; i < options.max_startups; i++) if (startup_pipes[i] != -1) FD_SET(startup_pipes[i], fdset); + TAILQ_FOREACH(r_pipe, &resilient_pipes, next) + if (r_pipe->pipe != -1) + FD_SET(r_pipe->pipe, fdset); /* Wait in select until there is a connection. */ ret = select(maxfd+1, fdset, NULL, NULL, NULL); @@ -1410,6 +1438,76 @@ if (ret < 0) continue; + /* Resilient message routing */ + struct resilient_pipe *next; + struct resilient_pipe *dst; + for (r_pipe = TAILQ_FIRST(&resilient_pipes); + r_pipe; r_pipe = next) { + Buffer m; + u_char type; + u_char *resilient_id; + + next = TAILQ_NEXT(r_pipe, next); + + if (!FD_ISSET(r_pipe->pipe, fdset)) + continue; + + buffer_init(&m); + + /* Garbage collection for a dying child process */ + if (r_request_receive(r_pipe->pipe, &m) <= 0) { + debug3("%s: Removing entry!", __func__); + close(r_pipe->pipe); + TAILQ_REMOVE(&resilient_pipes, r_pipe, next); + buffer_free(&m); + continue; + } + debug3("request received (%d bytes)", buffer_len(&m)); + + type = buffer_get_char(&m); + resilient_id = ((u_char *)buffer_ptr(&m)) + sizeof(int) / sizeof(u_char); + + switch (type) { + case MONITOR_REQ_NIL: + memcpy(&r_pipe->session_id, resilient_id, 20); + debug("registered session id %s", hex_dump(resilient_id, 20)); + break; + + case MONITOR_REQ_VALIDATE_RECONNECT: + case MONITOR_REQ_PASS_CONNECTION: + debug("message pass session id %s", + hex_dump(resilient_id, 20)); + + TAILQ_FOREACH(dst, &resilient_pipes, next) { + int fd = passed_fd; + if (memcmp(&dst->session_id, resilient_id, 20)) + continue; + + /* If sending/receiving fails, the + garbage collection will clean */ + debug3("passing an fd %d to an old session (fd: %d)", passed_fd, dst->pipe); + if (r_request_send(dst->pipe, type, &m) < 0) { + close(fd); + break; + } + close(fd); + + if (r_request_receive(dst->pipe, &m) <= 0) + break; + + r_request_send(r_pipe->pipe, buffer_get_char(&m), &m); + break; + } + + break; + + default: + break; + } + + buffer_free(&m); + } + for (i = 0; i < options.max_startups; i++) if (startup_pipes[i] != -1 && FD_ISSET(startup_pipes[i], fdset)) { @@ -1458,6 +1556,24 @@ continue; } + // todo: inetd mode? + if (options.suspend_grace_time) { + if (socketpair(AF_UNIX, SOCK_STREAM, 0, resilient_p) == -1) { + error("resilient socketpair: %s", + strerror(errno)); + close(newsock); + close(startup_p[0]); + close(startup_p[1]); + close(config_s[0]); + close(config_s[1]); + continue; + } + r_pipe = xmalloc(sizeof(*r_pipe)); + memset(r_pipe, 0, sizeof(*r_pipe)); + r_pipe->pipe = resilient_p[0]; + TAILQ_INSERT_TAIL(&resilient_pipes, r_pipe, next); + } + for (j = 0; j < options.max_startups; j++) if (startup_pipes[j] == -1) { startup_pipes[j] = startup_p[0]; @@ -1484,6 +1600,9 @@ close(startup_p[0]); close(startup_p[1]); startup_pipe = -1; + resilient_pipe = -1; + /* TODO: the resilient pipe? */ + pid = getpid(); if (rexec_flag) { send_rexec_state(config_s[0], @@ -1506,6 +1625,9 @@ * the connection. */ startup_pipe = startup_p[1]; + resilient_pipe = resilient_p[1]; + + close_resilient_pipes(); close_startup_pipes(); close_listen_socks(); sock_in = newsock; @@ -1551,6 +1673,8 @@ } } + debug3("child resilient_pipe = %x %ld", resilient_pipe, pid); + /* This is the child processing a new connection. */ setproctitle("%s", "[accepted]"); @@ -1571,7 +1695,6 @@ if (rexec_flag) { int fd; - debug("rexec start in %d out %d newsock %d pipe %d sock %d", sock_in, sock_out, newsock, startup_pipe, config_s[0]); dup2(newsock, STDIN_FILENO); @@ -1580,12 +1703,17 @@ close(REEXEC_STARTUP_PIPE_FD); else dup2(startup_pipe, REEXEC_STARTUP_PIPE_FD); + if (startup_pipe != -1) + close(startup_pipe); + dup2(resilient_pipe, REEXEC_RESILIENT_PIPE_FD); + if (resilient_pipe != -1) + close(resilient_pipe); + dup2(config_s[1], REEXEC_CONFIG_PASS_FD); close(config_s[1]); - if (startup_pipe != -1) - close(startup_pipe); + debug3(" before exec resilient_pipe = %x %s", resilient_pipe, rexec_argv[0]); execv(rexec_argv[0], rexec_argv); /* Reexec has failed, fall back and continue */ @@ -1593,9 +1721,10 @@ recv_rexec_state(REEXEC_CONFIG_PASS_FD, NULL); log_init(__progname, options.log_level, options.log_facility, log_stderr); - + debug3("exec done resilient_pipe = %x", resilient_pipe); /* Clean up fds */ startup_pipe = REEXEC_STARTUP_PIPE_FD; + resilient_pipe = REEXEC_RESILIENT_PIPE_FD; close(config_s[1]); close(REEXEC_CONFIG_PASS_FD); newsock = sock_out = sock_in = dup(STDIN_FILENO); @@ -1709,7 +1838,15 @@ * the current keystate and exits */ if (use_privsep) { - mm_send_keystate(pmonitor); + Buffer m; + + buffer_init(&m); + mm_send_keystate(&m, *pmonitor->m_pkex); + + mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_KEYEXPORT, &m); + debug3("%s: Finished sending state", __func__); + + buffer_free(&m); exit(0); } @@ -2004,12 +2141,17 @@ } myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = list_hostkey_types(); + /* todo: inetd mode */ + if (options.suspend_grace_time) + myproposal[PROPOSAL_KEX_ALGS] = KEX_RESILIENT_KEX; /* start key exchange */ kex = kex_setup(myproposal); kex->kex[KEX_DH_GRP1_SHA1] = kexdh_server; kex->kex[KEX_DH_GRP14_SHA1] = kexdh_server; kex->kex[KEX_DH_GEX_SHA1] = kexgex_server; + /* Insert the key exchange function for resilience here */ + kex->kex[KEX_REUSE] = kexreuse_server; kex->server = 1; kex->client_version_string=client_version_string; kex->server_version_string=server_version_string; Index: openssh-4.2p1/sshconnect.c =================================================================== --- openssh-4.2p1/sshconnect.c (revision 14) +++ openssh-4.2p1/sshconnect.c (revision 76) @@ -380,7 +380,7 @@ error("setsockopt SO_KEEPALIVE: %.100s", strerror(errno)); /* Set the connection. */ - packet_set_connection(sock, sock); + packet_set_connection(sock, sock); return 0; } @@ -389,7 +389,7 @@ * Waits for the server identification string, and sends our own * identification string. */ -static void +void ssh_exchange_identification(void) { char buf[256], remote_version[256]; /* must be same size! */ Index: openssh-4.2p1/sshconnect.h =================================================================== --- openssh-4.2p1/sshconnect.h (revision 14) +++ openssh-4.2p1/sshconnect.h (revision 76) @@ -40,10 +40,14 @@ void ssh_login(Sensitive *, const char *, struct sockaddr *, struct passwd *); +void +ssh_exchange_identification(void); + int verify_host_key(char *, struct sockaddr *, Key *); void ssh_kex(char *, struct sockaddr *); void ssh_kex2(char *, struct sockaddr *); +void ssh_kex2_reconnect(); void ssh_userauth1(const char *, const char *, char *, Sensitive *); void ssh_userauth2(const char *, const char *, char *, Sensitive *); Index: openssh-4.2p1/servconf.c =================================================================== --- openssh-4.2p1/servconf.c (revision 14) +++ openssh-4.2p1/servconf.c (revision 76) @@ -101,6 +101,7 @@ options->authorized_keys_file = NULL; options->authorized_keys_file2 = NULL; options->num_accept_env = 0; + options->suspend_grace_time = -1; /* Needs to be accessable in many places */ use_privsep = -1; @@ -229,6 +230,8 @@ } if (options->authorized_keys_file == NULL) options->authorized_keys_file = _PATH_SSH_USER_PERMITTED_KEYS; + if (options->suspend_grace_time == -1) + options->suspend_grace_time = 0; /* Turn privilege separation on by default */ if (use_privsep == -1) @@ -271,7 +274,7 @@ sHostbasedUsesNameFromPacketOnly, sClientAliveInterval, sClientAliveCountMax, sAuthorizedKeysFile, sAuthorizedKeysFile2, sGssAuthentication, sGssCleanupCreds, sAcceptEnv, - sUsePrivilegeSeparation, + sUsePrivilegeSeparation, sSuspendGraceTime, sDeprecated, sUnsupported } ServerOpCodes; @@ -373,6 +376,7 @@ { "authorizedkeysfile2", sAuthorizedKeysFile2 }, { "useprivilegeseparation", sUsePrivilegeSeparation}, { "acceptenv", sAcceptEnv }, + { "suspendgracetime", sSuspendGraceTime}, { NULL, sBadOption } }; @@ -512,6 +516,10 @@ intptr = &options->key_regeneration_time; goto parse_time; + case sSuspendGraceTime: + intptr = &options->suspend_grace_time; + goto parse_time; + case sListenAddress: arg = strdelim(&cp); if (arg == NULL || *arg == '\0') Index: openssh-4.2p1/servconf.h =================================================================== --- openssh-4.2p1/servconf.h (revision 14) +++ openssh-4.2p1/servconf.h (revision 76) @@ -134,6 +134,8 @@ char *authorized_keys_file; /* File containing public keys */ char *authorized_keys_file2; int use_pam; /* Enable auth via PAM */ + int suspend_grace_time; + } ServerOptions; void initialize_server_options(ServerOptions *); Index: openssh-4.2p1/resilient.c =================================================================== --- openssh-4.2p1/resilient.c (revision 0) +++ openssh-4.2p1/resilient.c (revision 76) @@ -0,0 +1,152 @@ +/* $OpenBSD: kex.h,v 1.37 2005/07/25 11:59:39 markus Exp $ */ + +/* + * Copyright (c) 2005 Teemu Koponen. 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" +RCSID("$OpenBSD: kex.c,v 1.64 2005/07/25 11:59:39 markus Exp $"); + +#include + +#include "atomicio.h" +#include "bufaux.h" +#include "buffer.h" +#include "getput.h" +#include "kex.h" +#include "log.h" +#include "monitor.h" +#include "monitor_fdpass.h" +#include "packet.h" +#include "resilient.h" +#include "ssh2.h" +#include "xmalloc.h" +#include "mac.h" + +int resilient_pipe = -1; + +/* Functions for privileged process. */ +int +r_request_send(int sock, enum monitor_reqtype type, Buffer *m) +{ + u_int mlen = buffer_len(m); + u_char buf[5]; + + debug3("%s entering: type %d", __func__, type); + + PUT_32BIT(buf, mlen + 1); + buf[4] = (u_char) type; /* 1st byte of payload is mesg-type */ + if (atomicio(write_fd, sock, buf, sizeof(buf)) != sizeof(buf)) + return -1; + if (atomicio(vwrite, sock, buffer_ptr(m), mlen) != mlen) + return -1; + return mlen; +} + +int +r_request_receive(int sock, Buffer *m) +{ + u_char buf[4]; + u_int msg_len; + + debug3("%s entering", __func__); + + passed_fd = -1; + if (atomicio(read_fd, sock, buf, sizeof(buf)) != sizeof(buf)) + return -1; + + msg_len = GET_32BIT(buf); + if (msg_len > 256 * 1024) + return -1; + buffer_clear(m); + buffer_append_space(m, msg_len); + if (atomicio(read, sock, buffer_ptr(m), msg_len) != msg_len) + return -1; + + return msg_len; +} + +int pass_connection(int sock, Buffer *m, Buffer *n) +{ + u_char type; + + debug3("%s: passing connection (fd = %d)", __func__, sock); + + passed_fd = sock; + if (r_request_send(resilient_pipe, MONITOR_REQ_PASS_CONNECTION, m) < 0) + fatal("%s: unable to pass connection", __func__); + if (r_request_receive(resilient_pipe, n) < 0) + fatal("%s: unable to pass", __func__); + + type = buffer_get_char(n); + if (type != MONITOR_ANS_PASS_CONNECTION) + fatal("%s: read: rtype %d != type %d", __func__, + type, MONITOR_ANS_PASS_CONNECTION); + + close(sock); + return(0); +} + +int validate_reconnect(Buffer *m, Buffer *n) +{ + u_char type; + + debug3("%s: entering", __func__); + + if (r_request_send(resilient_pipe, MONITOR_REQ_VALIDATE_RECONNECT, m) == 0) + fatal("%s: unable to validate", __func__); + if (r_request_receive(resilient_pipe, n) == 0) + fatal("%s: unable to validate", __func__); + + type = buffer_get_char(n); + if (type != MONITOR_ANS_VALIDATE_RECONNECT) + fatal("%s: read: rtype %d != type %d", __func__, + type, MONITOR_ANS_VALIDATE_RECONNECT); + return(0); +} + +int +get_resilient_pipe() +{ + return resilient_pipe; +} + +char * +hex_dump(u_char *data, int len) { + static char dump[1024]; + dump[0] = '\0'; + if (len > (int)(sizeof(dump) / 2 - 1)) + len = (int)(sizeof(dump) / 2 - 1); + while (len--) { + char byte[3]; + sprintf(byte, "%02x", *data++); + strcat(dump, byte); + } + return dump; +} + +void +resilient_register(Kex * kex) +{ + debug3("%s entering", __func__); +} Index: openssh-4.2p1/Makefile.in =================================================================== --- openssh-4.2p1/Makefile.in (revision 14) +++ openssh-4.2p1/Makefile.in (revision 76) @@ -70,11 +70,11 @@ readpass.o rsa.o ttymodes.o xmalloc.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 + kexgex.o kexdhc.o kexreusec.o kexreuses.o kexgexc.o scard.o msg.o progressmeter.o dns.o \ + entropy.o scard-opensc.o gss-genr.o resilient.o SSHOBJS= ssh.o readconf.o clientloop.o sshtty.o \ - sshconnect.o sshconnect1.o sshconnect2.o + sshconnect.o sshconnect1.o sshconnect2.o keystate.o SSHDOBJS=sshd.o auth-rhosts.o auth-passwd.o auth-rsa.o auth-rh-rsa.o \ sshpty.o sshlogin.o servconf.o serverloop.o \ @@ -86,7 +86,7 @@ auth-krb5.o \ auth2-gss.o gss-serv.o gss-serv-krb5.o \ loginrec.o auth-pam.o auth-shadow.o auth-sia.o md5crypt.o \ - audit.o audit-bsm.o + audit.o audit-bsm.o keystate.o MANPAGES = scp.1.out ssh-add.1.out ssh-agent.1.out ssh-keygen.1.out ssh-keyscan.1.out ssh.1.out sshd.8.out sftp-server.8.out sftp.1.out ssh-rand-helper.8.out ssh-keysign.8.out sshd_config.5.out ssh_config.5.out MANPAGES_IN = scp.1 ssh-add.1 ssh-agent.1 ssh-keygen.1 ssh-keyscan.1 ssh.1 sshd.8 sftp-server.8 sftp.1 ssh-rand-helper.8 ssh-keysign.8 sshd_config.5 ssh_config.5 Index: openssh-4.2p1/resilient.h =================================================================== --- openssh-4.2p1/resilient.h (revision 0) +++ openssh-4.2p1/resilient.h (revision 76) @@ -0,0 +1,52 @@ +/* $OpenBSD: kex.h,v 1.37 2005/07/25 11:59:39 markus Exp $ */ + +/* + * Copyright (c) 2005 Teemu Koponen. 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. + */ +#ifndef RESILIENT_H +#define RESILIENT_H + +#include "buffer.h" +#include "kex.h" +#include "monitor.h" +#include + +extern int resilient_pipe; + +struct resilient_pipe { + TAILQ_ENTRY(resilient_pipe) next; + u_char session_id[20]; + int session_id_len; + int pipe; +}; + +void r_close_bus_entry(struct resilient_pipe *) ; +int r_request_receive(int, Buffer *); +int r_request_send(int, enum monitor_reqtype, Buffer *); +void resilient_register(Kex *kex); +int validate_reconnect(Buffer *, Buffer *); +char * hex_dump(u_char *, int); +int pass_connection(int, Buffer *, Buffer *); +int get_resilient_pipe(); + +#endif Index: openssh-4.2p1/kexgex.c =================================================================== --- openssh-4.2p1/kexgex.c (revision 14) +++ openssh-4.2p1/kexgex.c (revision 76) @@ -74,7 +74,8 @@ buffer_put_bignum2(&b, gen); buffer_put_bignum2(&b, client_dh_pub); buffer_put_bignum2(&b, server_dh_pub); - buffer_put_bignum2(&b, shared_secret); + if (shared_secret) + buffer_put_bignum2(&b, shared_secret); #ifdef DEBUG_KEXDH buffer_dump(&b); Index: openssh-4.2p1/clientloop.c =================================================================== --- openssh-4.2p1/clientloop.c (revision 14) +++ openssh-4.2p1/clientloop.c (revision 76) @@ -84,7 +84,17 @@ #include "monitor_fdpass.h" #include "match.h" #include "msg.h" +#include "myproposal.h" +#include "resilient.h" +#include "sshconnect.h" +#include "keystate.h" +/* remote host */ +extern struct sockaddr_storage hostaddr; + +/* effective UID */ +extern uid_t original_effective_uid; + /* import options */ extern Options options; @@ -104,6 +114,9 @@ */ extern char *host; +extern Newkeys *newkeys[]; +extern Newkeys *oldkeys[]; + /* * Flag to indicate that we have received a window change signal which has * not yet been processed. This will cause a message indicating the new @@ -133,9 +146,13 @@ static int need_rekeying; /* Set to non-zero if rekeying is requested. */ static int session_closed = 0; /* In SSH2: login session closed. */ static int server_alive_timeouts = 0; +static struct timeval reconnect_time = {0, 0}; +static int resilient_pid = 0; +static int reconnect_process = 0; static void client_init_dispatch(void); int session_ident = -1; +u_int32_t old_read_seqnr; /* Old sequence # for SYNCHRONIZE processing */ struct confirm_ctx { int want_tty; @@ -488,13 +505,14 @@ memset(*writesetp, 0, *nallocp); return; } else { - FD_SET(connection_in, *readsetp); + if (!connection_closed) + FD_SET(connection_in, *readsetp); } } /* Select server connection if have data to write to the server. */ - if (packet_have_data_to_write()) - FD_SET(connection_out, *writesetp); + if (packet_have_data_to_write() && !connection_closed) + FD_SET(connection_out, *writesetp); if (control_fd != -1) FD_SET(control_fd, *readsetp); @@ -508,10 +526,27 @@ if (options.server_alive_interval == 0 || !compat20) tvp = NULL; else { - tv.tv_sec = options.server_alive_interval; - tv.tv_usec = 0; - tvp = &tv; + tv.tv_sec = options.server_alive_interval; + tv.tv_usec = 0; + tvp = &tv; } + + if (connection_closed) { + struct timeval now; + + tvp = &tv; + gettimeofday(&now, NULL); + tv.tv_sec = reconnect_time.tv_sec - now.tv_sec; + if (tv.tv_sec < 0) + tv.tv_sec = 0; + tv.tv_usec = 1; + } + + if (resilient && get_resilient_pipe() != -1) { + FD_SET(get_resilient_pipe(), *readsetp); + *maxfdp = MAX(*maxfdp, get_resilient_pipe()); + } + ret = select((*maxfdp)+1, *readsetp, *writesetp, NULL, tvp); if (ret < 0) { char buf[100]; @@ -530,8 +565,8 @@ snprintf(buf, sizeof buf, "select: %s\r\n", strerror(errno)); buffer_append(&stderr_buffer, buf, strlen(buf)); quit_pending = 1; - } else if (ret == 0) - server_alive_check(); + } else if (ret == 0 && !connection_closed) + server_alive_check(); } static void @@ -585,7 +620,9 @@ snprintf(buf, sizeof buf, "Connection to %.300s closed by remote host.\r\n", host); buffer_append(&stderr_buffer, buf, strlen(buf)); - quit_pending = 1; + connection_closed = 1; + quit_pending = 1; + return; } /* @@ -600,7 +637,9 @@ snprintf(buf, sizeof buf, "Read from remote host %.300s: %.100s\r\n", host, strerror(errno)); buffer_append(&stderr_buffer, buf, strlen(buf)); - quit_pending = 1; + connection_closed = 1; + quit_pending = 1; + return; } packet_process_incoming(buf, len); @@ -675,7 +714,73 @@ xfree(cctx); } +void +client_reconnect_sighandler(int signo) +{ + struct timeval now; + + debug("reconnect request -> reconnecting (signal %d).", signo); + connection_closed = 1; + gettimeofday(&now, NULL); + reconnect_time.tv_sec = now.tv_sec; + reconnect_time.tv_usec = 0; +} + static void +client_process_reconnect(fd_set * readset) +{ + struct timeval now; + Buffer b; + + if (get_resilient_pipe() != -1 && FD_ISSET(get_resilient_pipe(), readset)) { + debug3("%s: keystate incoming", __func__); + buffer_init(&b); + + if (r_request_receive(get_resilient_pipe(), &b) < 0) { + debug3("%s: reconnect child on pid %d died", __func__, resilient_pid); + gettimeofday(&now, NULL); + waitpid(resilient_pid, NULL, 0); + debug3("pid %d done", resilient_pid); + + /* determine the next reconnect time */ + reconnect_time.tv_usec = reconnect_time.tv_usec ? reconnect_time.tv_usec << 1 : 1; + reconnect_time.tv_sec = now.tv_sec + reconnect_time.tv_usec; + debug("reconnecting in %d seconds", reconnect_time.tv_usec); + + resilient_pipe = -1; + resilient_pid = 0; + buffer_free(&b); + return; + } + + packet_set_connection(passed_fd, passed_fd); + passed_fd = -1; + buffer_get_char(&b); /* type */ + + mm_get_keystate(&b, &xxx_kex); + debug3("%s: deserialized", __func__); + + monitor_apply_keystate(NULL, NULL); + debug3("%s: applied", __func__); + + packet_synchronize(); + + buffer_clear(&b); + buffer_put_int(&b, 1); + r_request_send(get_resilient_pipe(), MONITOR_ANS_PASS_CONNECTION, &b); + + waitpid(resilient_pid, NULL, 0); + close(resilient_pipe); + resilient_pipe = -1; + resilient_pid = 0; + connection_closed = 0; + memset(&reconnect_time, 0, sizeof(struct timeval)); + buffer_free(&b); + debug3("%s: done.", __func__); + } +} + +static void client_process_control(fd_set * readset) { Buffer m; @@ -1298,13 +1403,83 @@ leave_raw_mode(); } +/* After every possible quit_pending and resilient mode turned on, we + should move to queueing mode and try to reconnect? */ +static void reconnect() +{ + struct timeval now; + int resilient_p[2], mode; + Buffer m, n; + + gettimeofday(&now, NULL); + if ((reconnect_time.tv_sec && now.tv_sec < reconnect_time.tv_sec) || resilient_pid) + return; + + debug3("%s: time to reconnect (pid = %d, sec = %d)", __func__, resilient_pid, reconnect_time.tv_sec); + + if (socketpair(AF_UNIX, SOCK_STREAM, 0, resilient_p) == -1) + fatal("%s: socketpair: %s", __func__, strerror(errno)); + + if ((resilient_pid = fork()) != 0) { + /* Parent */ + resilient_pipe = resilient_p[0]; + close(resilient_p[1]); + return; + } + + debug3("%s: child, reconnecting in progress", __func__); + + /* to prevent the cleanup_exit cleaning the tty */ + reconnect_process = 1; + resilient_pipe = resilient_p[1]; + close(resilient_p[0]); + + old_read_seqnr = p_read.seqnr; + p_read.seqnr = 0; + p_send.seqnr = 0; + for (mode = 0; mode < MODE_MAX; mode++) + xxx_kex->newkeys[mode] = newkeys[mode]; + + /* Re-open a connection to the remote host. */ + if (ssh_connect(host, &hostaddr, options.port, + options.address_family, 1, +#ifdef HAVE_CYGWIN + options.use_privileged_port, +#else + original_effective_uid == 0 && options.use_privileged_port, +#endif + options.proxy_command) != 0) + cleanup_exit(255); + + /* Exchange protocol version identification strings with the server. */ + ssh_exchange_identification(); + + /* Put the connection into non-blocking mode. */ + packet_set_nonblocking(); + + /* Flush packet buffers */ + packet_flush(); + + /* resync */ + ssh_kex2_reconnect(); + + debug3("reconnected, serializing the state"); + + buffer_init(&m); + buffer_init(&n); + mm_send_keystate(&m, xxx_kex); + pass_connection(packet_get_connection_in(), &m, &n); + + debug3("reconnection child dying"); + cleanup_exit(255); +} + /* * Implements the interactive session with the server. This is called after * the user has been authenticated, and a command has been started on the * remote host. If escape_char != SSH_ESCAPECHAR_NONE, it is the character * used as an escape character for terminating or suspending the session. */ - int client_loop(int have_pty, int escape_char_arg, int ssh2_chan_id) { @@ -1326,9 +1501,6 @@ buffer_high = 64 * 1024; connection_in = packet_get_connection_in(); connection_out = packet_get_connection_out(); - max_fd = MAX(connection_in, connection_out); - if (control_fd != -1) - max_fd = MAX(max_fd, control_fd); if (!compat20) { /* enable nonblocking unless tty */ @@ -1386,16 +1558,20 @@ } /* Main loop of the client for the interactive session mode. */ - while (!quit_pending) { + while (1) { + if (connection_closed && resilient) { + reconnect(); + quit_pending = 0; + } + /* Quit if not reconnecting */ + if (quit_pending) + break; /* Process buffered packets sent by the server. */ client_process_buffered_input_packets(); - if (compat20 && session_closed && !channel_still_open()) break; - rekeying = (xxx_kex != NULL && !xxx_kex->done); - if (rekeying) { debug("rekeying in progress"); } else { @@ -1410,7 +1586,7 @@ * Make packets from buffered channel data, and * enqueue them for sending to the server. */ - if (packet_not_very_much_data_to_write()) + if (packet_not_very_much_data_to_write()) channel_output_poll(); /* @@ -1420,18 +1596,22 @@ client_check_window_change(); if (quit_pending) - break; + continue; } /* * Wait until we have something to do (something becomes * available on one of the descriptors). */ + max_fd = MAX(connection_in, connection_out); + if (control_fd != -1) + max_fd = MAX(max_fd, control_fd); + max_fd2 = max_fd; client_wait_until_can_do_something(&readset, &writeset, - &max_fd2, &nalloc, rekeying); + &max_fd2, &nalloc, rekeying); if (quit_pending) - break; + continue; /* Do channel operations unless rekeying in progress. */ if (!rekeying) { @@ -1443,15 +1623,17 @@ need_rekeying = 0; } } - /* Buffer input from the connection. */ client_process_net_input(readset); /* Accept control connections. */ client_process_control(readset); + /* Reconnect state transfer. */ + client_process_reconnect(readset); + if (quit_pending) - break; + continue; if (!compat20) { /* Buffer data from stdin */ @@ -1996,9 +2178,11 @@ void cleanup_exit(int i) { - leave_raw_mode(); - leave_non_blocking(); - if (options.control_path != NULL && control_fd != -1) - unlink(options.control_path); + if (!reconnect_process) { + leave_raw_mode(); + leave_non_blocking(); + if (options.control_path != NULL && control_fd != -1) + unlink(options.control_path); + } _exit(i); } Index: openssh-4.2p1/clientloop.h =================================================================== --- openssh-4.2p1/clientloop.h (revision 14) +++ openssh-4.2p1/clientloop.h (revision 76) @@ -42,6 +42,7 @@ void client_global_request_reply_fwd(int, u_int32_t, void *); void client_session2_setup(int, int, int, const char *, struct termios *, int, Buffer *, char **, dispatch_fn *); +void client_reconnect_sighandler(int); /* Multiplexing protocol version */ #define SSHMUX_VER 1 Index: openssh-4.2p1/config.h.in =================================================================== --- openssh-4.2p1/config.h.in (revision 14) +++ openssh-4.2p1/config.h.in (revision 76) @@ -283,6 +283,9 @@ /* Define if you want to allow MD5 passwords */ #undef HAVE_MD5_PASSWORDS +/* Define if you want to enable resilient connections */ +#undef RESILIENT + /* Define if you want to disable shadow passwords */ #undef DISABLE_SHADOW Index: openssh-4.2p1/ssh2.h =================================================================== --- openssh-4.2p1/ssh2.h (revision 14) +++ openssh-4.2p1/ssh2.h (revision 76) @@ -80,6 +80,8 @@ #define SSH2_MSG_DEBUG 4 #define SSH2_MSG_SERVICE_REQUEST 5 #define SSH2_MSG_SERVICE_ACCEPT 6 +#define SSH2_MSG_ACK 7 +#define SSH2_MSG_SYNCHRONIZE 8 /* transport layer: alg negotiation */ @@ -98,6 +100,10 @@ #define SSH2_MSG_KEX_DH_GEX_REPLY 33 #define SSH2_MSG_KEX_DH_GEX_REQUEST 34 +/* reconnect */ +#define SSH2_MSG_KEXRECONNECT_INIT 30 +#define SSH2_MSG_KEXRECONNECT_REPLY 31 + /* user authentication: generic */ #define SSH2_MSG_USERAUTH_REQUEST 50 Index: openssh-4.2p1/monitor.c =================================================================== --- openssh-4.2p1/monitor.c (revision 14) +++ openssh-4.2p1/monitor.c (revision 76) @@ -63,6 +63,8 @@ #include "bufaux.h" #include "compat.h" #include "ssh2.h" +#include "keystate.h" +#include "resilient.h" #ifdef GSSAPI #include "ssh-gss.h" @@ -81,29 +83,6 @@ extern int auth_debug_init; extern Buffer loginmsg; -/* State exported from the child */ - -struct { - z_stream incoming; - z_stream outgoing; - u_char *keyin; - u_int keyinlen; - u_char *keyout; - u_int keyoutlen; - u_char *ivin; - u_int ivinlen; - u_char *ivout; - u_int ivoutlen; - u_char *ssh1key; - u_int ssh1keylen; - int ssh1cipher; - int ssh1protoflags; - u_char *input; - u_int ilen; - u_char *output; - u_int olen; -} child_state; - /* Functions on the monitor that answer unprivileged requests */ int mm_answer_moduli(int, Buffer *); @@ -121,11 +100,17 @@ int mm_answer_pty(int, Buffer *); int mm_answer_pty_cleanup(int, Buffer *); int mm_answer_term(int, Buffer *); +int mm_pass_validate_reconnect_req(int sock, Buffer *m); +int mm_pass_validate_reconnect_ans(int sock, Buffer *m); +int mm_pass_pass_connection_req(int sock, Buffer *m); +int mm_pass_pass_connection_ans(int sock, Buffer *m); +int mm_pass_register(int sock, Buffer *m); int mm_answer_rsa_keyallowed(int, Buffer *); int mm_answer_rsa_challenge(int, Buffer *); int mm_answer_rsa_response(int, Buffer *); int mm_answer_sesskey(int, Buffer *); int mm_answer_sessid(int, Buffer *); +int mm_pass(int, Buffer *); #ifdef USE_PAM int mm_answer_pam_start(int, Buffer *); @@ -162,20 +147,6 @@ static u_char *session_id2 = NULL; static pid_t monitor_child_pid; -struct mon_table { - enum monitor_reqtype type; - int flags; - int (*f)(int, Buffer *); -}; - -#define MON_ISAUTH 0x0004 /* Required for Authentication */ -#define MON_AUTHDECIDE 0x0008 /* Decides Authentication */ -#define MON_ONCE 0x0010 /* Disable after calling */ - -#define MON_AUTH (MON_ISAUTH|MON_AUTHDECIDE) - -#define MON_PERMIT 0x1000 /* Request is permitted */ - struct mon_table mon_dispatch_proto20[] = { {MONITOR_REQ_MODULI, MON_ONCE, mm_answer_moduli}, {MONITOR_REQ_SIGN, MON_ONCE, mm_answer_sign}, @@ -210,6 +181,9 @@ {MONITOR_REQ_GSSUSEROK, MON_AUTH, mm_answer_gss_userok}, {MONITOR_REQ_GSSCHECKMIC, MON_ISAUTH, mm_answer_gss_checkmic}, #endif + {MONITOR_REQ_VALIDATE_RECONNECT, MON_ONCE, mm_pass_validate_reconnect_req}, + {MONITOR_REQ_NIL, MON_ONCE, mm_pass_register}, + {MONITOR_REQ_PASS_CONNECTION, MON_ONCE, mm_pass_pass_connection_req}, {0, 0, NULL} }; @@ -223,6 +197,8 @@ {MONITOR_REQ_AUDIT_EVENT, MON_PERMIT, mm_answer_audit_event}, {MONITOR_REQ_AUDIT_COMMAND, MON_PERMIT, mm_answer_audit_command}, #endif + {MONITOR_ANS_VALIDATE_RECONNECT, MON_PERMIT, mm_pass_validate_reconnect_ans}, + {MONITOR_ANS_PASS_CONNECTION, MON_PERMIT, mm_pass_pass_connection_ans}, {0, 0, NULL} }; @@ -269,6 +245,7 @@ }; struct mon_table *mon_dispatch; +struct mon_table *mon_resilient_dispatch; /* Specifies if a certain message is allowed at the moment */ @@ -318,15 +295,22 @@ /* Permit requests for moduli and signatures */ monitor_permit(mon_dispatch, MONITOR_REQ_MODULI, 1); monitor_permit(mon_dispatch, MONITOR_REQ_SIGN, 1); + + /* Permit reconnect attempts */ + monitor_permit(mon_dispatch, MONITOR_REQ_VALIDATE_RECONNECT, 1); + monitor_permit(mon_dispatch, MONITOR_REQ_NIL, 1); + monitor_permit(mon_dispatch, MONITOR_REQ_PASS_CONNECTION, 1); } else { mon_dispatch = mon_dispatch_proto15; monitor_permit(mon_dispatch, MONITOR_REQ_SESSKEY, 1); } + /* TODO: in principle, the resilient bus should be monitored already here? */ + /* The first few requests do not require asynchronous access */ while (!authenticated) { - authenticated = monitor_read(pmonitor, mon_dispatch, &ent); + authenticated = monitor_read(pmonitor->m_sendfd, mon_dispatch, &ent); if (authenticated) { if (!(ent->flags & MON_AUTHDECIDE)) fatal("%s: unexpected authentication from %d", @@ -362,7 +346,19 @@ debug("%s: %s has been authenticated by privileged process", __func__, authctxt->user); - mm_get_keystate(pmonitor); + debug3("%s: Waiting for new keys", __func__); + + Buffer m; + buffer_init(&m); + mm_request_receive_expect(pmonitor->m_sendfd, MONITOR_REQ_KEYEXPORT, &m); + mm_get_keystate(&m, pmonitor->m_pkex); + (*pmonitor->m_pkex)->kex[KEX_DH_GRP1_SHA1] = kexdh_server; + (*pmonitor->m_pkex)->kex[KEX_DH_GRP14_SHA1] = kexdh_server; + (*pmonitor->m_pkex)->kex[KEX_DH_GEX_SHA1] = kexgex_server; + (*pmonitor->m_pkex)->server = 1; + (*pmonitor->m_pkex)->load_host_key=&get_hostkey_by_type; + (*pmonitor->m_pkex)->host_key_index=&get_hostkey_index; + buffer_free(&m); } static void @@ -380,6 +376,8 @@ void monitor_child_postauth(struct monitor *pmonitor) { + debug3("%s: entering", __func__); + monitor_set_child_handler(pmonitor->m_pid); signal(SIGHUP, &monitor_child_handler); signal(SIGTERM, &monitor_child_handler); @@ -400,8 +398,42 @@ monitor_permit(mon_dispatch, MONITOR_REQ_PTYCLEANUP, 1); } - for (;;) - monitor_read(pmonitor, mon_dispatch, NULL); + for (;;) { + int max_fd; + fd_set readset; + + FD_ZERO(&readset); + FD_SET(pmonitor->m_sendfd, &readset); + if (resilient_pipe != -1) + FD_SET(resilient_pipe, &readset); + max_fd = MAX(pmonitor->m_sendfd, resilient_pipe); + + if (select(max_fd + 1, &readset, NULL, NULL, NULL) < 0) { + if (errno != EINTR) + error("select: %.100s", strerror(errno)); + continue; + } + + if (FD_ISSET(pmonitor->m_sendfd, &readset)) { + monitor_read(pmonitor->m_sendfd, mon_dispatch, NULL); + debug3("%s: monitor_read done.", __func__); + } + /* Forward resilient messages as such */ + if (resilient_pipe != -1 && FD_ISSET(resilient_pipe, &readset)) { + Buffer m; + int fd; + + buffer_init(&m); + r_request_receive(resilient_pipe, &m); + fd = passed_fd; + r_request_send(pmonitor->m_sendfd, buffer_get_char(&m), &m); + if (fd != -1) + close(fd); + buffer_free(&m); + } + } + + debug3("%s: monitor done", __func__); } void @@ -414,8 +446,9 @@ } int -monitor_read(struct monitor *pmonitor, struct mon_table *ent, - struct mon_table **pent) +monitor_read(int sock, + struct mon_table *ent, + struct mon_table **pent) { Buffer m; int ret; @@ -423,7 +456,7 @@ buffer_init(&m); - mm_request_receive(pmonitor->m_sendfd, &m); + mm_request_receive(sock, &m); type = buffer_get_char(&m); debug3("%s: checking request %d", __func__, type); @@ -438,7 +471,7 @@ if (!(ent->flags & MON_PERMIT)) fatal("%s: unpermitted request %d", __func__, type); - ret = (*ent->f)(pmonitor->m_sendfd, &m); + ret = (*ent->f)(sock, &m); buffer_free(&m); /* The child may use this request only once, disable it */ @@ -451,6 +484,7 @@ if (pent != NULL) *pent = ent; + debug3("%s: request processed.", __func__); return ret; } @@ -523,6 +557,56 @@ return (0); } +int +mm_pass_pass_connection_req(int sock, Buffer *m) +{ + Buffer n; + buffer_init(&n); + + debug3("%s: entering", __func__); + + pass_connection(packet_get_connection_in(), m, &n); + + mm_request_send(sock, MONITOR_ANS_PASS_CONNECTION, &n); + buffer_free(&n); + return (0); +} + +int +mm_pass_pass_connection_ans(int sock, Buffer *m) +{ + debug3("%s: forwarding pass connection answer", __func__); + mm_request_send(resilient_pipe, MONITOR_ANS_PASS_CONNECTION, m); + return (0); +} + +int +mm_pass_validate_reconnect_req(int sock, Buffer *m) +{ + Buffer n; + + buffer_init(&n); + validate_reconnect(m, &n); + mm_request_send(sock, MONITOR_ANS_VALIDATE_RECONNECT, &n); + return (0); +} + +int +mm_pass_validate_reconnect_ans(int sock, Buffer *m) +{ + debug3("%s: forwarding validation answer", __func__); + mm_request_send(resilient_pipe, MONITOR_ANS_VALIDATE_RECONNECT, m); + return (0); +} + +int +mm_pass_register(int sock, Buffer *m) +{ + debug3("%s: forwarding the register request (%d bytes)", __func__, buffer_len(m)); + mm_request_send(resilient_pipe, MONITOR_REQ_NIL, m); + return (0); +} + int mm_answer_sign(int sock, Buffer *m) { @@ -1557,169 +1641,6 @@ } #endif /* SSH_AUDIT_EVENTS */ -void -monitor_apply_keystate(struct monitor *pmonitor) -{ - if (compat20) { - set_newkeys(MODE_IN); - set_newkeys(MODE_OUT); - } else { - packet_set_protocol_flags(child_state.ssh1protoflags); - packet_set_encryption_key(child_state.ssh1key, - child_state.ssh1keylen, child_state.ssh1cipher); - xfree(child_state.ssh1key); - } - - /* for rc4 and other stateful ciphers */ - packet_set_keycontext(MODE_OUT, child_state.keyout); - xfree(child_state.keyout); - packet_set_keycontext(MODE_IN, child_state.keyin); - xfree(child_state.keyin); - - if (!compat20) { - packet_set_iv(MODE_OUT, child_state.ivout); - xfree(child_state.ivout); - packet_set_iv(MODE_IN, child_state.ivin); - xfree(child_state.ivin); - } - - memcpy(&incoming_stream, &child_state.incoming, - sizeof(incoming_stream)); - memcpy(&outgoing_stream, &child_state.outgoing, - sizeof(outgoing_stream)); - - /* Update with new address */ - if (options.compression) - mm_init_compression(pmonitor->m_zlib); - - /* Network I/O buffers */ - /* XXX inefficient for large buffers, need: buffer_init_from_string */ - buffer_clear(&input); - buffer_append(&input, child_state.input, child_state.ilen); - memset(child_state.input, 0, child_state.ilen); - xfree(child_state.input); - - buffer_clear(&output); - buffer_append(&output, child_state.output, child_state.olen); - memset(child_state.output, 0, child_state.olen); - xfree(child_state.output); -} - -static Kex * -mm_get_kex(Buffer *m) -{ - Kex *kex; - void *blob; - u_int bloblen; - - kex = xmalloc(sizeof(*kex)); - memset(kex, 0, sizeof(*kex)); - kex->session_id = buffer_get_string(m, &kex->session_id_len); - if ((session_id2 == NULL) || - (kex->session_id_len != session_id2_len) || - (memcmp(kex->session_id, session_id2, session_id2_len) != 0)) - fatal("mm_get_get: internal error: bad session id"); - kex->we_need = buffer_get_int(m); - kex->kex[KEX_DH_GRP1_SHA1] = kexdh_server; - kex->kex[KEX_DH_GRP14_SHA1] = kexdh_server; - kex->kex[KEX_DH_GEX_SHA1] = kexgex_server; - kex->server = 1; - kex->hostkey_type = buffer_get_int(m); - kex->kex_type = buffer_get_int(m); - blob = buffer_get_string(m, &bloblen); - buffer_init(&kex->my); - buffer_append(&kex->my, blob, bloblen); - xfree(blob); - blob = buffer_get_string(m, &bloblen); - buffer_init(&kex->peer); - buffer_append(&kex->peer, blob, bloblen); - xfree(blob); - kex->done = 1; - kex->flags = buffer_get_int(m); - kex->client_version_string = buffer_get_string(m, NULL); - kex->server_version_string = buffer_get_string(m, NULL); - kex->load_host_key=&get_hostkey_by_type; - kex->host_key_index=&get_hostkey_index; - - return (kex); -} - -/* This function requries careful sanity checking */ - -void -mm_get_keystate(struct monitor *pmonitor) -{ - Buffer m; - u_char *blob, *p; - u_int bloblen, plen; - u_int32_t seqnr, packets; - u_int64_t blocks; - - debug3("%s: Waiting for new keys", __func__); - - buffer_init(&m); - mm_request_receive_expect(pmonitor->m_sendfd, MONITOR_REQ_KEYEXPORT, &m); - if (!compat20) { - child_state.ssh1protoflags = buffer_get_int(&m); - child_state.ssh1cipher = buffer_get_int(&m); - child_state.ssh1key = buffer_get_string(&m, - &child_state.ssh1keylen); - child_state.ivout = buffer_get_string(&m, - &child_state.ivoutlen); - child_state.ivin = buffer_get_string(&m, &child_state.ivinlen); - goto skip; - } else { - /* Get the Kex for rekeying */ - *pmonitor->m_pkex = mm_get_kex(&m); - } - - blob = buffer_get_string(&m, &bloblen); - current_keys[MODE_OUT] = mm_newkeys_from_blob(blob, bloblen); - xfree(blob); - - debug3("%s: Waiting for second key", __func__); - blob = buffer_get_string(&m, &bloblen); - current_keys[MODE_IN] = mm_newkeys_from_blob(blob, bloblen); - xfree(blob); - - /* Now get sequence numbers for the packets */ - seqnr = buffer_get_int(&m); - blocks = buffer_get_int64(&m); - packets = buffer_get_int(&m); - packet_set_state(MODE_OUT, seqnr, blocks, packets); - seqnr = buffer_get_int(&m); - blocks = buffer_get_int64(&m); - packets = buffer_get_int(&m); - packet_set_state(MODE_IN, seqnr, blocks, packets); - - skip: - /* Get the key context */ - child_state.keyout = buffer_get_string(&m, &child_state.keyoutlen); - child_state.keyin = buffer_get_string(&m, &child_state.keyinlen); - - debug3("%s: Getting compression state", __func__); - /* Get compression state */ - p = buffer_get_string(&m, &plen); - if (plen != sizeof(child_state.outgoing)) - fatal("%s: bad request size", __func__); - memcpy(&child_state.outgoing, p, sizeof(child_state.outgoing)); - xfree(p); - - p = buffer_get_string(&m, &plen); - if (plen != sizeof(child_state.incoming)) - fatal("%s: bad request size", __func__); - memcpy(&child_state.incoming, p, sizeof(child_state.incoming)); - xfree(p); - - /* Network I/O buffers */ - debug3("%s: Getting Network I/O buffers", __func__); - child_state.input = buffer_get_string(&m, &child_state.ilen); - child_state.output = buffer_get_string(&m, &child_state.olen); - - buffer_free(&m); -} - - /* Allocation functions for zlib */ void * mm_zalloc(struct mm_master *mm, u_int ncount, u_int size) @@ -1760,7 +1681,7 @@ fatal("fcntl(%d, F_SETFD)", x); \ } while (0) -static void +void monitor_socketpair(int *pair) { #ifdef HAVE_SOCKETPAIR Index: openssh-4.2p1/monitor.h =================================================================== --- openssh-4.2p1/monitor.h (revision 14) +++ openssh-4.2p1/monitor.h (revision 76) @@ -60,7 +60,10 @@ MONITOR_REQ_PAM_RESPOND, MONITOR_ANS_PAM_RESPOND, MONITOR_REQ_PAM_FREE_CTX, MONITOR_ANS_PAM_FREE_CTX, MONITOR_REQ_AUDIT_EVENT, MONITOR_REQ_AUDIT_COMMAND, - MONITOR_REQ_TERM + MONITOR_REQ_TERM, + MONITOR_REQ_NIL, + MONITOR_REQ_VALIDATE_RECONNECT, MONITOR_ANS_VALIDATE_RECONNECT, + MONITOR_REQ_PASS_CONNECTION, MONITOR_ANS_PASS_CONNECTION, }; struct mm_master; @@ -72,17 +75,31 @@ struct Kex **m_pkex; pid_t m_pid; }; +struct mon_table { + enum monitor_reqtype type; + int flags; + int (*f)(int, Buffer *); +}; +#define MON_ISAUTH 0x0004 /* Required for Authentication */ +#define MON_AUTHDECIDE 0x0008 /* Decides Authentication */ +#define MON_ONCE 0x0010 /* Disable after calling */ + +#define MON_AUTH (MON_ISAUTH|MON_AUTHDECIDE) + +#define MON_PERMIT 0x1000 /* Request is permitted */ + struct monitor *monitor_init(void); void monitor_reinit(struct monitor *); void monitor_sync(struct monitor *); +void monitor_socketpair(int *pair); struct Authctxt; void monitor_child_preauth(struct Authctxt *, struct monitor *); void monitor_child_postauth(struct monitor *); struct mon_table; -int monitor_read(struct monitor*, struct mon_table *, struct mon_table **); +int monitor_read(int, struct mon_table *, struct mon_table **); /* Prototypes for request sending and receiving */ void mm_request_send(int, enum monitor_reqtype, Buffer *); Index: openssh-4.2p1/configure =================================================================== --- openssh-4.2p1/configure (revision 14) +++ openssh-4.2p1/configure (revision 76) @@ -25821,9 +25821,9 @@ exec 5>>config.log { echo - sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <&5 cat >&5 <<_CSEOF Index: openssh-4.2p1/serverloop.c =================================================================== --- openssh-4.2p1/serverloop.c (revision 14) +++ openssh-4.2p1/serverloop.c (revision 76) @@ -37,6 +37,8 @@ #include "includes.h" RCSID("$OpenBSD: serverloop.c,v 1.118 2005/07/17 07:17:55 djm Exp $"); +#include + #include "xmalloc.h" #include "packet.h" #include "buffer.h" @@ -55,8 +57,17 @@ #include "serverloop.h" #include "misc.h" #include "kex.h" +#include "resilient.h" +#include "monitor.h" +#include "monitor_wrap.h" +#include "monitor_fdpass.h" +#include "bufaux.h" +#include "keystate.h" +extern Newkeys *newkeys[]; +extern char *server_version_string; extern ServerOptions options; +extern struct monitor *pmonitor; /* XXX */ extern Kex *xxx_kex; @@ -79,9 +90,9 @@ static int fdin_is_tty = 0; /* fdin points to a tty. */ static int connection_in; /* Connection to client (input). */ static int connection_out; /* Connection to client (output). */ -static int connection_closed = 0; /* Connection to client closed. */ static u_int buffer_high; /* "Soft" max buffer size. */ static int client_alive_timeouts = 0; +static int daemon_channel; /* Descriptor for daemon process communication */ /* * This SIGCHLD kludge is used to detect when the child exits. The server @@ -90,9 +101,19 @@ static volatile sig_atomic_t child_terminated = 0; /* The child has terminated. */ +static struct timeval suspend_time = {0, 0}; /* Suspend start time */ + /* prototypes */ static void server_init_dispatch(void); +static int answer_validate_reconnect(int, Buffer *); +static int answer_pass_connection(int, Buffer *); +static struct mon_table resilient_dispatch[] = { + {MONITOR_REQ_VALIDATE_RECONNECT, MON_PERMIT, answer_validate_reconnect}, + {MONITOR_REQ_PASS_CONNECTION, MON_PERMIT, answer_pass_connection}, + {0, 0, NULL} +}; + /* * we write to this pipe if a SIGCHLD is caught in order to avoid * the race between select() and child_terminated @@ -254,12 +275,25 @@ * this could be randomized somewhat to make traffic * analysis more difficult, but we're not doing it yet. */ - if (compat20 && - max_time_milliseconds == 0 && options.client_alive_interval) { - client_alive_scheduled = 1; - max_time_milliseconds = options.client_alive_interval * 1000; - } + if (!connection_closed) + if (compat20 && + max_time_milliseconds == 0 && options.client_alive_interval) { + client_alive_scheduled = 1; + max_time_milliseconds = options.client_alive_interval * 1000; + } + /* if suspended, do not wait infinitely */ + if (connection_closed) { + struct timeval time; + gettimeofday(&time, NULL); + if ((time.tv_sec - suspend_time.tv_sec) < options.suspend_grace_time) { + u_int grace_left = options.suspend_grace_time - + (time.tv_sec - suspend_time.tv_sec); + if (grace_left * 1000 < max_time_milliseconds) + max_time_milliseconds = grace_left * 1000; + } + } + /* Allocate and update select() masks for channel descriptors. */ channel_prepare_select(readsetp, writesetp, maxfdp, nallocp, 0); @@ -268,7 +302,9 @@ /* wrong: bad condition XXX */ if (channel_not_very_much_buffered_data()) #endif - FD_SET(connection_in, *readsetp); + if (!connection_closed) + + FD_SET(connection_in, *readsetp); } else { /* * Read packets from the client unless we have too much @@ -300,9 +336,14 @@ * If we have buffered packet data going to the client, mark that * descriptor. */ - if (packet_have_data_to_write()) - FD_SET(connection_out, *writesetp); + if (packet_have_data_to_write() && !connection_closed) + FD_SET(connection_out, *writesetp); + if (resilient) { + FD_SET(PRIVSEP(get_resilient_pipe()), *readsetp); + *maxfdp = MAX(*maxfdp, PRIVSEP(get_resilient_pipe())); + } + /* * If child has terminated and there is enough buffer space to read * from it, then read as much as is available and exit. @@ -333,6 +374,124 @@ notify_done(*readsetp); } +/* Verifies the MAC client sent */ +static int answer_validate_reconnect(int sock, Buffer *m) +{ + static int md_len; + static u_char md[EVP_MAX_MD_SIZE]; + u_char * my = NULL, * peer = NULL, * resilient_id, + * client_version_string, * req_mac; + u_int resilient_id_len, my_len, peer_len, client_version_len, + mac_len, valid = 0; + HMAC_CTX c; + Mac *mac = &newkeys[MODE_IN]->mac; + const u_char one = 1, two = 2; + + debug3("%s: validating a reconnection request", __func__); + + resilient_id = buffer_get_string(m, &resilient_id_len); + client_version_string = buffer_get_string(m, &client_version_len); + my = buffer_get_string(m, &my_len); + peer = buffer_get_string(m, &peer_len); + req_mac = buffer_get_string(m, &mac_len); + + /* client MAC = f(k, "1" | V_C | V_S | I_C | I_S) */ + HMAC_Init(&c, mac->key, mac->key_len, mac->md); + HMAC_Update(&c, &one, sizeof(one)); + HMAC_Update(&c, client_version_string, client_version_len); + HMAC_Update(&c, (const unsigned char *)server_version_string, strlen(server_version_string)); + HMAC_Update(&c, peer, peer_len); + HMAC_Update(&c, my, my_len); + HMAC_Final(&c, md, NULL); + md_len = mac->mac_len; + HMAC_cleanup(&c); + + valid = mac->mac_len == mac_len && !memcmp(md, req_mac, mac_len); + debug3("client gave %s MAC", valid ? "valid" : "invalid"); + + buffer_clear(m); + buffer_put_int(m, valid); + + if (valid) { + /* server MAC = f(k, "2" | V_C | V_S | I_C | I_S) */ + HMAC_Init(&c, mac->key, mac->key_len, mac->md); + HMAC_Update(&c, &two, sizeof(two)); + HMAC_Update(&c, client_version_string, client_version_len); + HMAC_Update(&c, (const unsigned char *)server_version_string, strlen(server_version_string)); + HMAC_Update(&c, peer, peer_len); + HMAC_Update(&c, my, my_len); + HMAC_Final(&c, md, NULL); + HMAC_cleanup(&c); + buffer_put_string(m, md, mac->mac_len); + buffer_put_string(m, xxx_kex->session_id, xxx_kex->session_id_len); + buffer_put_bignum2(m, xxx_kex->shared_secret); + buffer_put_int(m, p_read.seqnr); + } + + r_request_send(sock, MONITOR_ANS_VALIDATE_RECONNECT, m); + + xfree(resilient_id); + xfree(client_version_string); + xfree(req_mac); + xfree(my); + xfree(peer); + return(0); +} + +/* Receives the new connection fd from the privileged process */ +static int answer_pass_connection(int sock, Buffer *m) +{ + u_char *resilient_id; + debug3("%s: about to deserialize", __func__); + + packet_set_connection(passed_fd, passed_fd); + resilient_id = buffer_get_string(m, NULL); + + mm_get_keystate(m, pmonitor->m_pkex); + (*pmonitor->m_pkex)->kex[KEX_DH_GRP1_SHA1] = kexdh_server; + (*pmonitor->m_pkex)->kex[KEX_DH_GRP14_SHA1] = kexdh_server; + (*pmonitor->m_pkex)->kex[KEX_DH_GEX_SHA1] = kexgex_server; + (*pmonitor->m_pkex)->server = 1; + (*pmonitor->m_pkex)->load_host_key=&get_hostkey_by_type; + (*pmonitor->m_pkex)->host_key_index=&get_hostkey_index; + debug3("%s: deserialized", __func__); + + monitor_apply_keystate(NULL, NULL); + debug3("%s: applied", __func__); + + packet_synchronize(); + + buffer_clear(m); + buffer_put_int(m, 1); + mm_request_send(sock, MONITOR_ANS_PASS_CONNECTION, m); + + xfree(resilient_id); + connection_closed = 0; + return(0); +} + +static int +process_resilient(fd_set *readset) +{ + int sock = PRIVSEP(get_resilient_pipe()); + + if (connection_closed && !resilient) + return(1); + + if (connection_closed && !suspend_time.tv_sec) { + debug("Connection closed => suspend start."); + gettimeofday(&suspend_time, NULL); + } + + if (FD_ISSET(sock, readset)) { + debug3("%s: processing a request from resilient pipe", __func__); + monitor_read(sock, resilient_dispatch, NULL); + debug3("%s: processed", __func__); + } + + return(0); +} + /* * Processes input from the client and the program. Input data is stored * in buffers and processed later. @@ -347,8 +506,8 @@ if (FD_ISSET(connection_in, readset)) { len = read(connection_in, buf, sizeof(buf)); if (len == 0) { - verbose("Connection closed by %.100s", - get_remote_ipaddr()); + verbose("Connection closed by %.100s", + get_remote_ipaddr()); connection_closed = 1; if (compat20) return; @@ -404,6 +563,8 @@ u_int dlen; int len; + debug3("%s: entering", __func__); + /* Write buffered data to program stdin. */ if (!compat20 && fdin != -1 && FD_ISSET(fdin, writeset)) { data = buffer_ptr(&stdin_buffer); @@ -472,6 +633,8 @@ static void process_buffered_input_packets(void) { + debug3("%s: entering", __func__); + dispatch_run(DISPATCH_NONBLOCK, NULL, compat20 ? xxx_kex : NULL); } @@ -740,7 +903,8 @@ server_loop2(Authctxt *authctxt) { fd_set *readset = NULL, *writeset = NULL; - int rekeying = 0, max_fd, nalloc = 0; + int rekeying = 0, max_fd; + u_int nalloc = 0; debug("Entering interactive session for SSH2."); @@ -751,18 +915,19 @@ notify_setup(); - max_fd = MAX(connection_in, connection_out); - max_fd = MAX(max_fd, notify_pipe[0]); - server_init_dispatch(); for (;;) { + max_fd = MAX(connection_in, connection_out); + max_fd = MAX(max_fd, notify_pipe[0]); + process_buffered_input_packets(); rekeying = (xxx_kex != NULL && !xxx_kex->done); if (!rekeying && packet_not_very_much_data_to_write()) channel_output_poll(); + wait_until_can_do_something(&readset, &writeset, &max_fd, &nalloc, 0); @@ -775,9 +940,11 @@ kex_send_kexinit(xxx_kex); } } + process_input(readset); - if (connection_closed) - break; + + if (process_resilient(readset)) + break; process_output(writeset); } collect_children(); Index: openssh-4.2p1/packet.c =================================================================== --- openssh-4.2p1/packet.c (revision 14) +++ openssh-4.2p1/packet.c (revision 76) @@ -79,6 +79,8 @@ static int connection_in = -1; static int connection_out = -1; +int connection_closed = 0; /* Connection to server/client closed. */ + /* Protocol flags for the remote side. */ static u_int remote_protocol_flags = 0; @@ -91,6 +93,9 @@ /* Buffer for raw input data from the socket. */ Buffer input; +/* Packet length in raw input data buffer. */ +static u_int received_packet_length = 0; + /* Buffer for raw output data going to the socket. */ Buffer output; @@ -124,12 +129,14 @@ /* Session key information for Encryption and MAC */ Newkeys *newkeys[MODE_MAX]; -static struct packet_state { - u_int32_t seqnr; - u_int32_t packets; - u_int64_t blocks; -} p_read, p_send; +struct packet_state p_read, p_send; + +static CipherContext old_receive_context; +static CipherContext old_send_context; +Newkeys *oldkeys[MODE_MAX]; +static struct packet_state old_p_read, old_p_send; + static u_int64_t max_blocks_in, max_blocks_out; static u_int32_t rekey_limit; @@ -140,13 +147,42 @@ /* roundup current message to extra_pad bytes */ static u_char extra_pad = 0; +/* resilient flush going on? */ +static int resilient_flush = 0; + struct packet { TAILQ_ENTRY(packet) next; u_char type; Buffer payload; }; TAILQ_HEAD(, packet) outgoing; +struct resilient_packet { + TAILQ_ENTRY(resilient_packet) next; + u_int32_t seqnr; + Buffer payload; +}; +TAILQ_HEAD(, resilient_packet) resilient_outgoing; +static int resilient_outgoing_bytes = 0; + +int resilient = 0; /* Connection in resilient mode + (applies both client and + server) */ + +int resilient_enqueued_bytes() +{ + return resilient_outgoing_bytes; +} + +void +packet_flush() +{ + received_packet_length = 0; + buffer_clear(&input); + buffer_clear(&outgoing_packet); + buffer_clear(&output); +} + /* * Sets the descriptors used for communication. Disables encryption until * packet_set_encryption_key is called. @@ -158,6 +194,32 @@ if (none == NULL) fatal("packet_set_connection: cannot load cipher 'none'"); + if (connection_closed) { + debug("Closing the old TCP connection, storing the old crypto state, disabling crypto (old fd = %d, new fd %d).", + connection_in, fd_out); + if (fd_in != fd_out) { + dup2(fd_out, connection_out); + close(fd_out); + } + dup2(fd_in, connection_in); + close(fd_in); + memcpy(&old_send_context, &send_context, sizeof(CipherContext)); + memcpy(&old_receive_context, &receive_context, sizeof(CipherContext)); + memcpy(&old_p_send, &p_send, sizeof(struct packet_state)); + memcpy(&old_p_read, &p_read, sizeof(struct packet_state)); + memcpy(&old_receive_context, &receive_context, sizeof(CipherContext)); + oldkeys[MODE_IN] = newkeys[MODE_IN]; + oldkeys[MODE_OUT] = newkeys[MODE_OUT]; + + cipher_init(&send_context, none, (const u_char *)"", + 0, NULL, 0, CIPHER_ENCRYPT); + cipher_init(&receive_context, none, (const u_char *)"", + 0, NULL, 0, CIPHER_DECRYPT); + newkeys[MODE_IN] = newkeys[MODE_OUT] = NULL; + packet_flush(); + return; + } + connection_in = fd_in; connection_out = fd_out; cipher_init(&send_context, none, (const u_char *)"", @@ -172,6 +234,8 @@ buffer_init(&outgoing_packet); buffer_init(&incoming_packet); TAILQ_INIT(&outgoing); + TAILQ_INIT(&resilient_outgoing); + resilient_outgoing_bytes = 0; } } @@ -355,7 +419,9 @@ if (!initialized) return; initialized = 0; - if (connection_in == connection_out) { + debug3("%s: enter", __func__); + if (connection_in == connection_out) { + debug3("%s: shutdown", __func__); shutdown(connection_out, SHUT_RDWR); close(connection_out); } else { @@ -696,6 +762,7 @@ Mac *mac = NULL; Comp *comp = NULL; int block_size; + int rekeying = (xxx_kex != NULL && !xxx_kex->done); if (newkeys[MODE_OUT] != NULL) { enc = &newkeys[MODE_OUT]->enc; @@ -712,7 +779,7 @@ buffer_dump(&outgoing_packet); #endif - if (comp && comp->enabled) { + if (!resilient_flush && comp && comp->enabled) { len = buffer_len(&outgoing_packet); /* skip header, compress only payload */ buffer_consume(&outgoing_packet, 5); @@ -729,6 +796,22 @@ /* sizeof (packet_len + pad_len + payload) */ len = buffer_len(&outgoing_packet); + /* Queues a packet; received ACK remove the queued packets. Received + synchronize right after a NEWKEYS flushes the buffer from packet N + to end. */ + if (!resilient_flush && !rekeying && resilient && type != SSH2_MSG_ACK) { + struct resilient_packet *p; + + debug3("enqueuing (plain but compressed payload) packet due to resiliency: %u, type: %d", + p_send.seqnr, type); + p = xmalloc(sizeof(*p)); + buffer_init(&p->payload); + p->seqnr = p_send.seqnr; + buffer_append(&p->payload, buffer_ptr(&outgoing_packet), len); + TAILQ_INSERT_TAIL(&resilient_outgoing, p, next); + resilient_outgoing_bytes += len; + } + /* * calc size of padding, alloc space, get random data, * minimum padding is 4 bytes @@ -770,7 +853,7 @@ macbuf = mac_compute(mac, p_send.seqnr, buffer_ptr(&outgoing_packet), buffer_len(&outgoing_packet)); - DBG(debug("done calc MAC out #%d", p_send.seqnr)); + DBG(debug("done calc MAC out #%d", p_send.seqnr)); } /* encrypt packet and append to output buffer. */ cp = buffer_append_space(&output, buffer_len(&outgoing_packet)); @@ -783,6 +866,7 @@ fprintf(stderr, "encrypted: "); buffer_dump(&output); #endif + /* increment sequence number for outgoing packets */ if (++p_send.seqnr == 0) logit("outgoing seqnr wraps around"); @@ -810,8 +894,8 @@ /* during rekeying we can only send key exchange messages */ if (rekeying) { - if (!((type >= SSH2_MSG_TRANSPORT_MIN) && - (type <= SSH2_MSG_TRANSPORT_MAX))) { + if (!(type >= SSH2_MSG_TRANSPORT_MIN && + type <= SSH2_MSG_TRANSPORT_MAX)) { debug("enqueue packet: %u", type); p = xmalloc(sizeof(*p)); p->type = type; @@ -905,7 +989,8 @@ /* Read data from the socket. */ len = read(connection_in, buf, sizeof(buf)); if (len == 0) { - logit("Connection closed by %.200s", get_remote_ipaddr()); + logit("Connection (fd = %d) closed by %.200s", connection_in, + get_remote_ipaddr()); cleanup_exit(255); } if (len < 0) @@ -919,7 +1004,8 @@ int packet_read(void) { - return packet_read_seqnr(NULL); + u_int32_t seqnr; + return packet_read_seqnr(&seqnr); } /* @@ -932,10 +1018,15 @@ { int type; - type = packet_read(); - if (type != expected_type) - packet_disconnect("Protocol error: expected packet type %d, got %d", - expected_type, type); + while (1) { + type = packet_read(); + if (type == expected_type) + break; + + if (type != SSH2_MSG_ACK) + packet_disconnect("Protocol error: expected packet type %d, got %d", + expected_type, type); + } } /* Checks if a full packet is available in the data received so far via @@ -1017,7 +1108,7 @@ buffer_uncompress(&incoming_packet, &compression_buffer); buffer_clear(&incoming_packet); buffer_append(&incoming_packet, buffer_ptr(&compression_buffer), - buffer_len(&compression_buffer)); + buffer_len(&compression_buffer)); } type = buffer_get_char(&incoming_packet); if (type < SSH_MSG_MIN || type > SSH_MSG_MAX) @@ -1028,7 +1119,6 @@ static int packet_read_poll2(u_int32_t *seqnr_p) { - static u_int packet_length = 0; u_int padlen, need; u_char *macbuf, *cp, type; u_int maclen, block_size; @@ -1041,10 +1131,11 @@ mac = &newkeys[MODE_IN]->mac; comp = &newkeys[MODE_IN]->comp; } + maclen = mac && mac->enabled ? mac->mac_len : 0; block_size = enc ? enc->block_size : 8; - if (packet_length == 0) { + if (received_packet_length == 0) { /* * check if input size is less than the cipher block size, * decrypt first block and extract length of incoming packet @@ -1056,18 +1147,18 @@ cipher_crypt(&receive_context, cp, buffer_ptr(&input), block_size); cp = buffer_ptr(&incoming_packet); - packet_length = GET_32BIT(cp); - if (packet_length < 1 + 4 || packet_length > 256 * 1024) { + received_packet_length = GET_32BIT(cp); + if (received_packet_length < 1 + 4 || received_packet_length > 256 * 1024) { #ifdef PACKET_DEBUG buffer_dump(&incoming_packet); #endif - packet_disconnect("Bad packet length %u.", packet_length); + packet_disconnect("Bad packet length %u.", received_packet_length); } - DBG(debug("input: packet len %u", packet_length+4)); + DBG(debug("input: packet len %u", received_packet_length+4)); buffer_consume(&input, block_size); } /* we have a partial packet of block_size bytes */ - need = 4 + packet_length - block_size; + need = 4 + received_packet_length - block_size; DBG(debug("partial packet %d, need %d, maclen %d", block_size, need, maclen)); if (need % block_size != 0) @@ -1094,6 +1185,7 @@ macbuf = mac_compute(mac, p_read.seqnr, buffer_ptr(&incoming_packet), buffer_len(&incoming_packet)); + DBG(debug("done calc MAC in #%d", p_read.seqnr)); if (memcmp(macbuf, buffer_ptr(&input), mac->mac_len) != 0) packet_disconnect("Corrupted MAC on input."); DBG(debug("MAC #%d ok", p_read.seqnr)); @@ -1106,7 +1198,7 @@ if (++p_read.packets == 0) if (!(datafellows & SSH_BUG_NOREKEY)) fatal("XXX too many packets with same key"); - p_read.blocks += (packet_length + 4) / block_size; + p_read.blocks += (received_packet_length + 4) / block_size; /* get padlen */ cp = buffer_ptr(&incoming_packet); @@ -1145,7 +1237,7 @@ buffer_dump(&incoming_packet); #endif /* reset for next packet */ - packet_length = 0; + received_packet_length = 0; return type; } @@ -1155,6 +1247,8 @@ u_int reason, seqnr; u_char type; char *msg; + struct resilient_packet *p; + int rekeying = (xxx_kex != NULL && !xxx_kex->done); for (;;) { if (compat20) { @@ -1185,7 +1279,32 @@ debug("Received SSH2_MSG_UNIMPLEMENTED for %u", seqnr); break; + case SSH2_MSG_ACK: + seqnr = packet_get_int(); + debug("Received SSH2_MSG_ACK for %u", seqnr); + + /* ACKs should come in order, i.e., this is O(1) + operation. */ + while((p = TAILQ_FIRST(&resilient_outgoing)) && + p->seqnr < seqnr) { + resilient_outgoing_bytes -= buffer_len(&p->payload); + TAILQ_REMOVE(&resilient_outgoing, p, next); + buffer_free(&p->payload); + xfree(p); + } + + break; default: + /* NONE, IGNORE, DEBUG, DISCONNECT nor + UNIMPLEMENTed are not ACKed. Neither are the + packets sent during rekeying or initial + keyexchange. */ + if (!rekeying && resilient && type != SSH_MSG_NONE && type != SSH2_MSG_SYNCHRONIZE) { + packet_start(SSH2_MSG_ACK); + packet_put_int(*seqnr_p); + packet_send(); + debug3("ACK sent for %u.", *seqnr_p); + } return type; break; } @@ -1577,3 +1696,50 @@ { after_authentication = 1; } + +/* Removes the received packets and re-sends the rest. */ +void +packet_synchronize() +{ + struct resilient_packet *p; + Comp *comp = newkeys[MODE_OUT] ? &newkeys[MODE_OUT]->comp : NULL; + int compression_enabled = comp && comp->enabled; + Buffer b; + + debug3("%s: flushing from %d ->", __func__, p_send.seqnr); + + memcpy(&b, &outgoing_packet, sizeof(Buffer)); + + /* ACKs should come in order: this is O(1) operation. */ + while((p = TAILQ_FIRST(&resilient_outgoing)) && + p->seqnr < p_send.seqnr) { + resilient_outgoing_bytes -= buffer_len(&p->payload); + TAILQ_REMOVE(&resilient_outgoing, p, next); + buffer_free(&p->payload); + xfree(p); + } + + /* Disable while flushing (compressed) queued packets */ + if (compression_enabled) + comp->enabled = 0; + + /* Disable enqueuing due to connection break first */ + resilient_flush = 1; + while ((p = TAILQ_FIRST(&resilient_outgoing))) { + debug("resending packet: %u", p->seqnr); + memcpy(&outgoing_packet, &p->payload, sizeof(Buffer)); + resilient_outgoing_bytes -= buffer_len(&p->payload); + packet_send2_wrapped(); + + TAILQ_REMOVE(&resilient_outgoing, p, next); + buffer_free(&p->payload); + xfree(p); + } + /* Enable enqueuing due to connection break again, if needed. */ + resilient_flush = 0; + + if (compression_enabled) + comp->enabled = compression_enabled; + + memcpy(&outgoing_packet, &b, sizeof(Buffer)); +} Index: openssh-4.2p1/packet.h =================================================================== --- openssh-4.2p1/packet.h (revision 14) +++ openssh-4.2p1/packet.h (revision 76) @@ -49,6 +49,7 @@ void packet_process_incoming(const char *buf, u_int len); int packet_read_seqnr(u_int32_t *seqnr_p); int packet_read_poll_seqnr(u_int32_t *seqnr_p); +void resilient_input_ack(int type, u_int32_t seq, void *ctxt); u_int packet_get_char(void); u_int packet_get_int(void); @@ -101,4 +102,18 @@ int packet_need_rekeying(void); void packet_set_rekey_limit(u_int32_t); +struct packet_state { + u_int32_t seqnr; + u_int32_t packets; + u_int64_t blocks; +}; + +extern int connection_closed; +extern struct packet_state p_read; +extern struct packet_state p_send; +extern int resilient; + +void packet_restore_crypto(int); +void packet_synchronize(); + #endif /* PACKET_H */ Index: openssh-4.2p1/monitor_wrap.c =================================================================== --- openssh-4.2p1/monitor_wrap.c (revision 14) +++ openssh-4.2p1/monitor_wrap.c (revision 76) @@ -58,18 +58,13 @@ #include "auth.h" #include "channels.h" #include "session.h" - +#include "resilient.h" #ifdef GSSAPI #include "ssh-gss.h" #endif /* Imports */ -extern int compat20; -extern Newkeys *newkeys[]; -extern z_stream incoming_stream; -extern z_stream outgoing_stream; extern struct monitor *pmonitor; -extern Buffer input, output; extern Buffer loginmsg; extern ServerOptions options; extern Buffer loginmsg; @@ -84,13 +79,19 @@ return (pmonitor && pmonitor->m_pid > 0); } +int +mm_get_resilient_pipe() +{ + return pmonitor->m_recvfd; +} + void mm_request_send(int sock, enum monitor_reqtype type, Buffer *m) { u_int mlen = buffer_len(m); u_char buf[5]; - debug3("%s entering: type %d", __func__, type); + debug("%s entering: type %d, sock %d", __func__, type, sock); PUT_32BIT(buf, mlen + 1); buf[4] = (u_char) type; /* 1st byte of payload is mesg-type */ @@ -98,6 +99,8 @@ fatal("%s: write: %s", __func__, strerror(errno)); if (atomicio(vwrite, sock, buffer_ptr(m), mlen) != mlen) fatal("%s: write: %s", __func__, strerror(errno)); + + debug("%s leaving, wrote %d bytes", __func__, mlen + 4 + 1); } void @@ -106,11 +109,14 @@ u_char buf[4]; u_int msg_len; - debug3("%s entering", __func__); + debug("%s entering", __func__); - if (atomicio(read, sock, buf, sizeof(buf)) != sizeof(buf)) { - if (errno == EPIPE) + passed_fd = -1; + if (atomicio(read_fd, sock, buf, sizeof(buf)) != sizeof(buf)) { + if (errno == EPIPE) { + debug("%s: read: fd = %u, %s", __func__, sock, strerror(errno)); cleanup_exit(255); + } fatal("%s: read: %s", __func__, strerror(errno)); } msg_len = GET_32BIT(buf); @@ -127,7 +133,7 @@ { u_char rtype; - debug3("%s entering: type %d", __func__, type); + debug("%s entering: type %d", __func__, type); mm_request_receive(sock, m); rtype = buffer_get_char(m); @@ -170,6 +176,52 @@ return (dh_new_group(g, p)); } +/* Read any input data from the privileged to unprivileged process. */ +int mm_process_resilient(int sock, fd_set *readset) +{ + debug3("%s entering", __func__); + + if (FD_ISSET(sock, readset)) { + Buffer m; + buffer_init(&m); + mm_request_receive(sock, &m); + buffer_free(&m); + } + return(0); +} + +void mm_resilient_register(Kex *kex) +{ + Buffer m; + + debug3("%s entering", __func__); + + buffer_init(&m); + buffer_put_string(&m, kex->resilient_id, kex->session_id_len); + mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_NIL, &m); + buffer_free(&m); +} + +int mm_validate_reconnect(Buffer *m, Buffer *n) +{ + debug3("%s: sending %d bytes", __func__, buffer_len(m)); + + mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_VALIDATE_RECONNECT, m); + mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_VALIDATE_RECONNECT, n); + + return(0); +} + +int mm_pass_connection(int sock, Buffer *m, Buffer *n) +{ + debug3("%s entering", __func__); + + mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_PASS_CONNECTION, m); + mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_PASS_CONNECTION, n); + + return(0); +} + int mm_key_sign(Key *key, u_char **sigp, u_int *lenp, u_char *data, u_int datalen) { @@ -414,245 +466,22 @@ return (verified); } -/* Export key state after authentication */ -Newkeys * -mm_newkeys_from_blob(u_char *blob, int blen) -{ - Buffer b; - u_int len; - Newkeys *newkey = NULL; - Enc *enc; - Mac *mac; - Comp *comp; - - debug3("%s: %p(%d)", __func__, blob, blen); -#ifdef DEBUG_PK - dump_base64(stderr, blob, blen); -#endif - buffer_init(&b); - buffer_append(&b, blob, blen); - - newkey = xmalloc(sizeof(*newkey)); - enc = &newkey->enc; - mac = &newkey->mac; - comp = &newkey->comp; - - /* Enc structure */ - enc->name = buffer_get_string(&b, NULL); - buffer_get(&b, &enc->cipher, sizeof(enc->cipher)); - enc->enabled = buffer_get_int(&b); - enc->block_size = buffer_get_int(&b); - enc->key = buffer_get_string(&b, &enc->key_len); - enc->iv = buffer_get_string(&b, &len); - if (len != enc->block_size) - fatal("%s: bad ivlen: expected %u != %u", __func__, - enc->block_size, len); - - if (enc->name == NULL || cipher_by_name(enc->name) != enc->cipher) - fatal("%s: bad cipher name %s or pointer %p", __func__, - enc->name, enc->cipher); - - /* Mac structure */ - mac->name = buffer_get_string(&b, NULL); - if (mac->name == NULL || mac_init(mac, mac->name) == -1) - fatal("%s: can not init mac %s", __func__, mac->name); - mac->enabled = buffer_get_int(&b); - mac->key = buffer_get_string(&b, &len); - if (len > mac->key_len) - fatal("%s: bad mac key length: %u > %d", __func__, len, - mac->key_len); - mac->key_len = len; - - /* Comp structure */ - comp->type = buffer_get_int(&b); - comp->enabled = buffer_get_int(&b); - comp->name = buffer_get_string(&b, NULL); - - len = buffer_len(&b); - if (len != 0) - error("newkeys_from_blob: remaining bytes in blob %u", len); - buffer_free(&b); - return (newkey); -} - int -mm_newkeys_to_blob(int mode, u_char **blobp, u_int *lenp) -{ - Buffer b; - int len; - Enc *enc; - Mac *mac; - Comp *comp; - Newkeys *newkey = newkeys[mode]; - - debug3("%s: converting %p", __func__, newkey); - - if (newkey == NULL) { - error("%s: newkey == NULL", __func__); - return 0; - } - enc = &newkey->enc; - mac = &newkey->mac; - comp = &newkey->comp; - - buffer_init(&b); - /* Enc structure */ - buffer_put_cstring(&b, enc->name); - /* The cipher struct is constant and shared, you export pointer */ - buffer_append(&b, &enc->cipher, sizeof(enc->cipher)); - buffer_put_int(&b, enc->enabled); - buffer_put_int(&b, enc->block_size); - buffer_put_string(&b, enc->key, enc->key_len); - packet_get_keyiv(mode, enc->iv, enc->block_size); - buffer_put_string(&b, enc->iv, enc->block_size); - - /* Mac structure */ - buffer_put_cstring(&b, mac->name); - buffer_put_int(&b, mac->enabled); - buffer_put_string(&b, mac->key, mac->key_len); - - /* Comp structure */ - buffer_put_int(&b, comp->type); - buffer_put_int(&b, comp->enabled); - buffer_put_cstring(&b, comp->name); - - len = buffer_len(&b); - if (lenp != NULL) - *lenp = len; - if (blobp != NULL) { - *blobp = xmalloc(len); - memcpy(*blobp, buffer_ptr(&b), len); - } - memset(buffer_ptr(&b), 0, len); - buffer_free(&b); - return len; -} - -static void -mm_send_kex(Buffer *m, Kex *kex) -{ - buffer_put_string(m, kex->session_id, kex->session_id_len); - buffer_put_int(m, kex->we_need); - buffer_put_int(m, kex->hostkey_type); - buffer_put_int(m, kex->kex_type); - buffer_put_string(m, buffer_ptr(&kex->my), buffer_len(&kex->my)); - buffer_put_string(m, buffer_ptr(&kex->peer), buffer_len(&kex->peer)); - buffer_put_int(m, kex->flags); - buffer_put_cstring(m, kex->client_version_string); - buffer_put_cstring(m, kex->server_version_string); -} - -void -mm_send_keystate(struct monitor *monitor) -{ - Buffer m; - u_char *blob, *p; - u_int bloblen, plen; - u_int32_t seqnr, packets; - u_int64_t blocks; - - buffer_init(&m); - - if (!compat20) { - u_char iv[24]; - u_char *key; - u_int ivlen, keylen; - - buffer_put_int(&m, packet_get_protocol_flags()); - - buffer_put_int(&m, packet_get_ssh1_cipher()); - - debug3("%s: Sending ssh1 KEY+IV", __func__); - keylen = packet_get_encryption_key(NULL); - key = xmalloc(keylen+1); /* add 1 if keylen == 0 */ - keylen = packet_get_encryption_key(key); - buffer_put_string(&m, key, keylen); - memset(key, 0, keylen); - xfree(key); - - ivlen = packet_get_keyiv_len(MODE_OUT); - packet_get_keyiv(MODE_OUT, iv, ivlen); - buffer_put_string(&m, iv, ivlen); - ivlen = packet_get_keyiv_len(MODE_OUT); - packet_get_keyiv(MODE_IN, iv, ivlen); - buffer_put_string(&m, iv, ivlen); - goto skip; - } else { - /* Kex for rekeying */ - mm_send_kex(&m, *monitor->m_pkex); - } - - debug3("%s: Sending new keys: %p %p", - __func__, newkeys[MODE_OUT], newkeys[MODE_IN]); - - /* Keys from Kex */ - if (!mm_newkeys_to_blob(MODE_OUT, &blob, &bloblen)) - fatal("%s: conversion of newkeys failed", __func__); - - buffer_put_string(&m, blob, bloblen); - xfree(blob); - - if (!mm_newkeys_to_blob(MODE_IN, &blob, &bloblen)) - fatal("%s: conversion of newkeys failed", __func__); - - buffer_put_string(&m, blob, bloblen); - xfree(blob); - - packet_get_state(MODE_OUT, &seqnr, &blocks, &packets); - buffer_put_int(&m, seqnr); - buffer_put_int64(&m, blocks); - buffer_put_int(&m, packets); - packet_get_state(MODE_IN, &seqnr, &blocks, &packets); - buffer_put_int(&m, seqnr); - buffer_put_int64(&m, blocks); - buffer_put_int(&m, packets); - - debug3("%s: New keys have been sent", __func__); - skip: - /* More key context */ - plen = packet_get_keycontext(MODE_OUT, NULL); - p = xmalloc(plen+1); - packet_get_keycontext(MODE_OUT, p); - buffer_put_string(&m, p, plen); - xfree(p); - - plen = packet_get_keycontext(MODE_IN, NULL); - p = xmalloc(plen+1); - packet_get_keycontext(MODE_IN, p); - buffer_put_string(&m, p, plen); - xfree(p); - - /* Compression state */ - debug3("%s: Sending compression state", __func__); - buffer_put_string(&m, &outgoing_stream, sizeof(outgoing_stream)); - buffer_put_string(&m, &incoming_stream, sizeof(incoming_stream)); - - /* Network I/O buffers */ - buffer_put_string(&m, buffer_ptr(&input), buffer_len(&input)); - buffer_put_string(&m, buffer_ptr(&output), buffer_len(&output)); - - mm_request_send(monitor->m_recvfd, MONITOR_REQ_KEYEXPORT, &m); - debug3("%s: Finished sending state", __func__); - - buffer_free(&m); -} - -int mm_pty_allocate(int *ptyfd, int *ttyfd, char *namebuf, int namebuflen) { Buffer m; char *p, *msg; int success = 0; + debug("%s: entering", __func__); buffer_init(&m); mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_PTY, &m); - debug3("%s: waiting for MONITOR_ANS_PTY", __func__); mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_PTY, &m); success = buffer_get_int(&m); if (success == 0) { - debug3("%s: pty alloc failed", __func__); + debug("%s: pty alloc failed", __func__); buffer_free(&m); return (0); } @@ -669,6 +498,9 @@ *ptyfd = mm_receive_fd(pmonitor->m_recvfd); *ttyfd = mm_receive_fd(pmonitor->m_recvfd); + debug("%s: leaving.", __func__); + + /* Success */ return (1); } Index: openssh-4.2p1/readconf.c =================================================================== --- openssh-4.2p1/readconf.c (revision 14) +++ openssh-4.2p1/readconf.c (revision 76) @@ -107,7 +107,7 @@ oAddressFamily, oGssAuthentication, oGssDelegateCreds, oServerAliveInterval, oServerAliveCountMax, oIdentitiesOnly, oSendEnv, oControlPath, oControlMaster, oHashKnownHosts, - oDeprecated, oUnsupported + oResilientConnectionAttempts, oDeprecated, oUnsupported } OpCodes; /* Textual representations of the tokens. */ @@ -167,6 +167,7 @@ { "globalknownhostsfile2", oGlobalKnownHostsFile2 }, { "userknownhostsfile2", oUserKnownHostsFile2 }, /* obsolete */ { "connectionattempts", oConnectionAttempts }, + { "resilientconnectionattempts", oResilientConnectionAttempts }, { "batchmode", oBatchMode }, { "checkhostip", oCheckHostIP }, { "stricthostkeychecking", oStrictHostKeyChecking }, @@ -576,12 +577,18 @@ fatal("%.200s line %d: Bad number.", filename, linenum); if (*activep && *intptr == -1) *intptr = value; + debug("value = %d", value); break; case oConnectionAttempts: intptr = &options->connection_attempts; goto parse_int; + case oResilientConnectionAttempts: + intptr = &options->resilient_connection_attempts; + debug("reconnect attempts: "); + goto parse_int; + case oCipher: intptr = &options->cipher; arg = strdelim(&s); @@ -931,6 +938,7 @@ options->port = -1; options->address_family = -1; options->connection_attempts = -1; + options->resilient_connection_attempts = -1; options->connection_timeout = -1; options->number_of_password_prompts = -1; options->cipher = -1; @@ -1026,6 +1034,8 @@ options->address_family = AF_UNSPEC; if (options->connection_attempts == -1) options->connection_attempts = 1; + if (options->resilient_connection_attempts == -1) + options->resilient_connection_attempts = 0; if (options->number_of_password_prompts == -1) options->number_of_password_prompts = 3; /* Selected in ssh_login(). */ Index: openssh-4.2p1/monitor_wrap.h =================================================================== --- openssh-4.2p1/monitor_wrap.h (revision 14) +++ openssh-4.2p1/monitor_wrap.h (revision 76) @@ -28,6 +28,8 @@ #ifndef _MM_WRAP_H_ #define _MM_WRAP_H_ #include "key.h" +#include "kex.h" +#include "resilient.h" #include "buffer.h" extern int use_privsep; @@ -85,18 +87,16 @@ int mm_pty_allocate(int *, int *, char *, int); void mm_session_pty_cleanup2(struct Session *); +int mm_validate_reconnect(Buffer *, Buffer *); +int mm_process_resilient(int, fd_set *); +void mm_resilient_register(Kex *); +int mm_pass_connection(int, Buffer *, Buffer *); +int mm_get_resilient_pipe(); + /* SSHv1 interfaces */ void mm_ssh1_session_id(u_char *); int mm_ssh1_session_key(BIGNUM *); -/* Key export functions */ -struct Newkeys *mm_newkeys_from_blob(u_char *, int); -int mm_newkeys_to_blob(int, u_char **, u_int *); - -void monitor_apply_keystate(struct monitor *); -void mm_get_keystate(struct monitor *); -void mm_send_keystate(struct monitor*); - /* bsdauth */ int mm_bsdauth_query(void *, char **, char **, u_int *, char ***, u_int **); int mm_bsdauth_respond(void *, u_int, char **); @@ -109,6 +109,8 @@ void *mm_zalloc(struct mm_master *, u_int, u_int); void mm_zfree(struct mm_master *, void *); + +typedef void init_compression_fn(struct mm_master *); void mm_init_compression(struct mm_master *); #endif /* _MM_H_ */ Index: openssh-4.2p1/readconf.h =================================================================== --- openssh-4.2p1/readconf.h (revision 14) +++ openssh-4.2p1/readconf.h (revision 76) @@ -65,6 +65,8 @@ * giving up */ int connection_timeout; /* Max time (seconds) before * aborting connection attempt */ + int resilient_connection_attempts; /* Max attempts (seconds) before + * giving up after disconnection */ int number_of_password_prompts; /* Max number of password * prompts. */ int cipher; /* Cipher to use. */ Index: openssh-4.2p1/channels.c =================================================================== --- openssh-4.2p1/channels.c (revision 14) +++ openssh-4.2p1/channels.c (revision 76) @@ -719,6 +719,10 @@ /* check buffer limits */ limit = MIN(limit, (BUFFER_MAX_LEN - BUFFER_MAX_CHUNK - CHAN_RBUF)); + if (BUFFER_MAX_LEN - resilient_enqueued_bytes() < BUFFER_MAX_CHUNK) { + debug3("resilient buffer full!!!"); + limit = 0; + } if (c->istate == CHAN_INPUT_OPEN && limit > 0 && Index: openssh-4.2p1/kexdhc.c =================================================================== --- openssh-4.2p1/kexdhc.c (revision 14) +++ openssh-4.2p1/kexdhc.c (revision 76) @@ -124,23 +124,45 @@ dh_server_pub, shared_secret ); + + /* save session id */ + if (kex->session_id == NULL) { + kex->session_id_len = 20; + kex->session_id = xmalloc(kex->session_id_len); + memcpy(kex->session_id, hash, kex->session_id_len); + } + + /* calc resilient H */ + if (resilient) { + hash = kex_dh_hash( + 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, + dh->pub_key, + dh_server_pub, + NULL + ); + + /* save resilient id := resilient H */ + /* XXX hashlen equals to session id len */ + if (kex->resilient_id == NULL) { + kex->resilient_id = xmalloc(kex->session_id_len); + memcpy(kex->resilient_id, hash, kex->session_id_len); + } + } + xfree(server_host_key_blob); BN_clear_free(dh_server_pub); DH_free(dh); - if (key_verify(server_host_key, signature, slen, hash, 20) != 1) + if (key_verify(server_host_key, signature, slen, kex->session_id, 20) != 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 = 20; - kex->session_id = xmalloc(kex->session_id_len); - memcpy(kex->session_id, hash, kex->session_id_len); - } - - kex_derive_keys(kex, hash, shared_secret); + kex_derive_keys(kex, kex->session_id, shared_secret); BN_clear_free(shared_secret); - kex_finish(kex); + kex_finish(kex, NULL); } Index: openssh-4.2p1/kexdhs.c =================================================================== --- openssh-4.2p1/kexdhs.c (revision 14) +++ openssh-4.2p1/kexdhs.c (revision 76) @@ -43,6 +43,7 @@ u_char *kbuf, *hash, *signature = NULL, *server_host_key_blob = NULL; u_int sbloblen, klen, kout; u_int slen; + kex_finished_fn *fn = NULL; /* generate server DH public key */ switch (kex->kex_type) { @@ -113,8 +114,7 @@ dh->pub_key, shared_secret ); - BN_clear_free(dh_client_pub); - + /* save session id := H */ /* XXX hashlen depends on KEX */ if (kex->session_id == NULL) { @@ -123,9 +123,34 @@ memcpy(kex->session_id, hash, kex->session_id_len); } + /* calc resilient H */ + if (resilient) { + hash = kex_dh_hash( + 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, + dh_client_pub, + dh->pub_key, + NULL + ); + + /* save resilient id := resilient H */ + /* XXX hashlen equals to session id len */ + if (kex->resilient_id == NULL) { + kex->resilient_id = xmalloc(kex->session_id_len); + memcpy(kex->resilient_id, hash, kex->session_id_len); + } + + fn = PRIVSEP(resilient_register); + } + + BN_clear_free(dh_client_pub); + /* sign H */ /* XXX hashlen depends on KEX */ - PRIVSEP(key_sign(server_host_key, &signature, &slen, hash, 20)); + PRIVSEP(key_sign(server_host_key, &signature, &slen, kex->session_id, 20)); /* destroy_sensitive_data(); */ @@ -141,7 +166,7 @@ /* have keys, free DH */ DH_free(dh); - kex_derive_keys(kex, hash, shared_secret); + kex_derive_keys(kex, kex->session_id, shared_secret); BN_clear_free(shared_secret); - kex_finish(kex); + kex_finish(kex, fn); } Index: openssh-4.2p1/kex.c =================================================================== --- openssh-4.2p1/kex.c (revision 14) +++ openssh-4.2p1/kex.c (revision 76) @@ -41,15 +41,17 @@ #include "match.h" #include "dispatch.h" #include "monitor.h" +#include "myproposal.h" -#define KEX_COOKIE_LEN 16 +/* for rekeying XXX fixme */ +Kex *xxx_kex; /* prototype */ static void kex_kexinit_finish(Kex *); static void kex_choose_conf(Kex *); /* put algorithm proposal into buffer */ -static void +void kex_prop2buf(Buffer *b, char *proposal[PROPOSAL_MAX]) { u_int i; @@ -114,7 +116,7 @@ error("Hm, kex protocol error: type %d seq %u", type, seq); } -static void +void kex_reset_dispatch(void) { dispatch_range(SSH2_MSG_TRANSPORT_MIN, @@ -123,10 +125,9 @@ } void -kex_finish(Kex *kex) +kex_finish(Kex *kex, kex_finished_fn *fn) { kex_reset_dispatch(); - packet_start(SSH2_MSG_NEWKEYS); packet_send(); /* packet_write_wait(); */ @@ -134,7 +135,7 @@ debug("expecting SSH2_MSG_NEWKEYS"); packet_read_expect(SSH2_MSG_NEWKEYS); - packet_check_eom(); + packet_check_eom(); debug("SSH2_MSG_NEWKEYS received"); kex->done = 1; @@ -143,6 +144,9 @@ kex->flags &= ~KEX_INIT_SENT; xfree(kex->name); kex->name = NULL; + + if (fn) + fn(kex); } void @@ -291,13 +295,15 @@ { k->name = match_list(client, server, NULL); if (k->name == NULL) - fatal("no kex alg"); + fatal("no kex alg, client: %s, server: %s", client, server); if (strcmp(k->name, KEX_DH1) == 0) { k->kex_type = KEX_DH_GRP1_SHA1; } else if (strcmp(k->name, KEX_DH14) == 0) { k->kex_type = KEX_DH_GRP14_SHA1; } else if (strcmp(k->name, KEX_DHGEX) == 0) { k->kex_type = KEX_DH_GEX_SHA1; + } else if (strcmp(k->name, KEX_RESILIENT_KEX_ONLY) == 0) { + k->kex_type = KEX_REUSE; } else fatal("bad kex alg %s", k->name); } @@ -376,6 +382,22 @@ newkeys->mac.name, newkeys->comp.name); } + + /* Determine the mode from the proposals to keep the function + identical in both client and server; options structure differs + in client and server. */ + char *resilient_support = match_list(my[PROPOSAL_KEX_ALGS], KEX_RESILIENT_KEX_ONLY, NULL); + if (resilient_support) { + xfree(resilient_support); + + resilient_support = match_list(peer[PROPOSAL_KEX_ALGS], KEX_RESILIENT_KEX_ONLY, NULL); + if (resilient_support) { + debug("resilient support enabled"); + xfree(resilient_support); + resilient = 1; + } + } + choose_kex(kex, cprop[PROPOSAL_KEX_ALGS], sprop[PROPOSAL_KEX_ALGS]); choose_hostkeyalg(kex, cprop[PROPOSAL_SERVER_HOST_KEY_ALGS], sprop[PROPOSAL_SERVER_HOST_KEY_ALGS]); Index: openssh-4.2p1/keystate.c =================================================================== --- openssh-4.2p1/keystate.c (revision 0) +++ openssh-4.2p1/keystate.c (revision 76) @@ -0,0 +1,465 @@ +/* + * Copyright 2002 Niels Provos + * Copyright 2002 Markus Friedl + * 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" +RCSID("$OpenBSD: monitor_wrap.c,v 1.40 2005/05/24 17:32:43 avsm Exp $"); + +#include +#include + +#include "packet.h" +#include "buffer.h" +#include "bufaux.h" +#include "kex.h" +#include "monitor_wrap.h" +#include "log.h" +#ifdef TARGET_OS_MAC /* XXX Broken krb5 headers on Mac */ +#undef TARGET_OS_MAC +#include "zlib.h" +#define TARGET_OS_MAC 1 +#else +#include "zlib.h" +#endif +#include "mac.h" +#include "xmalloc.h" + +/* State exported from the child */ +struct { + z_stream incoming; + z_stream outgoing; + u_char *keyin; + u_int keyinlen; + u_char *keyout; + u_int keyoutlen; + u_char *ivin; + u_int ivinlen; + u_char *ivout; + u_int ivoutlen; + u_char *ssh1key; + u_int ssh1keylen; + int ssh1cipher; + int ssh1protoflags; + u_char *input; + u_int ilen; + u_char *output; + u_int olen; + int resilient; +} child_state; + +/* Imports */ +extern int compat20; +extern Newkeys *newkeys[]; +extern Newkeys *current_keys[]; +extern z_stream incoming_stream; +extern z_stream outgoing_stream; +extern Buffer input, output; + +int +mm_newkeys_to_blob(int mode, u_char **blobp, u_int *lenp) +{ + Buffer b; + int len; + Enc *enc; + Mac *mac; + Comp *comp; + Newkeys *newkey = newkeys[mode]; + + debug3("%s: converting %p", __func__, newkey); + + if (newkey == NULL) { + error("%s: newkey == NULL", __func__); + return 0; + } + enc = &newkey->enc; + mac = &newkey->mac; + comp = &newkey->comp; + + buffer_init(&b); + /* Enc structure */ + buffer_put_cstring(&b, enc->name); + /* The cipher struct is constant and shared, you export pointer */ + buffer_append(&b, &enc->cipher,