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