roaming_client.c revision 240075
1240075Sdes/* $OpenBSD: roaming_client.c,v 1.4 2011/12/07 05:44:38 djm Exp $ */
2204861Sdes/*
3204861Sdes * Copyright (c) 2004-2009 AppGate Network Security AB
4204861Sdes *
5204861Sdes * Permission to use, copy, modify, and distribute this software for any
6204861Sdes * purpose with or without fee is hereby granted, provided that the above
7204861Sdes * copyright notice and this permission notice appear in all copies.
8204861Sdes *
9204861Sdes * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10204861Sdes * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11204861Sdes * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12204861Sdes * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13204861Sdes * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14204861Sdes * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15204861Sdes * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16204861Sdes */
17204861Sdes
18204861Sdes#include "includes.h"
19204861Sdes
20204861Sdes#include "openbsd-compat/sys-queue.h"
21204861Sdes#include <sys/types.h>
22204861Sdes#include <sys/socket.h>
23204861Sdes
24204861Sdes#ifdef HAVE_INTTYPES_H
25204861Sdes#include <inttypes.h>
26204861Sdes#endif
27204861Sdes#include <signal.h>
28204861Sdes#include <string.h>
29204861Sdes#include <unistd.h>
30204861Sdes
31204861Sdes#include <openssl/crypto.h>
32204861Sdes#include <openssl/sha.h>
33204861Sdes
34204861Sdes#include "xmalloc.h"
35204861Sdes#include "buffer.h"
36204861Sdes#include "channels.h"
37204861Sdes#include "cipher.h"
38204861Sdes#include "dispatch.h"
39204861Sdes#include "clientloop.h"
40204861Sdes#include "log.h"
41204861Sdes#include "match.h"
42204861Sdes#include "misc.h"
43204861Sdes#include "packet.h"
44204861Sdes#include "ssh.h"
45204861Sdes#include "key.h"
46204861Sdes#include "kex.h"
47204861Sdes#include "readconf.h"
48204861Sdes#include "roaming.h"
49204861Sdes#include "ssh2.h"
50204861Sdes#include "sshconnect.h"
51204861Sdes
52204861Sdes/* import */
53204861Sdesextern Options options;
54204861Sdesextern char *host;
55204861Sdesextern struct sockaddr_storage hostaddr;
56204861Sdesextern int session_resumed;
57204861Sdes
58204861Sdesstatic u_int32_t roaming_id;
59204861Sdesstatic u_int64_t cookie;
60204861Sdesstatic u_int64_t lastseenchall;
61204861Sdesstatic u_int64_t key1, key2, oldkey1, oldkey2;
62204861Sdes
63204861Sdesvoid
64204861Sdesroaming_reply(int type, u_int32_t seq, void *ctxt)
65204861Sdes{
66204861Sdes	if (type == SSH2_MSG_REQUEST_FAILURE) {
67204861Sdes		logit("Server denied roaming");
68204861Sdes		return;
69204861Sdes	}
70204861Sdes	verbose("Roaming enabled");
71204861Sdes	roaming_id = packet_get_int();
72204861Sdes	cookie = packet_get_int64();
73204861Sdes	key1 = oldkey1 = packet_get_int64();
74204861Sdes	key2 = oldkey2 = packet_get_int64();
75240075Sdes	set_out_buffer_size(packet_get_int() + get_snd_buf_size());
76204861Sdes	roaming_enabled = 1;
77204861Sdes}
78204861Sdes
79204861Sdesvoid
80204861Sdesrequest_roaming(void)
81204861Sdes{
82204861Sdes	packet_start(SSH2_MSG_GLOBAL_REQUEST);
83204861Sdes	packet_put_cstring(ROAMING_REQUEST);
84204861Sdes	packet_put_char(1);
85204861Sdes	packet_put_int(get_recv_buf_size());
86204861Sdes	packet_send();
87204861Sdes	client_register_global_confirm(roaming_reply, NULL);
88204861Sdes}
89204861Sdes
90204861Sdesstatic void
91204861Sdesroaming_auth_required(void)
92204861Sdes{
93204861Sdes	u_char digest[SHA_DIGEST_LENGTH];
94204861Sdes	EVP_MD_CTX md;
95204861Sdes	Buffer b;
96204861Sdes	const EVP_MD *evp_md = EVP_sha1();
97204861Sdes	u_int64_t chall, oldchall;
98204861Sdes
99204861Sdes	chall = packet_get_int64();
100204861Sdes	oldchall = packet_get_int64();
101204861Sdes	if (oldchall != lastseenchall) {
102204861Sdes		key1 = oldkey1;
103204861Sdes		key2 = oldkey2;
104204861Sdes	}
105204861Sdes	lastseenchall = chall;
106204861Sdes
107204861Sdes	buffer_init(&b);
108204861Sdes	buffer_put_int64(&b, cookie);
109204861Sdes	buffer_put_int64(&b, chall);
110204861Sdes	EVP_DigestInit(&md, evp_md);
111204861Sdes	EVP_DigestUpdate(&md, buffer_ptr(&b), buffer_len(&b));
112204861Sdes	EVP_DigestFinal(&md, digest, NULL);
113204861Sdes	buffer_free(&b);
114204861Sdes
115204861Sdes	packet_start(SSH2_MSG_KEX_ROAMING_AUTH);
116204861Sdes	packet_put_int64(key1 ^ get_recv_bytes());
117204861Sdes	packet_put_raw(digest, sizeof(digest));
118204861Sdes	packet_send();
119204861Sdes
120204861Sdes	oldkey1 = key1;
121204861Sdes	oldkey2 = key2;
122204861Sdes	calculate_new_key(&key1, cookie, chall);
123204861Sdes	calculate_new_key(&key2, cookie, chall);
124204861Sdes
125204861Sdes	debug("Received %llu bytes", (unsigned long long)get_recv_bytes());
126204861Sdes	debug("Sent roaming_auth packet");
127204861Sdes}
128204861Sdes
129204861Sdesint
130204861Sdesresume_kex(void)
131204861Sdes{
132204861Sdes	/*
133204861Sdes	 * This should not happen - if the client sends the kex method
134204861Sdes	 * resume@appgate.com then the kex is done in roaming_resume().
135204861Sdes	 */
136204861Sdes	return 1;
137204861Sdes}
138204861Sdes
139204861Sdesstatic int
140204861Sdesroaming_resume(void)
141204861Sdes{
142204861Sdes	u_int64_t recv_bytes;
143204861Sdes	char *str = NULL, *kexlist = NULL, *c;
144204861Sdes	int i, type;
145204861Sdes	int timeout_ms = options.connection_timeout * 1000;
146204861Sdes	u_int len;
147204861Sdes	u_int32_t rnd = 0;
148204861Sdes
149204861Sdes	resume_in_progress = 1;
150204861Sdes
151204861Sdes	/* Exchange banners */
152204861Sdes	ssh_exchange_identification(timeout_ms);
153204861Sdes	packet_set_nonblocking();
154204861Sdes
155204861Sdes	/* Send a kexinit message with resume@appgate.com as only kex algo */
156204861Sdes	packet_start(SSH2_MSG_KEXINIT);
157204861Sdes	for (i = 0; i < KEX_COOKIE_LEN; i++) {
158204861Sdes		if (i % 4 == 0)
159204861Sdes			rnd = arc4random();
160204861Sdes		packet_put_char(rnd & 0xff);
161204861Sdes		rnd >>= 8;
162204861Sdes	}
163204861Sdes	packet_put_cstring(KEX_RESUME);
164204861Sdes	for (i = 1; i < PROPOSAL_MAX; i++) {
165204861Sdes		/* kex algorithm added so start with i=1 and not 0 */
166204861Sdes		packet_put_cstring(""); /* Not used when we resume */
167204861Sdes	}
168204861Sdes	packet_put_char(1); /* first kex_packet follows */
169204861Sdes	packet_put_int(0); /* reserved */
170204861Sdes	packet_send();
171204861Sdes
172204861Sdes	/* Assume that resume@appgate.com will be accepted */
173204861Sdes	packet_start(SSH2_MSG_KEX_ROAMING_RESUME);
174204861Sdes	packet_put_int(roaming_id);
175204861Sdes	packet_send();
176204861Sdes
177204861Sdes	/* Read the server's kexinit and check for resume@appgate.com */
178204861Sdes	if ((type = packet_read()) != SSH2_MSG_KEXINIT) {
179204861Sdes		debug("expected kexinit on resume, got %d", type);
180204861Sdes		goto fail;
181204861Sdes	}
182204861Sdes	for (i = 0; i < KEX_COOKIE_LEN; i++)
183204861Sdes		(void)packet_get_char();
184204861Sdes	kexlist = packet_get_string(&len);
185204861Sdes	if (!kexlist
186204861Sdes	    || (str = match_list(KEX_RESUME, kexlist, NULL)) == NULL) {
187204861Sdes		debug("server doesn't allow resume");
188204861Sdes		goto fail;
189204861Sdes	}
190204861Sdes	xfree(str);
191204861Sdes	for (i = 1; i < PROPOSAL_MAX; i++) {
192204861Sdes		/* kex algorithm taken care of so start with i=1 and not 0 */
193204861Sdes		xfree(packet_get_string(&len));
194204861Sdes	}
195204861Sdes	i = packet_get_char(); /* first_kex_packet_follows */
196204861Sdes	if (i && (c = strchr(kexlist, ',')))
197204861Sdes		*c = 0;
198204861Sdes	if (i && strcmp(kexlist, KEX_RESUME)) {
199204861Sdes		debug("server's kex guess (%s) was wrong, skipping", kexlist);
200204861Sdes		(void)packet_read(); /* Wrong guess - discard packet */
201204861Sdes	}
202204861Sdes
203204861Sdes	/*
204204861Sdes	 * Read the ROAMING_AUTH_REQUIRED challenge from the server and
205204861Sdes	 * send ROAMING_AUTH
206204861Sdes	 */
207204861Sdes	if ((type = packet_read()) != SSH2_MSG_KEX_ROAMING_AUTH_REQUIRED) {
208204861Sdes		debug("expected roaming_auth_required, got %d", type);
209204861Sdes		goto fail;
210204861Sdes	}
211204861Sdes	roaming_auth_required();
212204861Sdes
213204861Sdes	/* Read ROAMING_AUTH_OK from the server */
214204861Sdes	if ((type = packet_read()) != SSH2_MSG_KEX_ROAMING_AUTH_OK) {
215204861Sdes		debug("expected roaming_auth_ok, got %d", type);
216204861Sdes		goto fail;
217204861Sdes	}
218204861Sdes	recv_bytes = packet_get_int64() ^ oldkey2;
219204861Sdes	debug("Peer received %llu bytes", (unsigned long long)recv_bytes);
220204861Sdes	resend_bytes(packet_get_connection_out(), &recv_bytes);
221204861Sdes
222204861Sdes	resume_in_progress = 0;
223204861Sdes
224204861Sdes	session_resumed = 1; /* Tell clientloop */
225204861Sdes
226204861Sdes	return 0;
227204861Sdes
228204861Sdesfail:
229204861Sdes	if (kexlist)
230204861Sdes		xfree(kexlist);
231204861Sdes	if (packet_get_connection_in() == packet_get_connection_out())
232204861Sdes		close(packet_get_connection_in());
233204861Sdes	else {
234204861Sdes		close(packet_get_connection_in());
235204861Sdes		close(packet_get_connection_out());
236204861Sdes	}
237204861Sdes	return 1;
238204861Sdes}
239204861Sdes
240204861Sdesint
241204861Sdeswait_for_roaming_reconnect(void)
242204861Sdes{
243204861Sdes	static int reenter_guard = 0;
244204861Sdes	int timeout_ms = options.connection_timeout * 1000;
245204861Sdes	int c;
246204861Sdes
247204861Sdes	if (reenter_guard != 0)
248204861Sdes		fatal("Server refused resume, roaming timeout may be exceeded");
249204861Sdes	reenter_guard = 1;
250204861Sdes
251204861Sdes	fprintf(stderr, "[connection suspended, press return to resume]");
252204861Sdes	fflush(stderr);
253204861Sdes	packet_backup_state();
254204861Sdes	/* TODO Perhaps we should read from tty here */
255204861Sdes	while ((c = fgetc(stdin)) != EOF) {
256204861Sdes		if (c == 'Z' - 64) {
257204861Sdes			kill(getpid(), SIGTSTP);
258204861Sdes			continue;
259204861Sdes		}
260204861Sdes		if (c != '\n' && c != '\r')
261204861Sdes			continue;
262204861Sdes
263204861Sdes		if (ssh_connect(host, &hostaddr, options.port,
264204861Sdes		    options.address_family, 1, &timeout_ms,
265204861Sdes		    options.tcp_keep_alive, options.use_privileged_port,
266204861Sdes		    options.proxy_command) == 0 && roaming_resume() == 0) {
267204861Sdes			packet_restore_state();
268204861Sdes			reenter_guard = 0;
269204861Sdes			fprintf(stderr, "[connection resumed]\n");
270204861Sdes			fflush(stderr);
271204861Sdes			return 0;
272204861Sdes		}
273204861Sdes
274204861Sdes		fprintf(stderr, "[reconnect failed, press return to retry]");
275204861Sdes		fflush(stderr);
276204861Sdes	}
277204861Sdes	fprintf(stderr, "[exiting]\n");
278204861Sdes	fflush(stderr);
279204861Sdes	exit(0);
280204861Sdes}
281