diff options
Diffstat (limited to 'usr.bin/ssh/roaming_client.c')
-rw-r--r-- | usr.bin/ssh/roaming_client.c | 276 |
1 files changed, 276 insertions, 0 deletions
diff --git a/usr.bin/ssh/roaming_client.c b/usr.bin/ssh/roaming_client.c new file mode 100644 index 00000000000..b77dbd59b9a --- /dev/null +++ b/usr.bin/ssh/roaming_client.c @@ -0,0 +1,276 @@ +/* $OpenBSD: roaming_client.c,v 1.1 2009/10/24 11:22:37 andreas Exp $ */ +/* + * Copyright (c) 2004-2009 AppGate Network Security AB + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/queue.h> +#include <sys/types.h> +#include <sys/socket.h> + +#include <inttypes.h> +#include <signal.h> +#include <string.h> +#include <unistd.h> + +#include <openssl/crypto.h> +#include <openssl/sha.h> + +#include "xmalloc.h" +#include "buffer.h" +#include "channels.h" +#include "cipher.h" +#include "dispatch.h" +#include "clientloop.h" +#include "log.h" +#include "match.h" +#include "misc.h" +#include "packet.h" +#include "ssh.h" +#include "key.h" +#include "kex.h" +#include "readconf.h" +#include "roaming.h" +#include "ssh2.h" +#include "sshconnect.h" + +/* import */ +extern Options options; +extern char *host; +extern struct sockaddr_storage hostaddr; +extern int session_resumed; + +static u_int32_t roaming_id; +static u_int64_t cookie; +static u_int64_t lastseenchall; +static u_int64_t key1, key2, oldkey1, oldkey2; + +void +roaming_reply(int type, u_int32_t seq, void *ctxt) +{ + if (type == SSH2_MSG_REQUEST_FAILURE) { + logit("Server denied roaming"); + return; + } + verbose("Roaming enabled"); + roaming_id = packet_get_int(); + cookie = packet_get_int64(); + key1 = oldkey1 = packet_get_int64(); + key2 = oldkey2 = packet_get_int64(); + set_out_buffer_size(packet_get_int() + get_snd_buf_size()); + roaming_enabled = 1; +} + +void +request_roaming(void) +{ + packet_start(SSH2_MSG_GLOBAL_REQUEST); + packet_put_cstring(ROAMING_REQUEST); + packet_put_char(1); + packet_put_int(get_recv_buf_size()); + packet_send(); + client_register_global_confirm(roaming_reply, NULL); +} + +static void +roaming_auth_required(void) +{ + u_char digest[SHA_DIGEST_LENGTH]; + EVP_MD_CTX md; + Buffer b; + const EVP_MD *evp_md = EVP_sha1(); + u_int64_t chall, oldchall; + + chall = packet_get_int64(); + oldchall = packet_get_int64(); + if (oldchall != lastseenchall) { + key1 = oldkey1; + key2 = oldkey2; + } + lastseenchall = chall; + + buffer_init(&b); + buffer_put_int64(&b, cookie); + buffer_put_int64(&b, chall); + EVP_DigestInit(&md, evp_md); + EVP_DigestUpdate(&md, buffer_ptr(&b), buffer_len(&b)); + EVP_DigestFinal(&md, digest, NULL); + buffer_free(&b); + + packet_start(SSH2_MSG_KEX_ROAMING_AUTH); + packet_put_int64(key1 ^ get_recv_bytes()); + packet_put_raw(digest, sizeof(digest)); + packet_send(); + + oldkey1 = key1; + oldkey2 = key2; + calculate_new_key(&key1, cookie, chall); + calculate_new_key(&key2, cookie, chall); + + debug("Received %" PRIu64 " bytes", get_recv_bytes()); + debug("Sent roaming_auth packet"); +} + +int +resume_kex(void) +{ + /* + * This should not happen - if the client sends the kex method + * resume@appgate.com then the kex is done in roaming_resume(). + */ + return 1; +} + +static int +roaming_resume(void) +{ + u_int64_t recv_bytes; + char *str = NULL, *kexlist = NULL, *c; + int i, type; + int timeout_ms = options.connection_timeout * 1000; + u_int len; + u_int32_t rnd = 0; + + resume_in_progress = 1; + + /* Exchange banners */ + ssh_exchange_identification(timeout_ms); + packet_set_nonblocking(); + + /* Send a kexinit message with resume@appgate.com as only kex algo */ + packet_start(SSH2_MSG_KEXINIT); + for (i = 0; i < KEX_COOKIE_LEN; i++) { + if (i % 4 == 0) + rnd = arc4random(); + packet_put_char(rnd & 0xff); + rnd >>= 8; + } + packet_put_cstring(KEX_RESUME); + for (i = 1; i < PROPOSAL_MAX; i++) { + /* kex algorithm added so start with i=1 and not 0 */ + packet_put_cstring(""); /* Not used when we resume */ + } + packet_put_char(1); /* first kex_packet follows */ + packet_put_int(0); /* reserved */ + packet_send(); + + /* Assume that resume@appgate.com will be accepted */ + packet_start(SSH2_MSG_KEX_ROAMING_RESUME); + packet_put_int(roaming_id); + packet_send(); + + /* Read the server's kexinit and check for resume@appgate.com */ + if ((type = packet_read()) != SSH2_MSG_KEXINIT) { + debug("expected kexinit on resume, got %d", type); + goto fail; + } + for (i = 0; i < KEX_COOKIE_LEN; i++) + (void)packet_get_char(); + kexlist = packet_get_string(&len); + if (!kexlist + || (str = match_list(KEX_RESUME, kexlist, NULL)) == NULL) { + debug("server doesn't allow resume"); + goto fail; + } + xfree(str); + for (i = 1; i < PROPOSAL_MAX; i++) { + /* kex algorithm taken care of so start with i=1 and not 0 */ + xfree(packet_get_string(&len)); + } + i = packet_get_char(); /* first_kex_packet_follows */ + if (i && (c = strchr(kexlist, ','))) + *c = 0; + if (i && strcmp(kexlist, KEX_RESUME)) { + debug("server's kex guess (%s) was wrong, skipping", kexlist); + (void)packet_read(); /* Wrong guess - discard packet */ + } + + /* + * Read the ROAMING_AUTH_REQUIRED challenge from the server and + * send ROAMING_AUTH + */ + if ((type = packet_read()) != SSH2_MSG_KEX_ROAMING_AUTH_REQUIRED) { + debug("expected roaming_auth_required, got %d", type); + goto fail; + } + roaming_auth_required(); + + /* Read ROAMING_AUTH_OK from the server */ + if ((type = packet_read()) != SSH2_MSG_KEX_ROAMING_AUTH_OK) { + debug("expected roaming_auth_ok, got %d", type); + goto fail; + } + recv_bytes = packet_get_int64() ^ oldkey2; + debug("Peer received %" PRIu64 " bytes", recv_bytes); + resend_bytes(packet_get_connection_out(), &recv_bytes); + + resume_in_progress = 0; + + session_resumed = 1; /* Tell clientloop */ + + return 0; + +fail: + if (kexlist) + xfree(kexlist); + if (packet_get_connection_in() == packet_get_connection_out()) + close(packet_get_connection_in()); + else { + close(packet_get_connection_in()); + close(packet_get_connection_out()); + } + return 1; +} + +int +wait_for_roaming_reconnect(void) +{ + static int reenter_guard = 0; + int timeout_ms = options.connection_timeout * 1000; + int c; + + if (reenter_guard != 0) + fatal("Server refused resume, roaming timeout may be exceeded"); + reenter_guard = 1; + + fprintf(stderr, "[connection suspended, press return to resume]"); + fflush(stderr); + packet_backup_state(); + /* TODO Perhaps we should read from tty here */ + while ((c = fgetc(stdin)) != EOF) { + if (c == 'Z' - 64) { + kill(getpid(), SIGTSTP); + continue; + } + if (c != '\n' && c != '\r') + continue; + + if (ssh_connect(host, &hostaddr, options.port, + options.address_family, 1, &timeout_ms, + options.tcp_keep_alive, options.use_privileged_port, + options.proxy_command) == 0 && roaming_resume() == 0) { + packet_restore_state(); + reenter_guard = 0; + fprintf(stderr, "[connection resumed]\n"); + fflush(stderr); + return 0; + } + + fprintf(stderr, "[reconnect failed, press return to retry]"); + fflush(stderr); + } + fprintf(stderr, "[exiting]\n"); + fflush(stderr); + exit(0); +} |