okan@openbsd.org | 80c25b7 | 2015-01-27 12:54:06 +0000 | [diff] [blame] | 1 | /* $OpenBSD: roaming_client.c,v 1.9 2015/01/27 12:54:06 okan Exp $ */ |
Darren Tucker | 8cbd403 | 2010-01-08 19:13:25 +1100 | [diff] [blame] | 2 | /* |
| 3 | * Copyright (c) 2004-2009 AppGate Network Security AB |
| 4 | * |
| 5 | * Permission to use, copy, modify, and distribute this software for any |
| 6 | * purpose with or without fee is hereby granted, provided that the above |
| 7 | * copyright notice and this permission notice appear in all copies. |
| 8 | * |
| 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
| 10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
| 11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
| 12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
| 13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
| 14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
| 15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
| 16 | */ |
| 17 | |
Darren Tucker | cb5a1b6 | 2010-01-08 20:09:01 +1100 | [diff] [blame] | 18 | #include "includes.h" |
| 19 | |
Tim Rice | f377567 | 2010-01-16 16:48:39 -0800 | [diff] [blame] | 20 | #include "openbsd-compat/sys-queue.h" |
Darren Tucker | 8cbd403 | 2010-01-08 19:13:25 +1100 | [diff] [blame] | 21 | #include <sys/types.h> |
| 22 | #include <sys/socket.h> |
| 23 | |
Darren Tucker | 8cbd403 | 2010-01-08 19:13:25 +1100 | [diff] [blame] | 24 | #include <signal.h> |
| 25 | #include <string.h> |
| 26 | #include <unistd.h> |
| 27 | |
Darren Tucker | 8cbd403 | 2010-01-08 19:13:25 +1100 | [diff] [blame] | 28 | #include "xmalloc.h" |
| 29 | #include "buffer.h" |
| 30 | #include "channels.h" |
| 31 | #include "cipher.h" |
| 32 | #include "dispatch.h" |
| 33 | #include "clientloop.h" |
| 34 | #include "log.h" |
| 35 | #include "match.h" |
| 36 | #include "misc.h" |
| 37 | #include "packet.h" |
| 38 | #include "ssh.h" |
| 39 | #include "key.h" |
| 40 | #include "kex.h" |
| 41 | #include "readconf.h" |
| 42 | #include "roaming.h" |
| 43 | #include "ssh2.h" |
| 44 | #include "sshconnect.h" |
Damien Miller | b3051d0 | 2014-01-10 10:58:53 +1100 | [diff] [blame] | 45 | #include "digest.h" |
Darren Tucker | 8cbd403 | 2010-01-08 19:13:25 +1100 | [diff] [blame] | 46 | |
| 47 | /* import */ |
| 48 | extern Options options; |
| 49 | extern char *host; |
| 50 | extern struct sockaddr_storage hostaddr; |
| 51 | extern int session_resumed; |
| 52 | |
| 53 | static u_int32_t roaming_id; |
| 54 | static u_int64_t cookie; |
| 55 | static u_int64_t lastseenchall; |
| 56 | static u_int64_t key1, key2, oldkey1, oldkey2; |
| 57 | |
| 58 | void |
| 59 | roaming_reply(int type, u_int32_t seq, void *ctxt) |
| 60 | { |
| 61 | if (type == SSH2_MSG_REQUEST_FAILURE) { |
| 62 | logit("Server denied roaming"); |
| 63 | return; |
| 64 | } |
| 65 | verbose("Roaming enabled"); |
| 66 | roaming_id = packet_get_int(); |
| 67 | cookie = packet_get_int64(); |
| 68 | key1 = oldkey1 = packet_get_int64(); |
| 69 | key2 = oldkey2 = packet_get_int64(); |
Damien Miller | 8ed4de8 | 2011-12-19 10:52:50 +1100 | [diff] [blame] | 70 | set_out_buffer_size(packet_get_int() + get_snd_buf_size()); |
Darren Tucker | 8cbd403 | 2010-01-08 19:13:25 +1100 | [diff] [blame] | 71 | roaming_enabled = 1; |
| 72 | } |
| 73 | |
| 74 | void |
| 75 | request_roaming(void) |
| 76 | { |
| 77 | packet_start(SSH2_MSG_GLOBAL_REQUEST); |
| 78 | packet_put_cstring(ROAMING_REQUEST); |
| 79 | packet_put_char(1); |
| 80 | packet_put_int(get_recv_buf_size()); |
| 81 | packet_send(); |
| 82 | client_register_global_confirm(roaming_reply, NULL); |
| 83 | } |
| 84 | |
| 85 | static void |
| 86 | roaming_auth_required(void) |
| 87 | { |
Damien Miller | b3051d0 | 2014-01-10 10:58:53 +1100 | [diff] [blame] | 88 | u_char digest[SSH_DIGEST_MAX_LENGTH]; |
Darren Tucker | 8cbd403 | 2010-01-08 19:13:25 +1100 | [diff] [blame] | 89 | Buffer b; |
Darren Tucker | 8cbd403 | 2010-01-08 19:13:25 +1100 | [diff] [blame] | 90 | u_int64_t chall, oldchall; |
| 91 | |
| 92 | chall = packet_get_int64(); |
| 93 | oldchall = packet_get_int64(); |
| 94 | if (oldchall != lastseenchall) { |
| 95 | key1 = oldkey1; |
| 96 | key2 = oldkey2; |
| 97 | } |
| 98 | lastseenchall = chall; |
| 99 | |
| 100 | buffer_init(&b); |
| 101 | buffer_put_int64(&b, cookie); |
| 102 | buffer_put_int64(&b, chall); |
Damien Miller | b3051d0 | 2014-01-10 10:58:53 +1100 | [diff] [blame] | 103 | if (ssh_digest_buffer(SSH_DIGEST_SHA1, &b, digest, sizeof(digest)) != 0) |
| 104 | fatal("%s: ssh_digest_buffer failed", __func__); |
Darren Tucker | 8cbd403 | 2010-01-08 19:13:25 +1100 | [diff] [blame] | 105 | buffer_free(&b); |
| 106 | |
| 107 | packet_start(SSH2_MSG_KEX_ROAMING_AUTH); |
| 108 | packet_put_int64(key1 ^ get_recv_bytes()); |
Damien Miller | b3051d0 | 2014-01-10 10:58:53 +1100 | [diff] [blame] | 109 | packet_put_raw(digest, ssh_digest_bytes(SSH_DIGEST_SHA1)); |
Darren Tucker | 8cbd403 | 2010-01-08 19:13:25 +1100 | [diff] [blame] | 110 | packet_send(); |
| 111 | |
| 112 | oldkey1 = key1; |
| 113 | oldkey2 = key2; |
| 114 | calculate_new_key(&key1, cookie, chall); |
| 115 | calculate_new_key(&key2, cookie, chall); |
| 116 | |
Tim Rice | f377567 | 2010-01-16 16:48:39 -0800 | [diff] [blame] | 117 | debug("Received %llu bytes", (unsigned long long)get_recv_bytes()); |
Darren Tucker | 8cbd403 | 2010-01-08 19:13:25 +1100 | [diff] [blame] | 118 | debug("Sent roaming_auth packet"); |
| 119 | } |
| 120 | |
| 121 | int |
| 122 | resume_kex(void) |
| 123 | { |
| 124 | /* |
| 125 | * This should not happen - if the client sends the kex method |
| 126 | * resume@appgate.com then the kex is done in roaming_resume(). |
| 127 | */ |
| 128 | return 1; |
| 129 | } |
| 130 | |
| 131 | static int |
| 132 | roaming_resume(void) |
| 133 | { |
| 134 | u_int64_t recv_bytes; |
| 135 | char *str = NULL, *kexlist = NULL, *c; |
| 136 | int i, type; |
| 137 | int timeout_ms = options.connection_timeout * 1000; |
| 138 | u_int len; |
| 139 | u_int32_t rnd = 0; |
| 140 | |
| 141 | resume_in_progress = 1; |
| 142 | |
| 143 | /* Exchange banners */ |
| 144 | ssh_exchange_identification(timeout_ms); |
| 145 | packet_set_nonblocking(); |
| 146 | |
| 147 | /* Send a kexinit message with resume@appgate.com as only kex algo */ |
| 148 | packet_start(SSH2_MSG_KEXINIT); |
| 149 | for (i = 0; i < KEX_COOKIE_LEN; i++) { |
| 150 | if (i % 4 == 0) |
| 151 | rnd = arc4random(); |
| 152 | packet_put_char(rnd & 0xff); |
| 153 | rnd >>= 8; |
| 154 | } |
| 155 | packet_put_cstring(KEX_RESUME); |
| 156 | for (i = 1; i < PROPOSAL_MAX; i++) { |
| 157 | /* kex algorithm added so start with i=1 and not 0 */ |
| 158 | packet_put_cstring(""); /* Not used when we resume */ |
| 159 | } |
| 160 | packet_put_char(1); /* first kex_packet follows */ |
| 161 | packet_put_int(0); /* reserved */ |
| 162 | packet_send(); |
| 163 | |
| 164 | /* Assume that resume@appgate.com will be accepted */ |
| 165 | packet_start(SSH2_MSG_KEX_ROAMING_RESUME); |
| 166 | packet_put_int(roaming_id); |
| 167 | packet_send(); |
| 168 | |
| 169 | /* Read the server's kexinit and check for resume@appgate.com */ |
| 170 | if ((type = packet_read()) != SSH2_MSG_KEXINIT) { |
| 171 | debug("expected kexinit on resume, got %d", type); |
| 172 | goto fail; |
| 173 | } |
| 174 | for (i = 0; i < KEX_COOKIE_LEN; i++) |
| 175 | (void)packet_get_char(); |
| 176 | kexlist = packet_get_string(&len); |
| 177 | if (!kexlist |
| 178 | || (str = match_list(KEX_RESUME, kexlist, NULL)) == NULL) { |
| 179 | debug("server doesn't allow resume"); |
| 180 | goto fail; |
| 181 | } |
Darren Tucker | a627d42 | 2013-06-02 07:31:17 +1000 | [diff] [blame] | 182 | free(str); |
Darren Tucker | 8cbd403 | 2010-01-08 19:13:25 +1100 | [diff] [blame] | 183 | for (i = 1; i < PROPOSAL_MAX; i++) { |
| 184 | /* kex algorithm taken care of so start with i=1 and not 0 */ |
Darren Tucker | a627d42 | 2013-06-02 07:31:17 +1000 | [diff] [blame] | 185 | free(packet_get_string(&len)); |
Darren Tucker | 8cbd403 | 2010-01-08 19:13:25 +1100 | [diff] [blame] | 186 | } |
| 187 | i = packet_get_char(); /* first_kex_packet_follows */ |
| 188 | if (i && (c = strchr(kexlist, ','))) |
| 189 | *c = 0; |
| 190 | if (i && strcmp(kexlist, KEX_RESUME)) { |
| 191 | debug("server's kex guess (%s) was wrong, skipping", kexlist); |
| 192 | (void)packet_read(); /* Wrong guess - discard packet */ |
| 193 | } |
| 194 | |
| 195 | /* |
| 196 | * Read the ROAMING_AUTH_REQUIRED challenge from the server and |
| 197 | * send ROAMING_AUTH |
| 198 | */ |
| 199 | if ((type = packet_read()) != SSH2_MSG_KEX_ROAMING_AUTH_REQUIRED) { |
| 200 | debug("expected roaming_auth_required, got %d", type); |
| 201 | goto fail; |
| 202 | } |
| 203 | roaming_auth_required(); |
| 204 | |
| 205 | /* Read ROAMING_AUTH_OK from the server */ |
| 206 | if ((type = packet_read()) != SSH2_MSG_KEX_ROAMING_AUTH_OK) { |
| 207 | debug("expected roaming_auth_ok, got %d", type); |
| 208 | goto fail; |
| 209 | } |
| 210 | recv_bytes = packet_get_int64() ^ oldkey2; |
Tim Rice | f377567 | 2010-01-16 16:48:39 -0800 | [diff] [blame] | 211 | debug("Peer received %llu bytes", (unsigned long long)recv_bytes); |
Darren Tucker | 8cbd403 | 2010-01-08 19:13:25 +1100 | [diff] [blame] | 212 | resend_bytes(packet_get_connection_out(), &recv_bytes); |
| 213 | |
| 214 | resume_in_progress = 0; |
| 215 | |
| 216 | session_resumed = 1; /* Tell clientloop */ |
| 217 | |
| 218 | return 0; |
| 219 | |
| 220 | fail: |
Darren Tucker | a627d42 | 2013-06-02 07:31:17 +1000 | [diff] [blame] | 221 | free(kexlist); |
Darren Tucker | 8cbd403 | 2010-01-08 19:13:25 +1100 | [diff] [blame] | 222 | if (packet_get_connection_in() == packet_get_connection_out()) |
| 223 | close(packet_get_connection_in()); |
| 224 | else { |
| 225 | close(packet_get_connection_in()); |
| 226 | close(packet_get_connection_out()); |
| 227 | } |
| 228 | return 1; |
| 229 | } |
| 230 | |
| 231 | int |
| 232 | wait_for_roaming_reconnect(void) |
| 233 | { |
| 234 | static int reenter_guard = 0; |
| 235 | int timeout_ms = options.connection_timeout * 1000; |
| 236 | int c; |
| 237 | |
| 238 | if (reenter_guard != 0) |
| 239 | fatal("Server refused resume, roaming timeout may be exceeded"); |
| 240 | reenter_guard = 1; |
| 241 | |
| 242 | fprintf(stderr, "[connection suspended, press return to resume]"); |
| 243 | fflush(stderr); |
| 244 | packet_backup_state(); |
| 245 | /* TODO Perhaps we should read from tty here */ |
| 246 | while ((c = fgetc(stdin)) != EOF) { |
| 247 | if (c == 'Z' - 64) { |
| 248 | kill(getpid(), SIGTSTP); |
| 249 | continue; |
| 250 | } |
| 251 | if (c != '\n' && c != '\r') |
| 252 | continue; |
| 253 | |
Damien Miller | 0faf747 | 2013-10-17 11:47:23 +1100 | [diff] [blame] | 254 | if (ssh_connect(host, NULL, &hostaddr, options.port, |
Darren Tucker | 8cbd403 | 2010-01-08 19:13:25 +1100 | [diff] [blame] | 255 | options.address_family, 1, &timeout_ms, |
Damien Miller | 0faf747 | 2013-10-17 11:47:23 +1100 | [diff] [blame] | 256 | options.tcp_keep_alive, options.use_privileged_port) == 0 && |
| 257 | roaming_resume() == 0) { |
Darren Tucker | 8cbd403 | 2010-01-08 19:13:25 +1100 | [diff] [blame] | 258 | packet_restore_state(); |
| 259 | reenter_guard = 0; |
| 260 | fprintf(stderr, "[connection resumed]\n"); |
| 261 | fflush(stderr); |
| 262 | return 0; |
| 263 | } |
| 264 | |
| 265 | fprintf(stderr, "[reconnect failed, press return to retry]"); |
| 266 | fflush(stderr); |
| 267 | } |
| 268 | fprintf(stderr, "[exiting]\n"); |
| 269 | fflush(stderr); |
| 270 | exit(0); |
| 271 | } |