dtls.c 24.1 KB
Newer Older
David Woodhouse's avatar
David Woodhouse committed
1
/*
2
 * OpenConnect (SSL + DTLS) VPN client
David Woodhouse's avatar
David Woodhouse committed
3
 *
David Woodhouse's avatar
David Woodhouse committed
4
 * Copyright © 2008-2012 Intel Corporation.
David Woodhouse's avatar
David Woodhouse committed
5
 *
6 7 8
 * Author: David Woodhouse <dwmw2@infradead.org>
 *
 * This program is free software; you can redistribute it and/or
David Woodhouse's avatar
David Woodhouse committed
9
 * modify it under the terms of the GNU Lesser General Public License
10
 * version 2.1, as published by the Free Software Foundation.
David Woodhouse's avatar
David Woodhouse committed
11
 *
12
 * This program is distributed in the hope that it will be useful, but
David Woodhouse's avatar
David Woodhouse committed
13 14 15 16 17 18 19 20 21 22
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to:
 *
 *   Free Software Foundation, Inc.
 *   51 Franklin Street, Fifth Floor,
 *   Boston, MA 02110-1301 USA
David Woodhouse's avatar
David Woodhouse committed
23 24 25
 */

#include <errno.h>
David Woodhouse's avatar
David Woodhouse committed
26 27 28 29
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
30
#include <netinet/in.h>
31
#include <fcntl.h>
Dirk Hohndel's avatar
Dirk Hohndel committed
32
#include <string.h>
33 34 35
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
David Woodhouse's avatar
David Woodhouse committed
36

37
#include "openconnect-internal.h"
David Woodhouse's avatar
David Woodhouse committed
38

39 40 41 42 43 44 45 46 47 48 49 50 51
static unsigned char nybble(unsigned char n)
{
	if      (n >= '0' && n <= '9') return n - '0';
	else if (n >= 'A' && n <= 'F') return n - ('A' - 10);
	else if (n >= 'a' && n <= 'f') return n - ('a' - 10);
	return 0;
}

unsigned char unhex(const char *data)
{
	return (nybble(data[0]) << 4) | nybble(data[1]);
}

52 53
#ifdef HAVE_DTLS

David Woodhouse's avatar
David Woodhouse committed
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
#if 0
/*
 * Useful for catching test cases, where we want everything to be
 * reproducible.  *NEVER* do this in the wild.
 */
time_t time(time_t *t)
{
	time_t x = 0x3ab2d948;
	if (t) *t = x;
	return x;
}

int RAND_pseudo_bytes(char *buf, int len)
{
	memset(buf, 0x5a, len);
	printf("FAKE PSEUDO RANDOM!\n");
	return 1;
Nick Andrew's avatar
Nick Andrew committed
71

David Woodhouse's avatar
David Woodhouse committed
72 73 74 75 76 77 78 79 80 81
}
int RAND_bytes(char *buf, int len)
{
	static int foo = 0x5b;
	printf("FAKE RANDOM!\n");
	memset(buf, foo, len);
	return 1;
}
#endif

David Woodhouse's avatar
David Woodhouse committed
82 83 84 85 86 87
/*
 * The master-secret is generated randomly by the client. The server
 * responds with a DTLS Session-ID. These, done over the HTTPS
 * connection, are enough to 'resume' a DTLS session, bypassing all
 * the normal setup of a normal DTLS connection.
 *
88 89 90 91
 * Cisco use a version of the protocol which predates RFC4347, but
 * isn't quite the same as the pre-RFC version of the protocol which
 * was in OpenSSL 0.9.8e -- it includes backports of some later
 * OpenSSL patches.
92
 *
Nick Andrew's avatar
Nick Andrew committed
93 94
 * The openssl/ directory of this source tree should contain both a
 * small patch against OpenSSL 0.9.8e to make it support Cisco's
95 96
 * snapshot of the protocol, and a larger patch against newer OpenSSL
 * which gives us an option to use the old protocol again.
97
 *
98 99 100 101 102 103
 * Cisco's server also seems to respond to the official version of the
 * protocol, with a change in the ChangeCipherSpec packet which implies
 * that it does know the difference and isn't just repeating the version
 * number seen in the ClientHello. But although I can make the handshake
 * complete by hacking tls1_mac() to use the _old_ protocol version
 * number when calculating the MAC, the server still seems to be ignoring
104 105
 * my subsequent data packets. So we use the old protocol, which is what
 * their clients use anyway.
Nick Andrew's avatar
Nick Andrew committed
106
 */
David Woodhouse's avatar
David Woodhouse committed
107

108
#if defined (DTLS_OPENSSL)
109 110
#define DTLS_SEND SSL_write
#define DTLS_RECV SSL_read
111 112 113 114 115 116 117

#ifdef HAVE_DTLS1_STOP_TIMER
/* OpenSSL doesn't deliberately export this, but we need it to
   workaround a DTLS bug in versions < 1.0.0e */
extern void dtls1_stop_timer (SSL *);
#endif

118
static int start_dtls_handshake(struct openconnect_info *vpninfo, int dtls_fd)
119
{
120
	STACK_OF(SSL_CIPHER) *ciphers;
121
	method_const SSL_METHOD *dtls_method;
122
	SSL_CIPHER *dtls_cipher;
123 124
	SSL *dtls_ssl;
	BIO *dtls_bio;
Nick Andrew's avatar
Nick Andrew committed
125

David Woodhouse's avatar
David Woodhouse committed
126 127 128 129
	if (!vpninfo->dtls_ctx) {
		dtls_method = DTLSv1_client_method();
		vpninfo->dtls_ctx = SSL_CTX_new(dtls_method);
		if (!vpninfo->dtls_ctx) {
130 131
			vpn_progress(vpninfo, PRG_ERR,
				     _("Initialise DTLSv1 CTX failed\n"));
132
			openconnect_report_ssl_errors(vpninfo);
133
			vpninfo->dtls_attempt_period = 0;
David Woodhouse's avatar
David Woodhouse committed
134 135
			return -EINVAL;
		}
136

David Woodhouse's avatar
David Woodhouse committed
137 138 139
		/* If we don't readahead, then we do short reads and throw
		   away the tail of data packets. */
		SSL_CTX_set_read_ahead(vpninfo->dtls_ctx, 1);
140 141

		if (!SSL_CTX_set_cipher_list(vpninfo->dtls_ctx, vpninfo->dtls_cipher)) {
142 143
			vpn_progress(vpninfo, PRG_ERR,
				     _("Set DTLS cipher list failed\n"));
144 145 146 147 148
			SSL_CTX_free(vpninfo->dtls_ctx);
			vpninfo->dtls_ctx = NULL;
			vpninfo->dtls_attempt_period = 0;
			return -EINVAL;
		}
David Woodhouse's avatar
David Woodhouse committed
149
	}
150

David Woodhouse's avatar
David Woodhouse committed
151 152 153 154
	if (!vpninfo->dtls_session) {
		/* We're going to "resume" a session which never existed. Fake it... */
		vpninfo->dtls_session = SSL_SESSION_new();
		if (!vpninfo->dtls_session) {
155 156
			vpn_progress(vpninfo, PRG_ERR,
				     _("Initialise DTLSv1 session failed\n"));
157
			vpninfo->dtls_attempt_period = 0;
David Woodhouse's avatar
David Woodhouse committed
158
			return -EINVAL;
Nick Andrew's avatar
Nick Andrew committed
159
		}
160
		vpninfo->dtls_session->ssl_version = 0x0100; /* DTLS1_BAD_VER */
161
	}
David Woodhouse's avatar
David Woodhouse committed
162

163 164 165 166
	/* Do this every time; it may have changed due to a rekey */
	vpninfo->dtls_session->master_key_length = sizeof(vpninfo->dtls_secret);
	memcpy(vpninfo->dtls_session->master_key, vpninfo->dtls_secret,
	       sizeof(vpninfo->dtls_secret));
David Woodhouse's avatar
David Woodhouse committed
167

168 169 170
	vpninfo->dtls_session->session_id_length = sizeof(vpninfo->dtls_session_id);
	memcpy(vpninfo->dtls_session->session_id, vpninfo->dtls_session_id,
	       sizeof(vpninfo->dtls_session_id));
171

David Woodhouse's avatar
David Woodhouse committed
172 173
	dtls_ssl = SSL_new(vpninfo->dtls_ctx);
	SSL_set_connect_state(dtls_ssl);
174 175 176

	ciphers = SSL_get_ciphers(dtls_ssl);
	if (sk_SSL_CIPHER_num(ciphers) != 1) {
177
		vpn_progress(vpninfo, PRG_ERR, _("Not precisely one DTLS cipher\n"));
178 179 180 181 182 183 184 185 186 187 188 189 190
		SSL_CTX_free(vpninfo->dtls_ctx);
		SSL_free(dtls_ssl);
		SSL_SESSION_free(vpninfo->dtls_session);
		vpninfo->dtls_ctx = NULL;
		vpninfo->dtls_session = NULL;
		vpninfo->dtls_attempt_period = 0;
		return -EINVAL;
	}
	dtls_cipher = sk_SSL_CIPHER_value(ciphers, 0);

	/* Set the appropriate cipher on our session to be resumed */
	vpninfo->dtls_session->cipher = dtls_cipher;
	vpninfo->dtls_session->cipher_id = dtls_cipher->id;
191

192
	/* Add the generated session to the SSL */
David Woodhouse's avatar
David Woodhouse committed
193
	if (!SSL_set_session(dtls_ssl, vpninfo->dtls_session)) {
194
		vpn_progress(vpninfo, PRG_ERR,
195 196 197 198 199
			     _("SSL_set_session() failed with old protocol version 0x%x\n"
			       "Are you using a version of OpenSSL older than 0.9.8m?\n"
			       "See http://rt.openssl.org/Ticket/Display.html?id=1751\n"
			       "Use the --no-dtls command line option to avoid this message\n"),
			     vpninfo->dtls_session->ssl_version);
200
		vpninfo->dtls_attempt_period = 0;
David Woodhouse's avatar
David Woodhouse committed
201
		return -EINVAL;
202
	}
203

204
	dtls_bio = BIO_new_socket(dtls_fd, BIO_NOCLOSE);
205 206
	/* Set non-blocking */
	BIO_set_nbio(dtls_bio, 1);
207 208
	SSL_set_bio(dtls_ssl, dtls_bio, dtls_bio);

209
	SSL_set_options(dtls_ssl, SSL_OP_CISCO_ANYCONNECT);
David Woodhouse's avatar
David Woodhouse committed
210

211
	vpninfo->new_dtls_ssl = dtls_ssl;
Nick Andrew's avatar
Nick Andrew committed
212

213
	return 0;
214 215
}

216
int dtls_try_handshake(struct openconnect_info *vpninfo)
217
{
218
	int ret = SSL_do_handshake(vpninfo->new_dtls_ssl);
David Woodhouse's avatar
David Woodhouse committed
219

220
	if (ret == 1) {
221
		vpn_progress(vpninfo, PRG_INFO, _("Established DTLS connection (using OpenSSL)\n"));
222 223 224 225 226

		if (vpninfo->dtls_ssl) {
			/* We are replacing an old connection */
			SSL_free(vpninfo->dtls_ssl);
			close(vpninfo->dtls_fd);
227 228 229
			FD_CLR(vpninfo->dtls_fd, &vpninfo->select_rfds);
			FD_CLR(vpninfo->dtls_fd, &vpninfo->select_wfds);
			FD_CLR(vpninfo->dtls_fd, &vpninfo->select_efds);
230 231 232 233 234 235 236
		}
		vpninfo->dtls_ssl = vpninfo->new_dtls_ssl;
		vpninfo->dtls_fd = vpninfo->new_dtls_fd;

		vpninfo->new_dtls_ssl = NULL;
		vpninfo->new_dtls_fd = -1;

237
		vpninfo->dtls_times.last_rx = vpninfo->dtls_times.last_tx = time(NULL);
238

239
		/* From about 8.4.1(11) onwards, the ASA seems to get
240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257
		   very unhappy if we resend ChangeCipherSpec messages
		   after the initial setup. This was "fixed" in OpenSSL
		   1.0.0e for RT#2505, but it's not clear if that was
		   the right fix. What happens if the original packet
		   *does* get lost? Surely we *wanted* the retransmits,
		   because without them the server will never be able
		   to decrypt anything we send?
		   Oh well, our retransmitted packets upset the server
		   because we don't get the Cisco-compatibility right
		   (this is one of the areas in which Cisco's DTLS differs
		   from the RFC4347 spec), and DPD should help us notice
		   if *nothing* is getting through. */
#if OPENSSL_VERSION_NUMBER >= 0x1000005fL
		/* OpenSSL 1.0.0e or above doesn't resend anyway; do nothing.
		   However, if we were *built* against 1.0.0e or newer, but at
		   runtime we find that we are being run against an older 
		   version, warn about it. */
		if (SSLeay() < 0x1000005fL) {
258
			vpn_progress(vpninfo, PRG_ERR,
259
				     _("Your OpenSSL is older than the one you built against, so DTLS may fail!"));
260 261
		}
#elif defined (HAVE_DTLS1_STOP_TIMER)
262 263 264 265 266
		/*
		 * This works for any normal OpenSSL that supports
		 * Cisco DTLS compatibility (0.9.8m to 1.0.0d inclusive,
		 * and even later versions although it isn't needed there.
		 */
267
		dtls1_stop_timer(vpninfo->dtls_ssl);
268 269 270 271 272 273
#elif defined (BIO_CTRL_DGRAM_SET_NEXT_TIMEOUT)
		/*
		 * Debian restricts visibility of dtls1_stop_timer()
		 * so do it manually. This version also works on all
		 * sane versions of OpenSSL:
		 */
274 275 276 277 278 279
		memset (&(vpninfo->dtls_ssl->d1->next_timeout), 0,
			sizeof((vpninfo->dtls_ssl->d1->next_timeout)));
		vpninfo->dtls_ssl->d1->timeout_duration = 1;
		BIO_ctrl(SSL_get_rbio(vpninfo->dtls_ssl),
			 BIO_CTRL_DGRAM_SET_NEXT_TIMEOUT, 0,
			 &(vpninfo->dtls_ssl->d1->next_timeout));
280 281 282 283 284 285 286 287 288 289 290 291 292 293 294
#elif defined (BIO_CTRL_DGRAM_SET_TIMEOUT)
		/*
		 * OK, here it gets more fun... this shoul handle the case
		 * of older OpenSSL which has the Cisco DTLS compatibility
		 * backported, but *not* the fix for RT#1922.
		 */
		BIO_ctrl(SSL_get_rbio(vpninfo->dtls_ssl),
			 BIO_CTRL_DGRAM_SET_TIMEOUT, 0, NULL);
#else
		/*
		 * And if they don't have any of the above, they probably
		 * don't have RT#1829 fixed either, but that's OK because
		 * that's the "fix" that *introduces* the timeout we're
		 * trying to disable. So do nothing...
		 */
295
#endif
296 297 298 299 300 301 302
		return 0;
	}

	ret = SSL_get_error(vpninfo->new_dtls_ssl, ret);
	if (ret == SSL_ERROR_WANT_WRITE || ret == SSL_ERROR_WANT_READ) {
		if (time(NULL) < vpninfo->new_dtls_started + 5)
			return 0;
303
		vpn_progress(vpninfo, PRG_TRACE, _("DTLS handshake timed out\n"));
304 305
	}

306
	vpn_progress(vpninfo, PRG_ERR, _("DTLS handshake failed: %d\n"), ret);
307
	openconnect_report_ssl_errors(vpninfo);
308

309 310
	/* Kill the new (failed) connection... */
	SSL_free(vpninfo->new_dtls_ssl);
311 312
	FD_CLR(vpninfo->new_dtls_fd, &vpninfo->select_rfds);
	FD_CLR(vpninfo->new_dtls_fd, &vpninfo->select_efds);
313 314 315 316 317 318 319 320
	close(vpninfo->new_dtls_fd);
	vpninfo->new_dtls_ssl = NULL;
	vpninfo->new_dtls_fd = -1;

	/* ... and kill the old one too. The only time there'll be a valid
	   existing session is when it was a rekey, and in that case it's
	   time for the old one to die. */
	if (vpninfo->dtls_ssl) {
David Woodhouse's avatar
David Woodhouse committed
321 322
		SSL_free(vpninfo->dtls_ssl);
		close(vpninfo->dtls_fd);
323 324 325
		FD_CLR(vpninfo->dtls_fd, &vpninfo->select_rfds);
		FD_CLR(vpninfo->dtls_fd, &vpninfo->select_wfds);
		FD_CLR(vpninfo->dtls_fd, &vpninfo->select_efds);
David Woodhouse's avatar
David Woodhouse committed
326 327 328
		vpninfo->dtls_ssl = NULL;
		vpninfo->dtls_fd = -1;
	}
David Woodhouse's avatar
David Woodhouse committed
329

330 331 332
	time(&vpninfo->new_dtls_started);
	return -EINVAL;
}
David Woodhouse's avatar
David Woodhouse committed
333

334
#elif defined (DTLS_GNUTLS)
335 336 337 338 339 340 341 342 343 344 345 346
struct {
	const char *name;
	gnutls_cipher_algorithm_t cipher;
	gnutls_mac_algorithm_t mac;
	const char *prio;
} gnutls_dtls_ciphers[] = {
	{ "AES128-SHA", GNUTLS_CIPHER_AES_128_CBC, GNUTLS_MAC_SHA1,
	  "NONE:+VERS-DTLS0.9:+COMP-NULL:+AES-128-CBC:+SHA1:+RSA:%COMPAT:%DISABLE_SAFE_RENEGOTIATION" },
	{ "DES-CBC3-SHA", GNUTLS_CIPHER_3DES_CBC, GNUTLS_MAC_SHA1,
	  "NONE:+VERS-DTLS0.9:+COMP-NULL:+3DES-CBC:+SHA1:+RSA:%COMPAT:%DISABLE_SAFE_RENEGOTIATION" },
};

347 348 349 350 351 352 353
#define DTLS_SEND gnutls_record_send
#define DTLS_RECV gnutls_record_recv
static int start_dtls_handshake(struct openconnect_info *vpninfo, int dtls_fd)
{
	gnutls_session_t dtls_ssl;
	gnutls_datum_t master_secret, session_id;
	int err;
354 355 356 357 358 359 360 361 362 363 364
	int cipher;

	for (cipher = 0; cipher < sizeof(gnutls_dtls_ciphers)/sizeof(gnutls_dtls_ciphers[0]); cipher++) {
		if (!strcmp(vpninfo->dtls_cipher, gnutls_dtls_ciphers[cipher].name))
			goto found_cipher;
	}
	vpn_progress(vpninfo, PRG_ERR, _("Unknown DTLS parameters for requested CipherSuite '%s'\n"),
		     vpninfo->dtls_cipher);
	vpninfo->dtls_attempt_period = 0;

	return -EINVAL;
365

366
 found_cipher:
367 368
	gnutls_init(&dtls_ssl, GNUTLS_CLIENT|GNUTLS_DATAGRAM|GNUTLS_NONBLOCK);
	err = gnutls_priority_set_direct(dtls_ssl,
369
					 gnutls_dtls_ciphers[cipher].prio,
370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385
					 NULL);
	if (err) {
		vpn_progress(vpninfo, PRG_ERR,
			     _("Failed to set DTLS priority: %s\n"),
			     gnutls_strerror(err));
		gnutls_deinit(dtls_ssl);
		vpninfo->dtls_attempt_period = 0;
		return -EINVAL;
	}
	gnutls_transport_set_ptr(dtls_ssl,
				 (gnutls_transport_ptr_t)(long) dtls_fd);
	gnutls_record_disable_padding(dtls_ssl);
	master_secret.data = vpninfo->dtls_secret;
	master_secret.size = sizeof(vpninfo->dtls_secret);
	session_id.data = vpninfo->dtls_session_id;
	session_id.size = sizeof(vpninfo->dtls_session_id);
386
	err = gnutls_session_set_premaster(dtls_ssl, GNUTLS_CLIENT, GNUTLS_DTLS0_9,
387 388
					   GNUTLS_KX_RSA, gnutls_dtls_ciphers[cipher].cipher,
					   gnutls_dtls_ciphers[cipher].mac, GNUTLS_COMP_NULL,
389
					   &master_secret, &session_id);
390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407
	if (err) {
		vpn_progress(vpninfo, PRG_ERR,
			     _("Failed to set DTLS session parameters: %s\n"),
			     gnutls_strerror(err));
		gnutls_deinit(dtls_ssl);
		vpninfo->dtls_attempt_period = 0;
		return -EINVAL;
	}

	vpninfo->new_dtls_ssl = dtls_ssl;
	return 0;
}

int dtls_try_handshake(struct openconnect_info *vpninfo)
{
	int err = gnutls_handshake(vpninfo->new_dtls_ssl);

	if (!err) {
408
		vpn_progress(vpninfo, PRG_INFO, _("Established DTLS connection (using GnuTLS)\n"));
409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464

		if (vpninfo->dtls_ssl) {
			/* We are replacing an old connection */
			gnutls_deinit(vpninfo->dtls_ssl);
			close(vpninfo->dtls_fd);
			FD_CLR(vpninfo->dtls_fd, &vpninfo->select_rfds);
			FD_CLR(vpninfo->dtls_fd, &vpninfo->select_wfds);
			FD_CLR(vpninfo->dtls_fd, &vpninfo->select_efds);
		}
		vpninfo->dtls_ssl = vpninfo->new_dtls_ssl;
		vpninfo->dtls_fd = vpninfo->new_dtls_fd;

		vpninfo->new_dtls_ssl = NULL;
		vpninfo->new_dtls_fd = -1;

		vpninfo->dtls_times.last_rx = vpninfo->dtls_times.last_tx = time(NULL);

		/* XXX: For OpenSSL we explicitly prevent retransmits here. */
		return 0;
	}

	if (err == GNUTLS_E_AGAIN) {
		if (time(NULL) < vpninfo->new_dtls_started + 5)
			return 0;
		vpn_progress(vpninfo, PRG_TRACE, _("DTLS handshake timed out\n"));
	}

	vpn_progress(vpninfo, PRG_ERR, _("DTLS handshake failed: %s\n"),
		     gnutls_strerror(err));

	/* Kill the new (failed) connection... */
	gnutls_deinit(vpninfo->new_dtls_ssl);
	FD_CLR(vpninfo->new_dtls_fd, &vpninfo->select_rfds);
	FD_CLR(vpninfo->new_dtls_fd, &vpninfo->select_efds);
	close(vpninfo->new_dtls_fd);
	vpninfo->new_dtls_ssl = NULL;
	vpninfo->new_dtls_fd = -1;

	/* ... and kill the old one too. The only time there'll be a valid
	   existing session is when it was a rekey, and in that case it's
	   time for the old one to die. */
	if (vpninfo->dtls_ssl) {
		gnutls_deinit(vpninfo->dtls_ssl);
		close(vpninfo->dtls_fd);
		FD_CLR(vpninfo->dtls_fd, &vpninfo->select_rfds);
		FD_CLR(vpninfo->dtls_fd, &vpninfo->select_wfds);
		FD_CLR(vpninfo->dtls_fd, &vpninfo->select_efds);
		vpninfo->dtls_ssl = NULL;
		vpninfo->dtls_fd = -1;
	}

	time(&vpninfo->new_dtls_started);
	return -EINVAL;
}
#endif

465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521
int connect_dtls_socket(struct openconnect_info *vpninfo)
{
	int dtls_fd, ret;

	if (!vpninfo->dtls_addr) {
		vpn_progress(vpninfo, PRG_ERR, _("No DTLS address\n"));
		vpninfo->dtls_attempt_period = 0;
		return -EINVAL;
	}

	if (!vpninfo->dtls_cipher) {
		/* We probably didn't offer it any ciphers it liked */
		vpn_progress(vpninfo, PRG_ERR, _("Server offered no DTLS cipher option\n"));
		vpninfo->dtls_attempt_period = 0;
		return -EINVAL;
	}

	if (vpninfo->proxy) {
		/* XXX: Theoretically, SOCKS5 proxies can do UDP too */
		vpn_progress(vpninfo, PRG_ERR, _("No DTLS when connected via proxy\n"));
		vpninfo->dtls_attempt_period = 0;
		return -EINVAL;
	}

	dtls_fd = socket(vpninfo->peer_addr->sa_family, SOCK_DGRAM, IPPROTO_UDP);
	if (dtls_fd < 0) {
		perror(_("Open UDP socket for DTLS:"));
		return -EINVAL;
	}

	if (connect(dtls_fd, vpninfo->dtls_addr, vpninfo->peer_addrlen)) {
		perror(_("UDP (DTLS) connect:\n"));
		close(dtls_fd);
		return -EINVAL;
	}

	fcntl(dtls_fd, F_SETFD, FD_CLOEXEC);
	fcntl(dtls_fd, F_SETFL, fcntl(dtls_fd, F_GETFL) | O_NONBLOCK);

	ret = start_dtls_handshake(vpninfo, dtls_fd);
	if (ret) {
		close(dtls_fd);
		return ret;
	}

	vpninfo->new_dtls_fd = dtls_fd;
	if (vpninfo->select_nfds <= dtls_fd)
		vpninfo->select_nfds = dtls_fd + 1;

	FD_SET(dtls_fd, &vpninfo->select_rfds);
	FD_SET(dtls_fd, &vpninfo->select_efds);

	time(&vpninfo->new_dtls_started);

	return dtls_try_handshake(vpninfo);
}

522
static int dtls_restart(struct openconnect_info *vpninfo)
523 524
{
	if (vpninfo->dtls_ssl) {
525
#if defined (DTLS_OPENSSL)
526
		SSL_free(vpninfo->dtls_ssl);
527
#elif defined (DTLS_GNUTLS)
528 529
		gnutls_deinit(vpninfo->dtls_ssl);
#endif
530
		close(vpninfo->dtls_fd);
531 532 533
		FD_CLR(vpninfo->dtls_fd, &vpninfo->select_rfds);
		FD_CLR(vpninfo->dtls_fd, &vpninfo->select_wfds);
		FD_CLR(vpninfo->dtls_fd, &vpninfo->select_efds);
534 535 536
		vpninfo->dtls_ssl = NULL;
		vpninfo->dtls_fd = -1;
	}
David Woodhouse's avatar
David Woodhouse committed
537

538
	return connect_dtls_socket(vpninfo);
539 540
}

541

542
int setup_dtls(struct openconnect_info *vpninfo)
David Woodhouse's avatar
David Woodhouse committed
543 544
{
	struct vpn_option *dtls_opt = vpninfo->dtls_options;
David Woodhouse's avatar
David Woodhouse committed
545
	int dtls_port = 0;
David Woodhouse's avatar
David Woodhouse committed
546

547 548 549 550 551 552 553 554 555
#if defined (OPENCONNECT_GNUTLS) && defined (DTLS_OPENSSL)
	/* If we're using GnuTLS for authentication but OpenSSL for DTLS,
	   we'll need to initialise OpenSSL now... */
	SSL_library_init ();
	ERR_clear_error ();
	SSL_load_error_strings ();
	OpenSSL_add_all_algorithms ();
#endif

David Woodhouse's avatar
David Woodhouse committed
556
	while (dtls_opt) {
557
		vpn_progress(vpninfo, PRG_TRACE,
558 559
			     _("DTLS option %s : %s\n"),
			     dtls_opt->option, dtls_opt->value);
David Woodhouse's avatar
David Woodhouse committed
560

561
		if (!strcmp(dtls_opt->option + 7, "Port")) {
David Woodhouse's avatar
David Woodhouse committed
562
			dtls_port = atol(dtls_opt->value);
David Woodhouse's avatar
David Woodhouse committed
563
		} else if (!strcmp(dtls_opt->option + 7, "Keepalive")) {
564
			vpninfo->dtls_times.keepalive = atol(dtls_opt->value);
David Woodhouse's avatar
David Woodhouse committed
565
		} else if (!strcmp(dtls_opt->option + 7, "DPD")) {
David Woodhouse's avatar
David Woodhouse committed
566 567 568
			int j = atol(dtls_opt->value);
			if (j && (!vpninfo->dtls_times.dpd || j < vpninfo->dtls_times.dpd))
				vpninfo->dtls_times.dpd = j;
569
		} else if (!strcmp(dtls_opt->option + 7, "Rekey-Time")) {
570
			vpninfo->dtls_times.rekey = atol(dtls_opt->value);
571
		} else if (!strcmp(dtls_opt->option + 7, "CipherSuite")) {
572
			vpninfo->dtls_cipher = strdup(dtls_opt->value);
David Woodhouse's avatar
David Woodhouse committed
573
		}
Nick Andrew's avatar
Nick Andrew committed
574

David Woodhouse's avatar
David Woodhouse committed
575 576
		dtls_opt = dtls_opt->next;
	}
577
	if (!dtls_port) {
578
		vpninfo->dtls_attempt_period = 0;
David Woodhouse's avatar
David Woodhouse committed
579
		return -EINVAL;
580 581 582 583 584 585 586 587
	}

	vpninfo->dtls_addr = malloc(vpninfo->peer_addrlen);
	if (!vpninfo->dtls_addr) {
		vpninfo->dtls_attempt_period = 0;
		return -ENOMEM;
	}
	memcpy(vpninfo->dtls_addr, vpninfo->peer_addr, vpninfo->peer_addrlen);
David Woodhouse's avatar
David Woodhouse committed
588

589
	if (vpninfo->peer_addr->sa_family == AF_INET) {
590
		struct sockaddr_in *sin = (void *)vpninfo->dtls_addr;
591 592
		sin->sin_port = htons(dtls_port);
	} else if (vpninfo->peer_addr->sa_family == AF_INET6) {
593
		struct sockaddr_in6 *sin = (void *)vpninfo->dtls_addr;
594 595
		sin->sin6_port = htons(dtls_port);
	} else {
596 597 598
		vpn_progress(vpninfo, PRG_ERR,
			     _("Unknown protocol family %d. Cannot do DTLS\n"),
			     vpninfo->peer_addr->sa_family);
599
		vpninfo->dtls_attempt_period = 0;
600 601 602
		return -EINVAL;
	}

603 604
	if (connect_dtls_socket(vpninfo))
		return -EINVAL;
605

606
	vpn_progress(vpninfo, PRG_TRACE,
607 608
		     _("DTLS connected. DPD %d, Keepalive %d\n"),
		     vpninfo->dtls_times.dpd, vpninfo->dtls_times.keepalive);
609 610

	return 0;
David Woodhouse's avatar
David Woodhouse committed
611 612
}

613 614
static struct pkt *dtls_pkt;

615
int dtls_mainloop(struct openconnect_info *vpninfo, int *timeout)
616
{
617
	int work_done = 0;
618
	char magic_pkt;
619

620 621 622 623 624 625 626 627 628 629 630 631 632
	while (1) {
		int len = vpninfo->mtu;
		unsigned char *buf;

		if (!dtls_pkt) {
			dtls_pkt = malloc(sizeof(struct pkt) + len);
			if (!dtls_pkt) {
				vpn_progress(vpninfo, PRG_ERR, "Allocation failed\n");
				break;
			}
		}

		buf = dtls_pkt->data - 1;
633
		len = DTLS_RECV(vpninfo->dtls_ssl, buf, len + 1);
634 635
		if (len <= 0)
			break;
636

637
		vpn_progress(vpninfo, PRG_TRACE,
638 639
			     _("Received DTLS packet 0x%02x of %d bytes\n"),
			     buf[0], len);
640

641
		vpninfo->dtls_times.last_rx = time(NULL);
642

643
		switch(buf[0]) {
David Woodhouse's avatar
David Woodhouse committed
644
		case AC_PKT_DATA:
645 646 647
			dtls_pkt->len = len - 1;
			queue_packet(&vpninfo->incoming_queue, dtls_pkt);
			dtls_pkt = NULL;
648 649 650
			work_done = 1;
			break;

651
		case AC_PKT_DPD_OUT:
652
			vpn_progress(vpninfo, PRG_TRACE, _("Got DTLS DPD request\n"));
653 654 655

			/* FIXME: What if the packet doesn't get through? */
			magic_pkt = AC_PKT_DPD_RESP;
656
			if (DTLS_SEND(vpninfo->dtls_ssl, &magic_pkt, 1) != 1)
657 658
				vpn_progress(vpninfo, PRG_ERR,
					     _("Failed to send DPD response. Expect disconnect\n"));
659 660
			continue;

David Woodhouse's avatar
David Woodhouse committed
661
		case AC_PKT_DPD_RESP:
662
			vpn_progress(vpninfo, PRG_TRACE, _("Got DTLS DPD response\n"));
663 664
			break;

665
		case AC_PKT_KEEPALIVE:
666
			vpn_progress(vpninfo, PRG_TRACE, _("Got DTLS Keepalive\n"));
667 668
			break;

669
		default:
670
			vpn_progress(vpninfo, PRG_ERR,
671 672
				     _("Unknown DTLS packet type %02x, len %d\n"),
				     buf[0], len);
673 674
			if (1) {
				/* Some versions of OpenSSL have bugs with receiving out-of-order
Nick Andrew's avatar
Nick Andrew committed
675
				 * packets. Not only do they wrongly decide to drop packets if
676 677 678 679 680 681 682 683 684
				 * two packets get swapped in transit, but they also _fail_ to
				 * drop the packet in non-blocking mode; instead they return
				 * the appropriate length of garbage. So don't abort... for now. */
				break;
			} else {
				vpninfo->quit_reason = "Unknown packet received";
				return 1;
			}

685
		}
686
	}
687

688 689
	switch (keepalive_action(&vpninfo->dtls_times, timeout)) {
	case KA_REKEY:
690
		vpn_progress(vpninfo, PRG_INFO, _("DTLS rekey due\n"));
691 692 693 694

		/* There ought to be a method of rekeying DTLS without tearing down
		   the CSTP session and restarting, but we don't (yet) know it */
		if (cstp_reconnect(vpninfo)) {
695
			vpn_progress(vpninfo, PRG_ERR, _("Reconnect failed\n"));
696 697 698 699 700
			vpninfo->quit_reason = "CSTP reconnect failed";
			return 1;
		}

		if (dtls_restart(vpninfo)) {
701
			vpn_progress(vpninfo, PRG_ERR, _("DTLS rekey failed\n"));
702 703
			return 1;
		}
704 705 706 707 708
		work_done = 1;
		break;


	case KA_DPD_DEAD:
709
		vpn_progress(vpninfo, PRG_ERR, _("DTLS Dead Peer Detection detected dead peer!\n"));
710 711 712
		/* Fall back to SSL, and start a new DTLS connection */
		dtls_restart(vpninfo);
		return 1;
713 714

	case KA_DPD:
715
		vpn_progress(vpninfo, PRG_TRACE, _("Send DTLS DPD\n"));
716

717
		magic_pkt = AC_PKT_DPD_OUT;
718 719 720 721
		if (DTLS_SEND(vpninfo->dtls_ssl, &magic_pkt, 1) != 1)
			vpn_progress(vpninfo, PRG_ERR,
				     _("Failed to send DPD request. Expect disconnect\n"));

722 723 724 725
		/* last_dpd will just have been set */
		vpninfo->dtls_times.last_tx = vpninfo->dtls_times.last_dpd;
		work_done = 1;
		break;
726

727 728 729 730 731
	case KA_KEEPALIVE:
		/* No need to send an explicit keepalive
		   if we have real data to send */
		if (vpninfo->outgoing_queue)
			break;
732

733
		vpn_progress(vpninfo, PRG_TRACE, _("Send DTLS Keepalive\n"));
734

735
		magic_pkt = AC_PKT_KEEPALIVE;
736 737 738
		if (DTLS_SEND(vpninfo->dtls_ssl, &magic_pkt, 1) != 1)
			vpn_progress(vpninfo, PRG_ERR,
				     _("Failed to send keepalive request. Expect disconnect\n"));
739 740 741
		time(&vpninfo->dtls_times.last_tx);
		work_done = 1;
		break;
742

743 744
	case KA_NONE:
		;
745 746
	}

747
	/* Service outgoing packet queue */
748 749 750
	while (vpninfo->outgoing_queue) {
		struct pkt *this = vpninfo->outgoing_queue;
		int ret;
751

752
		vpninfo->outgoing_queue = this->next;
753
		vpninfo->outgoing_qlen--;
754 755 756

		/* One byte of header */
		this->hdr[7] = AC_PKT_DATA;
Nick Andrew's avatar
Nick Andrew committed
757

758
#if defined(DTLS_OPENSSL)
759
		ret = SSL_write(vpninfo->dtls_ssl, &this->hdr[7], this->len + 1);
760 761 762 763 764 765
		if (ret <= 0) {
			ret = SSL_get_error(vpninfo->dtls_ssl, ret);

			/* If it's a real error, kill the DTLS connection and
			   requeue the packet to be sent over SSL */
			if (ret != SSL_ERROR_WANT_READ && ret != SSL_ERROR_WANT_WRITE) {
766
				vpn_progress(vpninfo, PRG_ERR,
767 768
					     _("DTLS got write error %d. Falling back to SSL\n"),
					     ret);
769
				openconnect_report_ssl_errors(vpninfo);
770 771
				dtls_restart(vpninfo);
				vpninfo->outgoing_queue = this;
772
				vpninfo->outgoing_qlen++;
773 774 775
			}
			return 1;
		}
776
#elif defined (DTLS_GNUTLS)
777 778 779 780 781 782 783 784 785 786 787 788 789
		ret = gnutls_record_send(vpninfo->dtls_ssl, &this->hdr[7], this->len + 1);
		if (ret <= 0) {
			if (ret != GNUTLS_E_AGAIN) {
				vpn_progress(vpninfo, PRG_ERR,
					     _("DTLS got write error: %s. Falling back to SSL\n"),
					     gnutls_strerror(ret));
				dtls_restart(vpninfo);
				vpninfo->outgoing_queue = this;
				vpninfo->outgoing_qlen++;
			}
			return 1;
		}
#endif
790
		time(&vpninfo->dtls_times.last_tx);
791
		vpn_progress(vpninfo, PRG_TRACE,
792
			     _("Sent DTLS packet of %d bytes; DTLS send returned %d\n"),
793
			     this->len, ret);
794
		free(this);
795
	}
796

797
	return work_done;
798
}
799 800
#else /* !HAVE_DTLS */
#warning Your SSL library does not seem to support Cisco DTLS compatibility
801
 int setup_dtls(struct openconnect_info *vpninfo)
802
{
803
	vpn_progress(vpninfo, PRG_ERR,
804
		     _("Built against SSL library with no Cisco DTLS support\n"));
805 806 807
	return -EINVAL;
}
#endif
808