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