1187706Sgonzo/* $OpenBSD: roaming_client.c,v 1.5 2013/05/17 00:13:14 djm Exp $ */
2187706Sgonzo/*
3187706Sgonzo * Copyright (c) 2004-2009 AppGate Network Security AB
4187706Sgonzo *
5187706Sgonzo * Permission to use, copy, modify, and distribute this software for any
6187706Sgonzo * purpose with or without fee is hereby granted, provided that the above
7187706Sgonzo * copyright notice and this permission notice appear in all copies.
8187706Sgonzo *
9187706Sgonzo * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10187706Sgonzo * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11187706Sgonzo * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12187706Sgonzo * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13187706Sgonzo * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14187706Sgonzo * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15187706Sgonzo * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16187706Sgonzo */
17187706Sgonzo
18187706Sgonzo#include "includes.h"
19187706Sgonzo
20187706Sgonzo#include "openbsd-compat/sys-queue.h"
21187706Sgonzo#include <sys/types.h>
22187706Sgonzo#include <sys/socket.h>
23187706Sgonzo
24187706Sgonzo#ifdef HAVE_INTTYPES_H
25187706Sgonzo#include <inttypes.h>
26187706Sgonzo#endif
27187706Sgonzo#include <signal.h>
28187706Sgonzo#include <string.h>
29187706Sgonzo#include <unistd.h>
30187706Sgonzo
31230195Sadrian#include <openssl/crypto.h>
32230195Sadrian#include <openssl/sha.h>
33187706Sgonzo
34187706Sgonzo#include "xmalloc.h"
35187706Sgonzo#include "buffer.h"
36187706Sgonzo#include "channels.h"
37187706Sgonzo#include "cipher.h"
38187706Sgonzo#include "dispatch.h"
39187706Sgonzo#include "clientloop.h"
40187706Sgonzo#include "log.h"
41187706Sgonzo#include "match.h"
42234365Sadrian#include "misc.h"
43234365Sadrian#include "packet.h"
44187706Sgonzo#include "ssh.h"
45187706Sgonzo#include "key.h"
46187706Sgonzo#include "kex.h"
47187706Sgonzo#include "readconf.h"
48187706Sgonzo#include "roaming.h"
49187706Sgonzo#include "ssh2.h"
50187706Sgonzo#include "sshconnect.h"
51210900Sgonzo
52187706Sgonzo/* import */
53187706Sgonzoextern Options options;
54187706Sgonzoextern char *host;
55187706Sgonzoextern struct sockaddr_storage hostaddr;
56187706Sgonzoextern int session_resumed;
57187706Sgonzo
58187706Sgonzostatic u_int32_t roaming_id;
59192161Sgonzostatic u_int64_t cookie;
60192161Sgonzostatic u_int64_t lastseenchall;
61187706Sgonzostatic u_int64_t key1, key2, oldkey1, oldkey2;
62211478Sadrian
63211478Sadrianvoid
64234217Sadrianroaming_reply(int type, u_int32_t seq, void *ctxt)
65234485Sadrian{
66234217Sadrian	if (type == SSH2_MSG_REQUEST_FAILURE) {
67234217Sadrian		logit("Server denied roaming");
68234366Sadrian		return;
69234366Sadrian	}
70234366Sadrian	verbose("Roaming enabled");
71187706Sgonzo	roaming_id = packet_get_int();
72234366Sadrian	cookie = packet_get_int64();
73187706Sgonzo	key1 = oldkey1 = packet_get_int64();
74187706Sgonzo	key2 = oldkey2 = packet_get_int64();
75234365Sadrian	set_out_buffer_size(packet_get_int() + get_snd_buf_size());
76234365Sadrian	roaming_enabled = 1;
77234365Sadrian}
78234365Sadrian
79187706Sgonzovoid
80187706Sgonzorequest_roaming(void)
81187706Sgonzo{
82187706Sgonzo	packet_start(SSH2_MSG_GLOBAL_REQUEST);
83245112Smonthadar	packet_put_cstring(ROAMING_REQUEST);
84187706Sgonzo	packet_put_char(1);
85187706Sgonzo	packet_put_int(get_recv_buf_size());
86187706Sgonzo	packet_send();
87191872Sgonzo	client_register_global_confirm(roaming_reply, NULL);
88210900Sgonzo}
89187706Sgonzo
90187706Sgonzostatic void
91187706Sgonzoroaming_auth_required(void)
92187706Sgonzo{
93191872Sgonzo	u_char digest[SHA_DIGEST_LENGTH];
94191872Sgonzo	EVP_MD_CTX md;
95191872Sgonzo	Buffer b;
96191872Sgonzo	const EVP_MD *evp_md = EVP_sha1();
97191872Sgonzo	u_int64_t chall, oldchall;
98191872Sgonzo
99234366Sadrian	chall = packet_get_int64();
100192822Sgonzo	oldchall = packet_get_int64();
101191872Sgonzo	if (oldchall != lastseenchall) {
102191872Sgonzo		key1 = oldkey1;
103192822Sgonzo		key2 = oldkey2;
104191872Sgonzo	}
105234365Sadrian	lastseenchall = chall;
106191872Sgonzo
107194273Sgonzo	buffer_init(&b);
108194273Sgonzo	buffer_put_int64(&b, cookie);
109191872Sgonzo	buffer_put_int64(&b, chall);
110191872Sgonzo	EVP_DigestInit(&md, evp_md);
111191872Sgonzo	EVP_DigestUpdate(&md, buffer_ptr(&b), buffer_len(&b));
112234366Sadrian	EVP_DigestFinal(&md, digest, NULL);
113192822Sgonzo	buffer_free(&b);
114191872Sgonzo
115191872Sgonzo	packet_start(SSH2_MSG_KEX_ROAMING_AUTH);
116192822Sgonzo	packet_put_int64(key1 ^ get_recv_bytes());
117191872Sgonzo	packet_put_raw(digest, sizeof(digest));
118234365Sadrian	packet_send();
119191872Sgonzo
120191872Sgonzo	oldkey1 = key1;
121194273Sgonzo	oldkey2 = key2;
122194273Sgonzo	calculate_new_key(&key1, cookie, chall);
123191872Sgonzo	calculate_new_key(&key2, cookie, chall);
124191872Sgonzo
125234366Sadrian	debug("Received %llu bytes", (unsigned long long)get_recv_bytes());
126234366Sadrian	debug("Sent roaming_auth packet");
127234366Sadrian}
128187706Sgonzo
129187706Sgonzoint
130187706Sgonzoresume_kex(void)
131187706Sgonzo{
132187706Sgonzo	/*
133187706Sgonzo	 * This should not happen - if the client sends the kex method
134234366Sadrian	 * resume@appgate.com then the kex is done in roaming_resume().
135187706Sgonzo	 */
136187706Sgonzo	return 1;
137187706Sgonzo}
138187706Sgonzo
139187706Sgonzostatic int
140187706Sgonzoroaming_resume(void)
141187706Sgonzo{
142187706Sgonzo	u_int64_t recv_bytes;
143187706Sgonzo	char *str = NULL, *kexlist = NULL, *c;
144187706Sgonzo	int i, type;
145187706Sgonzo	int timeout_ms = options.connection_timeout * 1000;
146187706Sgonzo	u_int len;
147234366Sadrian	u_int32_t rnd = 0;
148187706Sgonzo
149187706Sgonzo	resume_in_progress = 1;
150187706Sgonzo
151234365Sadrian	/* Exchange banners */
152234365Sadrian	ssh_exchange_identification(timeout_ms);
153234365Sadrian	packet_set_nonblocking();
154187706Sgonzo
155187706Sgonzo	/* Send a kexinit message with resume@appgate.com as only kex algo */
156187706Sgonzo	packet_start(SSH2_MSG_KEXINIT);
157187706Sgonzo	for (i = 0; i < KEX_COOKIE_LEN; i++) {
158187706Sgonzo		if (i % 4 == 0)
159187706Sgonzo			rnd = arc4random();
160187706Sgonzo		packet_put_char(rnd & 0xff);
161187706Sgonzo		rnd >>= 8;
162187706Sgonzo	}
163187706Sgonzo	packet_put_cstring(KEX_RESUME);
164187706Sgonzo	for (i = 1; i < PROPOSAL_MAX; i++) {
165187706Sgonzo		/* kex algorithm added so start with i=1 and not 0 */
166187706Sgonzo		packet_put_cstring(""); /* Not used when we resume */
167187706Sgonzo	}
168187706Sgonzo	packet_put_char(1); /* first kex_packet follows */
169187706Sgonzo	packet_put_int(0); /* reserved */
170187706Sgonzo	packet_send();
171187706Sgonzo
172187706Sgonzo	/* Assume that resume@appgate.com will be accepted */
173187706Sgonzo	packet_start(SSH2_MSG_KEX_ROAMING_RESUME);
174187706Sgonzo	packet_put_int(roaming_id);
175187706Sgonzo	packet_send();
176187706Sgonzo
177187706Sgonzo	/* Read the server's kexinit and check for resume@appgate.com */
178187706Sgonzo	if ((type = packet_read()) != SSH2_MSG_KEXINIT) {
179187706Sgonzo		debug("expected kexinit on resume, got %d", type);
180187706Sgonzo		goto fail;
181187706Sgonzo	}
182187706Sgonzo	for (i = 0; i < KEX_COOKIE_LEN; i++)
183187706Sgonzo		(void)packet_get_char();
184187706Sgonzo	kexlist = packet_get_string(&len);
185187706Sgonzo	if (!kexlist
186187706Sgonzo	    || (str = match_list(KEX_RESUME, kexlist, NULL)) == NULL) {
187187706Sgonzo		debug("server doesn't allow resume");
188234366Sadrian		goto fail;
189187706Sgonzo	}
190187706Sgonzo	free(str);
191187706Sgonzo	for (i = 1; i < PROPOSAL_MAX; i++) {
192187706Sgonzo		/* kex algorithm taken care of so start with i=1 and not 0 */
193187706Sgonzo		free(packet_get_string(&len));
194234366Sadrian	}
195187706Sgonzo	i = packet_get_char(); /* first_kex_packet_follows */
196187706Sgonzo	if (i && (c = strchr(kexlist, ',')))
197187706Sgonzo		*c = 0;
198234365Sadrian	if (i && strcmp(kexlist, KEX_RESUME)) {
199234365Sadrian		debug("server's kex guess (%s) was wrong, skipping", kexlist);
200234365Sadrian		(void)packet_read(); /* Wrong guess - discard packet */
201234366Sadrian	}
202187706Sgonzo
203187706Sgonzo	/*
204187706Sgonzo	 * Read the ROAMING_AUTH_REQUIRED challenge from the server and
205187706Sgonzo	 * send ROAMING_AUTH
206187706Sgonzo	 */
207187706Sgonzo	if ((type = packet_read()) != SSH2_MSG_KEX_ROAMING_AUTH_REQUIRED) {
208187706Sgonzo		debug("expected roaming_auth_required, got %d", type);
209187706Sgonzo		goto fail;
210187706Sgonzo	}
211187706Sgonzo	roaming_auth_required();
212234366Sadrian
213194059Sgonzo	/* Read ROAMING_AUTH_OK from the server */
214187706Sgonzo	if ((type = packet_read()) != SSH2_MSG_KEX_ROAMING_AUTH_OK) {
215187706Sgonzo		debug("expected roaming_auth_ok, got %d", type);
216234205Sadrian		goto fail;
217187706Sgonzo	}
218187706Sgonzo	recv_bytes = packet_get_int64() ^ oldkey2;
219187706Sgonzo	debug("Peer received %llu bytes", (unsigned long long)recv_bytes);
220234306Sadrian	resend_bytes(packet_get_connection_out(), &recv_bytes);
221234306Sadrian
222234306Sadrian	resume_in_progress = 0;
223234306Sadrian
224234306Sadrian	session_resumed = 1; /* Tell clientloop */
225234306Sadrian
226187706Sgonzo	return 0;
227187706Sgonzo
228187706Sgonzofail:
229187706Sgonzo	free(kexlist);
230187706Sgonzo	if (packet_get_connection_in() == packet_get_connection_out())
231187706Sgonzo		close(packet_get_connection_in());
232234365Sadrian	else {
233234204Sadrian		close(packet_get_connection_in());
234234204Sadrian		close(packet_get_connection_out());
235234204Sadrian	}
236234204Sadrian	return 1;
237234204Sadrian}
238234365Sadrian
239187706Sgonzoint
240187706Sgonzowait_for_roaming_reconnect(void)
241187706Sgonzo{
242187706Sgonzo	static int reenter_guard = 0;
243234366Sadrian	int timeout_ms = options.connection_timeout * 1000;
244187706Sgonzo	int c;
245187706Sgonzo
246187706Sgonzo	if (reenter_guard != 0)
247187706Sgonzo		fatal("Server refused resume, roaming timeout may be exceeded");
248187706Sgonzo	reenter_guard = 1;
249234204Sadrian
250187706Sgonzo	fprintf(stderr, "[connection suspended, press return to resume]");
251187706Sgonzo	fflush(stderr);
252187706Sgonzo	packet_backup_state();
253234204Sadrian	/* TODO Perhaps we should read from tty here */
254234204Sadrian	while ((c = fgetc(stdin)) != EOF) {
255234204Sadrian		if (c == 'Z' - 64) {
256234204Sadrian			kill(getpid(), SIGTSTP);
257234204Sadrian			continue;
258234365Sadrian		}
259234204Sadrian		if (c != '\n' && c != '\r')
260234204Sadrian			continue;
261234365Sadrian
262234204Sadrian		if (ssh_connect(host, &hostaddr, options.port,
263234204Sadrian		    options.address_family, 1, &timeout_ms,
264234204Sadrian		    options.tcp_keep_alive, options.use_privileged_port,
265234204Sadrian		    options.proxy_command) == 0 && roaming_resume() == 0) {
266234204Sadrian			packet_restore_state();
267234204Sadrian			reenter_guard = 0;
268234204Sadrian			fprintf(stderr, "[connection resumed]\n");
269234204Sadrian			fflush(stderr);
270187706Sgonzo			return 0;
271187706Sgonzo		}
272187706Sgonzo
273234365Sadrian		fprintf(stderr, "[reconnect failed, press return to retry]");
274234204Sadrian		fflush(stderr);
275234204Sadrian	}
276234204Sadrian	fprintf(stderr, "[exiting]\n");
277234365Sadrian	fflush(stderr);
278187706Sgonzo	exit(0);
279187706Sgonzo}
280230148Sadrian