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