/* $OpenBSD: roaming_client.c,v 1.3 2010/01/18 01:50:27 dtucker 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 "includes.h" #include "openbsd-compat/sys-queue.h" #include <sys/types.h> #include <sys/socket.h> #ifdef HAVE_INTTYPES_H #include <inttypes.h> #endif #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 %llu bytes", (unsigned long long)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 %llu bytes", (unsigned long long)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); }