changeset 0:17cb7cdbb8be draft default tip

Working prototype
author Ivo Smits <Ivo@UCIS.nl>
date Fri, 07 Feb 2014 23:28:39 +0100
parents
children
files include.h main.c protocol.c tunnel.c
diffstat 4 files changed, 781 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/include.h	Fri Feb 07 23:28:39 2014 +0100
@@ -0,0 +1,90 @@
+/* Copyright 2014 Ivo Smits <Ivo@UCIS.nl>. All rights reserved.
+   Redistribution and use in source and binary forms, with or without modification, are
+   permitted provided that the following conditions are met:
+
+   1. Redistributions of source code must retain the above copyright notice, this list of
+      conditions and the following disclaimer.
+
+   2. Redistributions in binary form must reproduce the above copyright notice, this list
+      of conditions and the following disclaimer in the documentation and/or other materials
+      provided with the distribution.
+
+   THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+   WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+   FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR
+   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+   CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+   SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+   ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+   ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+   The views and conclusions contained in the software and documentation are those of the
+   authors and should not be interpreted as representing official policies, either expressed
+   or implied, of Ivo Smits.*/
+
+#include <stdbool.h>
+
+typedef struct tunnel_context tunnel_context;
+typedef struct connection_context connection_context;
+
+struct connection_context {
+	int recv_socket;
+	unsigned char recv_buffer[2000];
+	int recv_offset;
+	bool recv_encrypted;
+	unsigned char recv_key[32];
+	unsigned char recv_nonce[24];
+
+	int send_socket;
+	bool send_encrypted;
+	unsigned char send_key[32];
+	unsigned char send_nonce[24];
+
+	unsigned char local_seckey_current[32];
+	unsigned char local_seckey_next[32];
+	unsigned char remote_pubkey[32];
+	unsigned char nonce_next[24];
+
+	char* password;
+
+	bool local_tunnelready;
+	bool remote_tunnelready;
+	bool key_updated;
+
+	bool pong;
+	bool startcryptauthsent;
+
+	unsigned char remote_pubkey_expect[32];
+	bool require_key_authentication;
+	bool require_encryption;
+	bool require_password_authentication;
+
+	tunnel_context* tunnel;
+};
+
+bool connection_init(connection_context* context);
+bool connection_init_socket(connection_context* context, const int recvsocket, const int sendsocket);
+bool connection_init_encryption(connection_context* context, const unsigned char* localseckey, const unsigned char* remotepubkey);
+bool connection_init_passwordauth(connection_context* context, char* password);
+bool connection_init_done(connection_context* context);
+bool connection_update_key(connection_context* context);
+bool connection_ping(connection_context* context);
+bool connection_read(connection_context* context);
+bool connection_write_data(connection_context* context, unsigned char* buffer, int len);
+
+struct tunnel_context {
+	int fd;
+	int fake_pi;
+	connection_context* connection;
+};
+
+bool tunnel_init(tunnel_context* context);
+bool tunnel_read(tunnel_context* context);
+bool tunnel_write_data(tunnel_context* tunnel, unsigned char* buffer, int len);
+
+extern char* (*getconf)(const char*);
+int errorexit(const char* text);
+int errorexitf(const char* text, const char* error);
+bool errorexitp(const char* text);
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main.c	Fri Feb 07 23:28:39 2014 +0100
@@ -0,0 +1,222 @@
+/* Copyright 2014 Ivo Smits <Ivo@UCIS.nl>. All rights reserved.
+   Redistribution and use in source and binary forms, with or without modification, are
+   permitted provided that the following conditions are met:
+
+   1. Redistributions of source code must retain the above copyright notice, this list of
+      conditions and the following disclaimer.
+
+   2. Redistributions in binary form must reproduce the above copyright notice, this list
+      of conditions and the following disclaimer in the documentation and/or other materials
+      provided with the distribution.
+
+   THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+   WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+   FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR
+   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+   CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+   SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+   ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+   ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+   The views and conclusions contained in the software and documentation are those of the
+   authors and should not be interpreted as representing official policies, either expressed
+   or implied, of Ivo Smits.*/
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/types.h>
+#include <poll.h>
+#include <sys/socket.h>
+#ifndef HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif
+#include <arpa/inet.h>
+#include <netdb.h>
+#include "include.h"
+
+char* (*getconf)(const char*) = getenv;
+
+static void hex2bin(unsigned char* dest, const char* src, const int count) {
+	int i;
+	for (i = 0; i < count; i++) {
+		if (*src >= '0' && *src <= '9') *dest = *src - '0';
+		else if (*src >= 'a' && * src <='f') *dest = *src - 'a' + 10;
+		else if (*src >= 'A' && * src <='F') *dest = *src - 'A' + 10;
+		src++; *dest = *dest << 4;
+		if (*src >= '0' && *src <= '9') *dest += *src - '0';
+		else if (*src >= 'a' && *src <= 'f') *dest += *src - 'a' + 10;
+		else if (*src >= 'A' && *src <= 'F') *dest += *src - 'A' + 10;
+		src++; dest++;
+	}
+}
+
+static bool crypto_init(connection_context* context, bool* keyupdate) {
+	unsigned char cpublickey[32], csecretkey[32];
+	bool hpublickey = false, hsecretkey = false;
+	char* envval;
+	*keyupdate = false;
+	if ((envval = getconf("PUBLIC_KEY"))) {
+		if (strlen(envval) != 64) return errorexit("PUBLIC_KEY length");
+		hex2bin(cpublickey, envval, 32);
+		hpublickey = true;
+	}
+	if ((envval = getconf("PRIVATE_KEY"))) {
+		if (strlen(envval) != 64) return errorexit("PRIVATE_KEY length");
+		hex2bin(csecretkey, envval, 32);
+		hsecretkey = true;
+	} else if ((envval = getconf("PRIVATE_KEY_FILE"))) {
+		FILE* pkfile = fopen(envval, "rb");
+		if (!pkfile) return errorexitp("Could not open PRIVATE_KEY_FILE");
+		char pktextbuf[64];
+		const size_t pktextsize = fread(pktextbuf, 1, sizeof(pktextbuf), pkfile);
+		if (pktextsize == 32) {
+			memcpy(csecretkey, pktextbuf, 32);
+		} else if (pktextsize == 64) {
+			hex2bin(csecretkey, pktextbuf, 32);
+		} else {
+			return errorexit("PRIVATE_KEY length");
+		}
+		fclose(pkfile);
+		hsecretkey = true;
+	}
+	if (hpublickey || hsecretkey || ((envval = getconf("ENCRYPT")) && atoi(envval))) {
+		if (!hpublickey) fprintf(stderr, "Warning: encryption enabled but remote key not set, cryptographic authentication disabled.\n");
+		if (!connection_init_encryption(context, hsecretkey ? csecretkey : NULL, hpublickey ? cpublickey : NULL)) return false;
+		*keyupdate = true;
+	} else {
+		fprintf(stderr, "Warning: encryption disabled.\n");
+	}
+	if ((envval = getconf("PASSWORD"))) {
+		if (!connection_init_passwordauth(context, strdup(envval))) return false;
+	}
+	return true;
+}
+
+typedef union {
+	struct sockaddr any;
+	struct sockaddr_in ip4;
+	struct sockaddr_in6 ip6;
+} sockaddr_any;
+
+static int sockaddr_set_port(sockaddr_any* sa, int port) {
+	port = htons(port);
+	int af = sa->any.sa_family;
+	if (af == AF_INET) sa->ip4.sin_port = port;
+	else if (af == AF_INET6) sa->ip6.sin6_port = port;
+	else return errorexit("Unknown address family");
+	return 0;
+}
+
+static bool socket_init(connection_context* context) {
+	char* envval;
+	fprintf(stderr, "Initializing socket...\n");
+	struct addrinfo *ai_local = NULL, *ai_remote = NULL;
+	unsigned short af = 0;
+	int ret;
+	if ((envval = getconf("LOCAL_ADDRESS"))) {
+		if ((ret = getaddrinfo(envval, NULL, NULL, &ai_local))) return errorexitf("getaddrinfo(LOCAL_ADDRESS)", gai_strerror(ret));
+		if (!ai_local) return errorexit("LOCAL_ADDRESS lookup failed");
+		if (ai_local->ai_addrlen > sizeof(sockaddr_any)) return errorexit("Resolved LOCAL_ADDRESS is too big");
+		af = ai_local->ai_family;
+	}
+	if ((envval = getconf("REMOTE_ADDRESS"))) {
+		if ((ret = getaddrinfo(envval, NULL, NULL, &ai_remote))) return errorexitf("getaddrinfo(REMOTE_ADDRESS)", gai_strerror(ret));
+		if (!ai_remote) return errorexit("REMOTE_ADDRESS lookup failed");
+		if (ai_remote->ai_addrlen > sizeof(sockaddr_any)) return errorexit("Resolved REMOTE_ADDRESS is too big");
+		if (af && af != ai_remote->ai_family) return errorexit("Address families do not match");
+		af = ai_remote->ai_family;
+	}
+	if (!af) return connection_init_socket(context, 0, 1);
+	int sa_size = sizeof(sockaddr_any);
+	if (af == AF_INET) sa_size = sizeof(struct sockaddr_in);
+	else if (af == AF_INET6) sa_size = sizeof(struct sockaddr_in6);
+	int sfd = socket(af, SOCK_STREAM, IPPROTO_TCP);
+	if (sfd < 0) return errorexitp("Could not create socket");
+	sockaddr_any udpaddr;
+	if (ai_local) {
+		memset(&udpaddr, 0, sizeof(udpaddr));
+		udpaddr.any.sa_family = af;
+		memcpy(&udpaddr, ai_local->ai_addr, ai_local->ai_addrlen);
+		int port = 2998;
+		if ((envval = getconf("LOCAL_PORT"))) port = atoi(envval);
+		if (sockaddr_set_port(&udpaddr, port)) return -1;
+		if (bind(sfd, &udpaddr.any, sa_size)) return errorexitp("Could not bind socket");
+	}
+	if (ai_remote) {
+		memset(&udpaddr, 0, sizeof(udpaddr));
+		udpaddr.any.sa_family = af;
+		memcpy(&udpaddr, ai_remote->ai_addr, ai_remote->ai_addrlen);
+		int port = 2998;
+		if ((envval = getconf("REMOTE_PORT"))) port = atoi(envval);
+		if (sockaddr_set_port(&udpaddr, port)) return -1;
+		if (connect(sfd, &udpaddr.any, sa_size)) return errorexitp("Could not connect socket");
+	} else {
+		return errorexit("REMOTE_ADDRESS not specified and server mode is currently not supported :-(. Please use (x)inetd or similar.");
+	}
+	if (ai_local) freeaddrinfo(ai_local);
+	if (ai_remote) freeaddrinfo(ai_remote);
+	return connection_init_socket(context, sfd, sfd);
+}
+
+static bool mainA() {
+	connection_context context;
+	tunnel_context tunnel;
+	bool keyupdate = false;
+	if (!connection_init(&context)) return false;
+	if (!socket_init(&context)) return false;
+	if (!crypto_init(&context, &keyupdate)) return false;
+	if (!connection_init_done(&context)) return false;
+	while (!context.local_tunnelready) if (!connection_read(&context)) return false;
+	if (keyupdate) connection_update_key(&context);
+
+	if (!tunnel_init(&tunnel)) return false;
+	context.tunnel = &tunnel;
+	tunnel.connection = &context;
+
+	struct pollfd fds[2];
+	fds[0].fd = context.recv_socket;
+	fds[0].events = POLLIN;
+	fds[1].fd = tunnel.fd;
+	fds[1].events = POLLIN;
+
+	while (true) {
+		int len = poll(fds, 2, 10000);
+		if (len < 0) return errorexitp("poll failed");
+		else if (fds[0].revents & (POLLERR | POLLHUP | POLLNVAL)) return errorexit("poll error on socket");
+		else if (fds[1].revents & (POLLHUP | POLLNVAL)) return errorexit("poll error on tap device");
+		if (len == 0) {
+			if (keyupdate) {
+				if (!context.key_updated) return errorexit("key update timed out");
+				if (!connection_update_key(&context)) return false;
+			} else {
+				if (!connection_ping(&context)) return errorexit("ping timed out");
+			}
+		}
+		if (fds[0].revents & POLLERR) return errorexitp("poll error on socket");
+		if (fds[0].revents & POLLIN) if (!connection_read(&context)) return false;
+		if (fds[1].revents & POLLIN) if (!tunnel_read(&tunnel)) return false;
+	}
+	return -1;
+}
+
+int main() {
+	return mainA() ? 0 : -1;
+}
+
+int errorexit(const char* text) {
+	fprintf(stderr, "%s\n", text);
+	return false;
+}
+int errorexitf(const char* text, const char* error) {
+	fprintf(stderr, "%s: %s\n", text, error);
+	return false;
+}
+bool errorexitp(const char* text) {
+	perror(text);
+	return false;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/protocol.c	Fri Feb 07 23:28:39 2014 +0100
@@ -0,0 +1,335 @@
+/* Copyright 2014 Ivo Smits <Ivo@UCIS.nl>. All rights reserved.
+   Redistribution and use in source and binary forms, with or without modification, are
+   permitted provided that the following conditions are met:
+
+   1. Redistributions of source code must retain the above copyright notice, this list of
+      conditions and the following disclaimer.
+
+   2. Redistributions in binary form must reproduce the above copyright notice, this list
+      of conditions and the following disclaimer in the documentation and/or other materials
+      provided with the distribution.
+
+   THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+   WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+   FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR
+   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+   CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+   SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+   ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+   ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+   The views and conclusions contained in the software and documentation are those of the
+   authors and should not be interpreted as representing official policies, either expressed
+   or implied, of Ivo Smits.*/
+
+#include <stdio.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sodium/crypto_box_curve25519xsalsa20poly1305.h>
+#include <sodium/crypto_scalarmult_curve25519.h>
+#include <sodium/crypto_hash_sha512.h>
+#include "include.h"
+
+static unsigned char workbuffer[2000];
+
+static int devurandomfd = -1;
+static bool randombytes(unsigned char* buffer, int len) {
+	if (devurandomfd == -1) devurandomfd = open("/dev/urandom", O_RDONLY);
+	if (devurandomfd == -1) return errorexit("could not open /dev/urandom");
+	while (len > 0) {
+		int got = read(devurandomfd, buffer, len);
+		if (got < 0) return errorexitp("could not read from /dev/urandom");
+		buffer += got;
+		len -= got;
+	}
+	return true;
+}
+
+static void dumphex(char* lbl, unsigned char* buffer, int len) {
+	fprintf(stderr, "%s: ", lbl);
+	for (; len > 0; len--, buffer++) fprintf(stderr, "%02x", *buffer);
+	fprintf(stderr, "\n");
+}
+
+static void nonceinc(unsigned char* nonce) {
+	int i;
+	for (i = 23; i >= 0 && (++nonce[i] == 0); i--);
+}
+
+static bool send_all(int socket, unsigned char* buffer, int length) {
+	while (length) {
+		int sent = write(socket, buffer, length);
+		if (sent <= 0) return errorexitp("socket write failed");
+		buffer += sent;
+		length -= sent;
+	}
+	return true;
+}
+static bool send_packet(connection_context* context, unsigned char* buffer, int length, bool controlflag) {
+	fprintf(stderr, "Send packet (size=%d type=%d control=%d encrypt=%d)\n", length, buffer[0], controlflag, context->send_encrypted);
+	if (context->send_encrypted) {
+		if (length + 32 > sizeof(workbuffer)) return errorexit("packet too big for encryption buffer");
+		memmove(workbuffer + 32, buffer, length);
+		memset(workbuffer, 0, 32);
+		crypto_box_curve25519xsalsa20poly1305_afternm(workbuffer, workbuffer, length + 32, context->send_nonce, context->send_key);
+		nonceinc(context->send_nonce);
+		buffer = workbuffer + 16;
+		length += 16;
+	}
+	if (length > 0x7FFF) return errorexit("packet too big for framing");
+	unsigned char lbuf[2];
+	length = (length & 0x7FFF) | (controlflag ? 0x8000 : 0);
+	lbuf[0] = (length >> 8) & 0xFF;
+	lbuf[1] = length & 0xFF;
+	if (!send_all(context->send_socket, lbuf, 2)) return false;
+	if (!send_all(context->send_socket, buffer, length & 0x7FFF)) return false;
+	return true;
+}
+static bool send_command_ack(connection_context* context, unsigned char code) {
+	workbuffer[0] = code;
+	return send_packet(context, workbuffer, 1, true);
+}
+
+
+static bool check_tunnel_ready(connection_context* context) {
+	if (context->local_tunnelready) return true;
+	if (context->require_key_authentication) return true;
+	if (context->require_password_authentication) return true;
+	if (context->require_encryption && !context->recv_encrypted) return true;
+	if (!send_command_ack(context, 0)) return false;
+	context->local_tunnelready = true;
+	return true;
+}
+
+static bool process_network_packet(connection_context* context, unsigned char* buffer, int pktlen, int controlflag);
+bool connection_read(connection_context* context) {
+	int got = read(context->recv_socket, context->recv_buffer + context->recv_offset, sizeof(context->recv_buffer) - context->recv_offset);
+	if (got < 0) return errorexitp("read failure on socket");
+	context->recv_offset += got;
+	while (context->recv_offset >= 2) {
+		int pktlen = ((unsigned char)context->recv_buffer[0] << 8) | (unsigned char)context->recv_buffer[1];
+		int controlflag = pktlen & 0x8000;
+		pktlen &= 0x7FFF;
+		if (pktlen > sizeof(context->recv_buffer)) return errorexit("received packet too big for buffer");
+		if (context->recv_offset < pktlen + 2) break;
+		if (!process_network_packet(context, context->recv_buffer + 2, pktlen, !!controlflag)) return false;
+		context->recv_offset -= 2 + pktlen;
+		memmove(context->recv_buffer, context->recv_buffer + 2 + pktlen, context->recv_offset);
+	}
+	if (!context->local_tunnelready) if (!check_tunnel_ready(context)) return false;
+	return true;
+}
+static bool send_start_crypt_auth(connection_context* context, const unsigned char* key) {
+	if (!context->key_updated) return context->startcryptauthsent && !key;
+	if (key) {
+		memcpy(context->local_seckey_next, key, 32);
+	} else {
+		randombytes(context->local_seckey_next, 32);
+	}
+	workbuffer[0] = 1;
+	crypto_scalarmult_curve25519_base(workbuffer + 1, context->local_seckey_next);
+	memcpy(workbuffer + 1 + 32, context->nonce_next, 24);
+	if (!send_packet(context, workbuffer, 1 + 32 + 24, true)) return false;
+	crypto_box_curve25519xsalsa20poly1305_beforenm(context->send_key, context->remote_pubkey, context->local_seckey_next);
+	memcpy(context->send_nonce + 12, context->nonce_next + 12, 12);
+	context->send_encrypted = true;
+	context->startcryptauthsent = true;
+	context->key_updated = false;
+	return true;
+}
+static bool begin_update_key(connection_context* context, char* seckey) {
+	if (!context->key_updated) return !seckey;
+	if (seckey) {
+		memcpy(context->local_seckey_next, seckey, 32);
+	} else {
+		randombytes(context->local_seckey_next, 32);
+	}
+	workbuffer[0] = 3;
+	crypto_scalarmult_curve25519_base(workbuffer + 1, context->local_seckey_next);
+	if (!send_packet(context, workbuffer, 1 + 32, true)) return false;
+	crypto_box_curve25519xsalsa20poly1305_beforenm(context->send_key, context->remote_pubkey, context->local_seckey_next);
+	context->send_encrypted = true;
+	context->key_updated = false;
+	return true;
+}
+static bool password_hash(unsigned char* output, unsigned char* salt, int saltlength, char* password) {
+	int pwlen = password ? strlen(password) : 0;
+	if (saltlength + pwlen > sizeof(workbuffer)) return errorexit("password and salt too big for buffer");
+	if (saltlength) memmove(workbuffer, salt, saltlength);
+	if (pwlen) memcpy(workbuffer + saltlength, password, pwlen);
+	crypto_hash_sha512(output, workbuffer, saltlength + pwlen);
+	return true;
+}
+static bool process_network_packet(connection_context* context, unsigned char* buffer, int pktlen, int controlflag) {
+	if (context->recv_encrypted) {
+		if (pktlen + 16 > sizeof(workbuffer)) return errorexit("received packet too big for decrypt buffer");
+		memmove(workbuffer + 16, buffer, pktlen);
+		memset(workbuffer, 0, 16);
+		if (crypto_box_curve25519xsalsa20poly1305_open_afternm(workbuffer, workbuffer, pktlen + 16, context->recv_nonce, context->recv_key)) return false;
+		nonceinc(context->recv_nonce);
+		buffer = workbuffer + 32;
+		pktlen -= 16;
+	}
+	if (controlflag) {
+		fprintf(stderr, "Received packet (size=%d type=%d control=%d encrypt=%d)\n", pktlen, buffer[0], controlflag, context->recv_encrypted);
+		if (pktlen < 1) return errorexit("zero length control packet");
+		switch (buffer[0]) {
+			case 0:
+				fprintf(stderr, "Control: tunnel ready\n");
+				context->remote_tunnelready = true;
+				break;
+			case 1:
+				fprintf(stderr, "Control: crypto auth req\n");
+				if (pktlen < 1 + 32 + 24) return errorexit("short control packet 1");
+				if (context->require_key_authentication) {
+					if (memcmp(buffer + 1, context->remote_pubkey_expect, 32)) return errorexit("incorrect crypto auth key");
+					context->require_key_authentication = true;
+				}
+				if (!context->startcryptauthsent) send_start_crypt_auth(context, NULL);
+				if (!send_command_ack(context, 2)) return false;
+				memcpy(context->remote_pubkey, buffer + 1, 32);
+				memcpy(context->send_nonce, buffer + 1 + 32, 12);
+				memcpy(context->recv_nonce + 12, buffer + 1 + 32 + 12, 12);
+				crypto_box_curve25519xsalsa20poly1305_beforenm(context->recv_key, context->remote_pubkey, context->local_seckey_current);
+				crypto_box_curve25519xsalsa20poly1305_beforenm(context->send_key, context->remote_pubkey, context->local_seckey_next);
+				context->recv_encrypted = context->send_encrypted = true;
+				break;
+			case 2:
+				fprintf(stderr, "Control: crypto auth ack\n");
+				memcpy(context->local_seckey_current, context->local_seckey_next, 32);
+				memcpy(context->recv_nonce, context->nonce_next, 12);
+				crypto_box_curve25519xsalsa20poly1305_beforenm(context->recv_key, context->remote_pubkey, context->local_seckey_current);
+				context->recv_encrypted = true;
+				context->key_updated = true;
+				begin_update_key(context, NULL);
+				break;
+			case 3:
+				fprintf(stderr, "Control: key update req\n");
+				if (pktlen < 1 + 32) return errorexit("short control packet 3");
+				if (!send_command_ack(context, 4)) return false;
+				memcpy(context->remote_pubkey, buffer + 1, 32);
+				crypto_box_curve25519xsalsa20poly1305_beforenm(context->recv_key, context->remote_pubkey, context->local_seckey_current);
+				crypto_box_curve25519xsalsa20poly1305_beforenm(context->send_key, context->remote_pubkey, context->local_seckey_next);
+				context->recv_encrypted = context->send_encrypted = true;
+				break;
+			case 4:
+				fprintf(stderr, "Control: key update ack\n");
+				memcpy(context->local_seckey_current, context->local_seckey_next, 32);
+				crypto_box_curve25519xsalsa20poly1305_beforenm(context->recv_key, context->remote_pubkey, context->local_seckey_current);
+				context->recv_encrypted = true;
+				context->key_updated = true;
+				break;
+			case 5:
+				fprintf(stderr, "Control: password authentication req\n");
+				unsigned char pwhash[64];
+				if (!password_hash(pwhash, buffer + 1, pktlen - 1, context->password)) return false;
+				workbuffer[0] = 6;
+				memcpy(workbuffer + 1, pwhash, 64);
+				if (!send_packet(context, workbuffer, 1 + 64, true)) return false;
+				break;
+			case 6:
+				fprintf(stderr, "Control: password authentication ack\n");
+				if (pktlen < 1 + 64) return errorexit("short control packet 6");;
+				char pwhashcheck[64];
+				memcpy(pwhashcheck, buffer + 1, 64);
+				if (!password_hash(pwhash, context->nonce_next, 12, context->password)) return false;
+				if (memcmp(pwhash, pwhashcheck, 64)) return errorexit("incorrect password auth");;
+				context->require_password_authentication = false;
+				break;
+			case 7:
+				fprintf(stderr, "Control: disable encryption req\n");
+				context->recv_encrypted = context->send_encrypted = false;
+				if (!send_command_ack(context, 8)) return false;
+				break;
+			case 8:
+				fprintf(stderr, "Control: disable encryption ack\n");
+				context->recv_encrypted = false;
+				break;
+			case 9:
+				fprintf(stderr, "Control: echo req\n");
+				if (pktlen > sizeof(workbuffer)) return errorexit("echo request too big");;
+				memmove(workbuffer, buffer, pktlen);
+				workbuffer[0] = 10;
+				if (!send_packet(context, workbuffer, pktlen, true)) return false;
+				break;
+			case 10:
+				fprintf(stderr, "Control: echo resp\n");
+				context->pong = true;
+				break;
+			case 13:
+				if (!context->local_tunnelready || !context->remote_tunnelready) return errorexit("received data packet while tunnel not ready");;
+				if (context->tunnel && !tunnel_write_data(context->tunnel, buffer + 1, pktlen - 1)) return false;
+				break;
+			case 11:
+			case 12:
+			case 17:
+			case 81:
+				break;
+			default:
+				fprintf(stderr, "Unknown control type %d\n", buffer[0]);
+				break;
+		}
+	} else {
+		if (!context->local_tunnelready || !context->remote_tunnelready) return errorexit("received data packet while tunnel not ready");;
+		fprintf(stderr, "Tunnel data %d\n", pktlen);
+		if (context->tunnel && !tunnel_write_data(context->tunnel, buffer, pktlen)) return false;
+	}
+	return true;
+}
+
+bool connection_ping(connection_context* context) {
+	if (!context->pong) return false;
+	context->pong = false;
+	send_command_ack(context, 9);
+	return true;
+}
+
+bool connection_init(connection_context* context) {
+	memset(context, 0, sizeof(connection_context));
+	char localpubkey[32];
+	crypto_scalarmult_curve25519_base(context->remote_pubkey, context->local_seckey_current);
+	randombytes(context->nonce_next, 24);
+	context->key_updated = true;
+	context->pong = true;
+	return true;
+}
+bool connection_init_socket(connection_context* context, const int recvsocket, const int sendsocket) {
+	context->recv_socket = recvsocket;
+	context->send_socket = sendsocket;
+	if (!send_packet(context, (unsigned char*)"QUICKTUN", 8, true)) return false;
+	return true;
+}
+bool connection_init_encryption(connection_context* context, const unsigned char* localseckey, const unsigned char* remotepubkey) {
+	if (remotepubkey) {
+		memcpy(context->remote_pubkey_expect, remotepubkey, 32);
+		context->require_key_authentication = true;
+	}
+	context->require_encryption = true;
+	if (!send_start_crypt_auth(context, localseckey)) return false;
+	return true;
+}
+bool connection_init_passwordauth(connection_context* context, char* password) {
+	context->password = password;
+	context->require_password_authentication = true;
+	workbuffer[0] = 5;
+	memcpy(workbuffer + 1, context->nonce_next, 12);
+	if (!send_packet(context, workbuffer, 1 + 12, true)) return false;
+	return true;
+}
+
+bool connection_write_data(connection_context* context, unsigned char* buffer, int len) {
+	return send_packet(context, buffer, len, false);
+}
+
+bool connection_init_done(connection_context* context) {
+	return check_tunnel_ready(context);
+}
+
+bool connection_update_key(connection_context* context) {
+	return begin_update_key(context, NULL);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tunnel.c	Fri Feb 07 23:28:39 2014 +0100
@@ -0,0 +1,134 @@
+/* Copyright 2014 Ivo Smits <Ivo@UCIS.nl>. All rights reserved.
+   Redistribution and use in source and binary forms, with or without modification, are
+   permitted provided that the following conditions are met:
+
+   1. Redistributions of source code must retain the above copyright notice, this list of
+      conditions and the following disclaimer.
+
+   2. Redistributions in binary form must reproduce the above copyright notice, this list
+      of conditions and the following disclaimer in the documentation and/or other materials
+      provided with the distribution.
+
+   THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+   WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+   FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR
+   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+   CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+   SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+   ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+   ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+   The views and conclusions contained in the software and documentation are those of the
+   authors and should not be interpreted as representing official policies, either expressed
+   or implied, of Ivo Smits.*/
+
+#include <stdio.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <net/if.h>
+#ifdef linux
+	#include <linux/if_tun.h>
+	#include <linux/if_ether.h>
+#else
+	#define ETH_FRAME_LEN 1514
+	#include <net/if_tun.h>
+	#ifdef SOLARIS
+		#include <sys/stropts.h>
+		#include <sys/sockio.h>
+	#endif
+#endif
+#include "include.h"
+
+static unsigned char readbuffer[2000];
+
+bool tunnel_write_data(tunnel_context* context, unsigned char* buffer, int len) {
+	if (context->fake_pi) {
+		if (len == 0) return true;
+		int ipver = (*buffer >> 4) & 0xf;
+		int pihdr = 0;
+#if defined linux
+		if (ipver == 4) pihdr = 0x0000 | (0x0008 << 16); //little endian: flags and protocol are swapped
+		else if (ipver == 6) pihdr = 0x0000 | (0xdd86 << 16);
+#else
+		if (ipver == 4) pihdr = htonl(AF_INET);
+		else if (ipver == 6) pihdr = htonl(AF_INET6);
+#endif
+		struct iovec iov[2];
+		iov[0].iov_base = &pihdr;
+		iov[0].iov_len = sizeof(pihdr);
+		iov[1].iov_base = buffer;
+		iov[1].iov_len = len;
+		writev(context->fd, iov, 2);
+	} else {
+		write(context->fd, buffer, len);
+	}
+	return true;
+}
+
+bool tunnel_read(tunnel_context* context) {
+	int len = read(context->fd, readbuffer, sizeof(readbuffer));
+	if (len < 0) return errorexitp("read failure on tap device");
+	if (!context->connection) return true;
+	if (context->fake_pi) {
+		if (len < 4) return errorexit("short packet received from tap device");
+		connection_write_data(context->connection, readbuffer + 4, len - 4);
+	} else {
+		connection_write_data(context->connection, readbuffer, len);
+	}
+	return true;
+}
+
+bool tunnel_init(tunnel_context* context) {
+	memset(context, 0, sizeof(tunnel_context));
+	char* envval;
+	fprintf(stderr, "Initializing tun/tap device...\n");
+	int ttfd; //Tap device file descriptor
+	int tunmode = 0;
+	if ((envval = getconf("TUN_MODE"))) tunmode = atoi(envval);
+#if defined(__linux__)
+	struct ifreq ifr; //required for tun/tap setup
+	memset(&ifr, 0, sizeof(ifr));
+	if ((ttfd = open("/dev/net/tun", O_RDWR)) < 0) return errorexitp("Could not open tun/tap device file");
+	if ((envval = getconf("INTERFACE"))) strcpy(ifr.ifr_name, envval);
+	ifr.ifr_flags = tunmode ? IFF_TUN : IFF_TAP;
+	ifr.ifr_flags |= IFF_NO_PI;
+	if (ioctl(ttfd, TUNSETIFF, (void *)&ifr) < 0) return errorexitp("TUNSETIFF ioctl failed");
+#elif defined SOLARIS
+	int ip_fd = -1, if_fd = -1, ppa = 0;
+	if ((ttfd = open("/dev/tun", O_RDWR)) < 0) return errorexitp("Could not open tun device file");
+	if ((ip_fd = open("/dev/ip", O_RDWR, 0)) < 0) return errorexitp("Could not open /dev/ip");
+	if ((envval = getconf("INTERFACE"))) {
+		while (*envval && !isdigit((int)*envval)) envval++;
+		ppa = atoi(envval);
+	}
+	if ((ppa = ioctl(ttfd, TUNNEWPPA, ppa)) < 0) return errorexitp("Could not assign new PPA");
+	if ((if_fd = open("/dev/tun", O_RDWR, 0)) < 0) return errorexitp("Could not open tun device file again");
+	if (ioctl(if_fd, I_PUSH, "ip") < 0) return errorexitp("Could not push IP module");
+	if (ioctl(if_fd, IF_UNITSEL, (char *)&ppa) < 0) return errorexitp("Could not set PPA");
+	if (ioctl(ip_fd, I_LINK, if_fd) < 0) return errorexitp("Could not link TUN device to IP");
+#else
+	if (!(envval = getconf("INTERFACE"))) envval = "/dev/tun0";
+	if ((ttfd = open(envval, O_RDWR)) < 0) return errorexitp("Could not open tun device file");
+	if (tunmode) {
+		int i = IFF_POINTOPOINT | IFF_MULTICAST;
+		ioctl(ttfd, TUNSIFMODE, &i);
+#if defined(__OpenBSD__)
+		context->fake_pi = true;
+#else
+		i = 1;
+		ioctl(ttfd, TUNSIFHEAD, &i);
+#endif
+	}
+#endif
+	if ((envval = getconf("TUN_UP_SCRIPT"))) system(envval);
+	context->fd = ttfd;
+	return true;
+}
+