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