| /* $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 "includes.h" |
| |
| #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); |
| } |