summary refs log tree commit diff stats
path: root/libotr/libotr-4.1.1/tests/regression/client/client.c
diff options
context:
space:
mode:
Diffstat (limited to 'libotr/libotr-4.1.1/tests/regression/client/client.c')
-rw-r--r--libotr/libotr-4.1.1/tests/regression/client/client.c1158
1 files changed, 1158 insertions, 0 deletions
diff --git a/libotr/libotr-4.1.1/tests/regression/client/client.c b/libotr/libotr-4.1.1/tests/regression/client/client.c
new file mode 100644
index 0000000..e72b661
--- /dev/null
+++ b/libotr/libotr-4.1.1/tests/regression/client/client.c
@@ -0,0 +1,1158 @@
+/*
+ * Copyright (C) 2014 - David Goulet <dgoulet@ev0ke.net>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License, version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 51
+ * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#define _GNU_SOURCE
+#include <assert.h>
+#include <ctype.h>
+#include <gcrypt.h>
+#include <getopt.h>
+#include <inttypes.h>
+#include <pthread.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <syscall.h>
+#include <sys/epoll.h>
+#include <sys/types.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+#include <context.h>
+#include <privkey.h>
+#include <proto.h>
+#include <message.h>
+
+#include <tap/tap.h>
+
+#define zmalloc(x) calloc(1, x)
+
+GCRY_THREAD_OPTION_PTHREAD_IMPL;
+
+/* Global OTR user state. */
+static OtrlUserState user_state;
+
+/* Getopt options. */
+static struct option long_opts[] = {
+	{ "load-instag", 1, NULL, 'i' },
+	{ "load-key",    1, NULL, 'k' },
+	{ "load-fp",     1, NULL, 'f' },
+	{ "timeout",     1, NULL, 't' },
+	{ "max-msg",     1, NULL, 'm' },
+	{ "disconnect",  0, NULL, 'd' },
+	{ "auth",        0, NULL, 'a' },
+	{ "fragment",    0, NULL, 'F' },
+
+	/* Closure. */
+	{ NULL, 0, NULL, 0 }
+};
+
+static char *opt_instag_path;
+static char *opt_key_path;
+static char *opt_key_fp_path;
+static unsigned int opt_max_num_msg;
+static int opt_disconnect;
+static int opt_auth;
+static int opt_fragment;
+
+/* Currently, the message size sent is between 1 and 600 len so 100 is a good
+ * middle ground. */
+static const int fragment_size = 100;
+/* By default, don't send frag. */
+static OtrlFragmentPolicy fragPolicy = OTRL_FRAGMENT_SEND_SKIP;
+
+static const char *protocol = "otr-test";
+static const char *alice_name = "alice";
+static const char *bob_name = "bob";
+
+static const char *unix_sock_bob_path = "/tmp/otr-test-bob.sock";
+static const char *unix_sock_alice_path = "/tmp/otr-test-alice.sock";
+
+static const char *auth_question = "What is NSA?";
+static const char *auth_secret = "No Sugar Added";
+
+/* Alice and Bob thread's socket. */
+static int alice_sock;
+static int bob_sock;
+/* Declare it global because we use it multiple times. */
+static struct sockaddr_un alice_sun;
+static struct sockaddr_un bob_sun;
+
+static int timeout_max = 1000;
+static unsigned int num_recv_msg;
+static unsigned int session_disconnected;
+
+static int quit_pipe[2] =  { -1, -1 };
+
+/*
+ * For now not really do much more but if we want to use the OK condition at
+ * some point to do something else, that will ease our life :).
+ */
+#define OK(cond, fmt, args...)				   \
+	do {										 \
+		ok(cond, fmt, ## args);				  \
+	} while (0)
+
+/*
+ * Used to pass OTR message between threads. This contains the cipher and
+ * plaintext so we are able to validate what's expected in both threads.
+ */
+struct otr_msg {
+	size_t plaintext_len;
+	size_t ciphertext_len;
+	char *plaintext;
+	char *ciphertext;
+};
+
+struct otr_info {
+	const char *user;
+	int sock;
+	unsigned int gone_secure;
+	unsigned int auth_done;
+};
+
+/* Stub */
+static int send_otr_msg(int sock, const char *to, const char *from,
+		struct otr_info *oinfo, const char *message);
+
+static OtrlPolicy ops_policy(void *opdata, ConnContext *context)
+{
+	return OTRL_POLICY_DEFAULT;
+}
+
+static void ops_inject_msg(void *opdata, const char *accountname,
+		const char *protocol, const char *recipient, const char *message)
+{
+	ssize_t ret;
+	struct otr_info *oinfo = opdata;
+	struct otr_msg *msg;
+
+	msg = zmalloc(sizeof(*msg));
+	if (!msg) {
+		perror("zmalloc inject");
+		return;
+	}
+
+	msg->ciphertext = strdup(message);
+	msg->ciphertext_len = strlen(message);
+
+	ret = send(oinfo->sock, &msg, sizeof(msg), 0);
+	if (ret < 0) {
+		perror("send msg");
+	}
+}
+
+static void ops_gone_secure(void *opdata, ConnContext *context)
+{
+	struct otr_info *oinfo = opdata;
+
+	session_disconnected = 0;
+	oinfo->gone_secure = 1;
+	/* XXX: gone_insecure is never called ref bug #40 so this will always be
+	 * true. */
+	OK(oinfo->gone_secure, "Gone secure for %s",
+			oinfo->user);
+}
+
+static void ops_gone_insecure(void *opdata, ConnContext *context)
+{
+	struct otr_info *oinfo = opdata;
+
+	OK(oinfo->gone_secure, "Gone insecure for %s",
+			oinfo->user);
+	oinfo->gone_secure = 0;
+}
+
+static int ops_max_message_size(void *opdata, ConnContext *context)
+{
+	if (opt_fragment) {
+		return fragment_size;
+	}
+	return 0;
+}
+
+static const char *ops_otr_error_message(void *opdata, ConnContext *context,
+		OtrlErrorCode code)
+{
+	char *msg = NULL;
+
+	switch (code) {
+	case OTRL_ERRCODE_NONE:
+		break;
+	case OTRL_ERRCODE_ENCRYPTION_ERROR:
+		msg = strdup("OTRL_ERRCODE_ENCRYPTION_ERROR");
+		break;
+	case OTRL_ERRCODE_MSG_NOT_IN_PRIVATE:
+		msg = strdup("OTRL_ERRCODE_MSG_NOT_IN_PRIVATE");
+		break;
+	case OTRL_ERRCODE_MSG_UNREADABLE:
+		msg = strdup("OTRL_ERRCODE_MSG_UNREADABLE");
+		break;
+	case OTRL_ERRCODE_MSG_MALFORMED:
+		msg = strdup("OTRL_ERRCODE_MSG_MALFORMED");
+		break;
+	}
+
+	return msg;
+}
+
+static void ops_otr_error_message_free(void *opdata, const char *err_msg)
+{
+	free((char *) err_msg);
+}
+
+static void ops_handle_msg_event(void *opdata, OtrlMessageEvent msg_event,
+		ConnContext *context, const char *message, gcry_error_t err)
+{
+	//char* msg = "";
+	struct otr_info *oinfo = opdata;
+
+	switch(msg_event) {
+	case OTRL_MSGEVENT_NONE:
+		//msg = "OTRL_MSGEVENT_NONE";
+		break;
+	case OTRL_MSGEVENT_ENCRYPTION_REQUIRED:
+		//msg = "OTRL_MSGEVENT_ENCRYPTION_REQUIRED";
+		break;
+	case OTRL_MSGEVENT_ENCRYPTION_ERROR:
+		//msg = "OTRL_MSGEVENT_ENCRYPTION_ERROR";
+		break;
+	case OTRL_MSGEVENT_CONNECTION_ENDED:
+		//msg = "OTRL_MSGEVENT_CONNECTION_ENDED";
+		oinfo->gone_secure = 0;
+		break;
+	case OTRL_MSGEVENT_SETUP_ERROR:
+		//msg = "OTRL_MSGEVENT_SETUP_ERROR";
+		break;
+	case OTRL_MSGEVENT_MSG_REFLECTED:
+		//msg = "OTRL_MSGEVENT_MSG_REFLECTED";
+		break;
+	case OTRL_MSGEVENT_MSG_RESENT:
+		//msg = "OTRL_MSGEVENT_MSG_RESENT";
+		break;
+	case OTRL_MSGEVENT_RCVDMSG_NOT_IN_PRIVATE:
+		//msg = "OTRL_MSGEVENT_RCVDMSG_NOT_IN_PRIVATE";
+		break;
+	case OTRL_MSGEVENT_RCVDMSG_UNREADABLE:
+		//msg = "OTRL_MSGEVENT_RCVDMSG_UNREADABLE";
+		break;
+	case OTRL_MSGEVENT_RCVDMSG_MALFORMED:
+		//msg = "OTRL_MSGEVENT_RCVDMSG_MALFORMED";
+		break;
+	case OTRL_MSGEVENT_LOG_HEARTBEAT_RCVD:
+		//msg = "OTRL_MSGEVENT_LOG_HEARTBEAT_RCVD";
+		break;
+	case OTRL_MSGEVENT_LOG_HEARTBEAT_SENT:
+		//msg = "OTRL_MSGEVENT_LOG_HEARTBEAT_SENT";
+		break;
+	case OTRL_MSGEVENT_RCVDMSG_GENERAL_ERR:
+		//msg = "OTRL_MSGEVENT_RCVDMSG_GENERAL_ERR";
+		break;
+	case OTRL_MSGEVENT_RCVDMSG_UNENCRYPTED:
+		//msg = "OTRL_MSGEVENT_RCVDMSG_UNENCRYPTED";
+		break;
+	case OTRL_MSGEVENT_RCVDMSG_UNRECOGNIZED:
+		//msg = "OTRL_MSGEVENT_RCVDMSG_UNRECOGNIZED";
+		break;
+	case OTRL_MSGEVENT_RCVDMSG_FOR_OTHER_INSTANCE:
+		//msg = "OTRL_MSGEVENT_RCVDMSG_FOR_OTHER_INSTANCE";
+		break;
+	default:
+		//msg = "Unknown OTRL message event";
+		break;
+	}
+}
+
+static void ops_create_instag(void *opdata, const char *accountname,
+		const char *protocol)
+{
+	otrl_instag_generate(user_state, "/dev/null", accountname,
+			protocol);
+}
+
+static int ops_is_logged_in(void *opdata, const char *accountname,
+		const char *protocol, const char *recipient)
+{
+	/* Always logged in or else we don't receive a disconnected TLV. */
+	return 1;
+}
+
+static void ops_create_privkey(void *opdata, const char *accountname,
+		const char *protocol)
+{
+	/* XXX: We should gen. our own key each time at some point? */
+	return;
+}
+
+static void ops_update_context_list(void *opdata)
+{
+	return;
+}
+
+static void ops_new_fingerprint(void *opdata, OtrlUserState us,
+		const char *accountname, const char *protocol,
+		const char *username, unsigned char fingerprint[20])
+{
+	return;
+}
+
+static void ops_write_fingerprints(void *opdata)
+{
+	return;
+}
+
+static void ops_still_secure(void *opdata, ConnContext *context, int is_reply)
+{
+	struct otr_info *oinfo = opdata;
+
+	OK(oinfo->gone_secure, "OP still secure");
+}
+
+static void ops_received_symkey(void *opdata, ConnContext *context,
+		unsigned int use, const unsigned char *usedata,
+		size_t usedatalen, const unsigned char *symkey)
+{
+	return;
+}
+
+static const char *ops_resent_msg_prefix(void *opdata, ConnContext *context)
+{
+	/* Just so we can test resent_msg_prefix_free */
+	char *prefix = zmalloc(32);
+	strncpy(prefix, "[such resent]", 32);
+
+	return prefix;
+}
+
+static void ops_resent_msg_prefix_free(void *opdata, const char *prefix)
+{
+	free((char *) prefix);
+}
+
+static void ops_convert_msg(void *opdata, ConnContext *context,
+		OtrlConvertType convert_type, char ** dest, const char *src)
+{
+	switch (convert_type) {
+	case OTRL_CONVERT_SENDING:
+	case OTRL_CONVERT_RECEIVING:
+		break;
+	default:
+		OK(0, "OP convert_msg, got a unknown type %d", convert_type);
+		break;
+	}
+
+	*dest = NULL;
+}
+
+static void ops_convert_free(void *opdata, ConnContext *context, char *dest)
+{
+	return;
+}
+
+/* Stub */
+static void ops_handle_smp_event(void *opdata, OtrlSMPEvent smp_event,
+		ConnContext *context, unsigned short progress_percent, char *question);
+static void ops_timer_control(void *opdata, unsigned int interval);
+
+/* OTR message operations. */
+static OtrlMessageAppOps ops = {
+	ops_policy,
+	ops_create_privkey,
+	ops_is_logged_in,
+	ops_inject_msg,
+	ops_update_context_list,
+	ops_new_fingerprint,
+	ops_write_fingerprints,
+	ops_gone_secure,
+	ops_gone_insecure,
+	ops_still_secure,
+	ops_max_message_size,
+	NULL, /* account_name - NOT USED */
+	NULL, /* account_name_free - NOT USED */
+	ops_received_symkey,
+	ops_otr_error_message,
+	ops_otr_error_message_free,
+	ops_resent_msg_prefix,
+	ops_resent_msg_prefix_free,
+	ops_handle_smp_event,
+	ops_handle_msg_event,
+	ops_create_instag,
+	ops_convert_msg,
+	ops_convert_free,
+	ops_timer_control,
+};
+
+
+static void ops_timer_control(void *opdata, unsigned int interval)
+{
+	otrl_message_poll(user_state, &ops, NULL);
+}
+
+static void ops_handle_smp_event(void *opdata, OtrlSMPEvent smp_event,
+		ConnContext *context, unsigned short progress_percent, char *question)
+{
+	struct otr_info *oinfo = opdata;
+
+	switch (smp_event) {
+	case OTRL_SMPEVENT_ASK_FOR_SECRET:
+		OK(!oinfo->auth_done &&
+			!strncmp(oinfo->user, alice_name, strlen(alice_name)),
+			"SMP Event, %s asking for secret", alice_name);
+		break;
+	case OTRL_SMPEVENT_ASK_FOR_ANSWER:
+		OK(!oinfo->auth_done &&
+				!strncmp(oinfo->user, bob_name, strlen(bob_name)) &&
+				!strncmp(auth_question, question, strlen(auth_question)),
+				"SMP Event, %s asking for answer", bob_name);
+		/*
+		 * Directly respond to the SMP auth here. Much more easy instead of in
+		 * bob's thread.
+		 */
+		otrl_message_respond_smp(user_state, &ops, opdata, context,
+				(unsigned char *) auth_secret, strlen(auth_secret));
+		break;
+	case OTRL_SMPEVENT_IN_PROGRESS:
+		OK(!oinfo->auth_done &&
+				!strncmp(oinfo->user, alice_name, strlen(alice_name)),
+				"SMP Event, %s asking for secret", alice_name);
+		break;
+	case OTRL_SMPEVENT_SUCCESS:
+		oinfo->auth_done = 1;
+		OK(oinfo->auth_done, "SMP authentication success for %s", oinfo->user);
+		break;
+	case OTRL_SMPEVENT_ABORT:
+	case OTRL_SMPEVENT_FAILURE:
+	case OTRL_SMPEVENT_CHEATED:
+	case OTRL_SMPEVENT_ERROR:
+	default:
+		OK(0, "SMP auth failed with event %d", smp_event);
+		break;
+	}
+}
+
+static void cleanup(void)
+{
+	ssize_t ret;
+
+	/* Wake up threads. */
+	ret = write(quit_pipe[1], "42", 2);
+	if (ret < 0) {
+		perror("write quit pipe");
+	}
+
+	/* Cleanup residual Unix socket path. */
+	unlink(alice_sun.sun_path);
+	unlink(bob_sun.sun_path);
+}
+
+static void update_msg_counter(void)
+{
+	num_recv_msg++;
+	if (num_recv_msg == opt_max_num_msg) {
+		cleanup();
+	}
+}
+
+/*
+ * Generate random string and stores it in out of size len.
+ */
+static void gen_random_string(char *out, size_t len)
+{
+	size_t i;
+	static const char alphanum[] =
+		"0123456789"
+		"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+		"abcdefghijklmnopqrstuvwxyz";
+
+	for (i = 0; i < len; i++) {
+		out[i] = alphanum[rand() % (sizeof(alphanum) - 1)];
+	}
+	out[len - 1] = '\0';
+}
+
+static int send_otr_msg(int sock, const char *to, const char *from,
+		struct otr_info *oinfo, const char *message)
+{
+	char *new_msg = NULL;
+	ssize_t ret;
+	gcry_error_t err;
+	struct otr_msg *omsg;
+
+	omsg = zmalloc(sizeof(*omsg));
+	if (!omsg) {
+		perror("zmalloc send otr msg");
+		goto error;
+	}
+
+	if (!message) {
+		size_t len = rand() % 600;
+		char *msg = zmalloc(len);
+		if (!msg) {
+			perror("random msg");
+			goto error;
+		}
+		gen_random_string(msg, len);
+		omsg->plaintext = msg;
+		omsg->plaintext_len = strlen(msg);
+	} else {
+		omsg->plaintext = strdup(message);
+		omsg->plaintext_len = strlen(message);
+	}
+
+	err = otrl_message_sending(user_state, &ops, oinfo, from, protocol, to,
+			OTRL_INSTAG_BEST, omsg->plaintext, NULL, &new_msg,
+			fragPolicy, NULL, NULL, NULL);
+	if (err) {
+		goto error;
+	}
+	if (new_msg) {
+		free(omsg->ciphertext);
+		omsg->ciphertext = strdup(new_msg);
+		omsg->ciphertext_len = strlen(omsg->ciphertext);
+		otrl_message_free(new_msg);
+	}
+
+	ret = send(sock, &omsg, sizeof(omsg), 0);
+	if (ret < 0) {
+		perror("send OTR msg");
+		goto error;
+	}
+
+	return 0;
+
+error:
+	if(omsg){
+		free(omsg->plaintext);
+		free(omsg->ciphertext);
+		free(omsg);
+	}
+	return -1;
+}
+
+static int recv_otr_msg(int sock, const char *to, const char *from,
+		struct otr_info *oinfo)
+{
+	int err;
+	ssize_t ret;
+	char *new_msg = NULL;
+	struct otr_msg *omsg;
+	OtrlTLV *tlvs = NULL;
+
+	ret = recv(sock, &omsg, sizeof(omsg), 0);
+	if (ret < 0) {
+		goto error;
+	}
+
+	err = otrl_message_receiving(user_state, &ops, oinfo, to, protocol, from,
+			omsg->ciphertext, &new_msg, &tlvs, NULL, NULL, NULL);
+	if (!err) {
+		if (new_msg) {
+			OK(strncmp(omsg->plaintext, new_msg, omsg->plaintext_len) == 0,
+					"Message exchanged is valid");
+			update_msg_counter();
+		}
+	} else {
+		OK(err == 1, "Internal OTR message valid");
+	}
+
+	free(omsg->plaintext);
+	free(omsg->ciphertext);
+	free(omsg);
+
+	OtrlTLV *tlv = otrl_tlv_find(tlvs, OTRL_TLV_DISCONNECTED);
+	/*
+	 * XXX: Somehow you can end up with a disconnected TLV in a gone secure
+	 * session (see #54). This is probably a bug but since the gone_insecure is
+	 * never called (see bug #48) we have no reliable way of knowing the state
+	 * of the session at this point.
+	 */
+	if (tlv && !oinfo->gone_secure) {
+		OK(session_disconnected, "Disconnected TLV confirmed");
+	}
+
+	otrl_tlv_free(tlvs);
+
+	return 0;
+
+error:
+	return -1;
+}
+
+static int add_sock_to_pollset(int epfd, int sock, uint32_t req_ev)
+{
+	int ret;
+	struct epoll_event ev;
+
+	memset(&ev, 0, sizeof(ev));
+	ev.events = req_ev;
+	ev.data.fd = sock;
+
+	ret = epoll_ctl(epfd, EPOLL_CTL_ADD, sock, &ev);
+	if (ret < 0) {
+		perror("epoll_ctl add");
+	}
+	return ret;
+}
+
+static void *alice_thread(void *data)
+{
+	int sock_to_bob, sock_from_bob = 0, epfd, ret;
+	unsigned int auth_started = 0;
+	struct otr_info oinfo;
+
+	memset(&oinfo, 0, sizeof(oinfo));
+
+	/* Poll size is ignored since 2.6.8 */
+	epfd = epoll_create(42);
+	if (epfd < 0) {
+		perror("epoll_create Bob");
+		goto error;
+	}
+
+	sock_to_bob = socket(PF_UNIX, SOCK_STREAM, 0);
+	if (sock_to_bob < 0) {
+		perror("Bob socket to Alice");
+		goto sock_error;
+	}
+	oinfo.sock = sock_to_bob;
+	oinfo.user = alice_name;
+	if (!opt_auth) {
+		/* We are not going to SMP auth for this session so indicate it's
+		 * completed so we can go forward with random disconnect.
+		 */
+		oinfo.auth_done = 1;
+	}
+
+	ret = connect(sock_to_bob, (struct sockaddr *) &bob_sun,
+			sizeof(bob_sun));
+	if (ret < 0) {
+		perror("connect to Alice");
+		goto end;
+	}
+
+	/* Add quit pipe to pollset trigger by a cleanup. */
+	ret = add_sock_to_pollset(epfd, quit_pipe[0], EPOLLIN);
+	if (ret < 0) {
+		goto end;
+	}
+
+	/* Add our socket to epoll set. */
+	ret = add_sock_to_pollset(epfd, alice_sock,
+			EPOLLIN | EPOLLRDHUP);
+	if (ret < 0) {
+		goto end;
+	}
+
+	while (1) {
+		int i, nb_fd, timeout;
+		struct epoll_event ev[3];
+		memset(ev, 0, sizeof(ev));
+
+		/*
+		 * Set random timeout and when we do timeout, use that to send message
+		 * to Alice.
+		 */
+		timeout = (rand() % (timeout_max - 1));
+
+		ret = epoll_wait(epfd, ev, sizeof(ev), timeout);
+		if (ret < 0) {
+			perror("epoll_wait Alice");
+			goto end;
+		}
+		nb_fd = ret;
+
+		/* Each timeout to 10 finishes the OTR session. */
+		if (!(timeout % 3) && opt_disconnect && oinfo.auth_done) {
+			session_disconnected = 1;
+			oinfo.gone_secure = 0;
+			otrl_message_disconnect(user_state, &ops, &oinfo,
+					alice_name, protocol, bob_name, OTRL_INSTAG_BEST);
+			OK(!oinfo.gone_secure, "OTR message disconnect");
+		}
+
+		/* Start authentication with Bob. */
+		if (opt_auth && !auth_started && oinfo.gone_secure) {
+			ConnContext *ctx;
+
+			/* We have to find our context before auth. */
+			ctx = otrl_context_find(user_state, bob_name, alice_name,
+					protocol, OTRL_INSTAG_BEST, 0, NULL, NULL, &oinfo);
+			OK(ctx, "Alice context found for SMP auth");
+
+			otrl_message_initiate_smp_q(user_state, &ops, &oinfo, ctx,
+					auth_question, (unsigned char *) auth_secret,
+					strlen(auth_secret));
+			auth_started = 1;
+		}
+
+		/* No event thus timeout, send message to Alice. */
+		if (nb_fd == 0) {
+			(void) send_otr_msg(sock_to_bob, bob_name, alice_name, &oinfo,
+					NULL);
+			continue;
+		}
+
+		for (i = 0; i < nb_fd; i++) {
+			int fd;
+			uint32_t event;
+
+			fd = ev[i].data.fd;
+			event = ev[i].events;
+
+			if (fd == quit_pipe[0]) {
+				/* Time to leave. */
+				goto end;
+			} else if (fd == alice_sock) {
+				if (event & (EPOLLERR | EPOLLHUP | EPOLLRDHUP)) {
+					goto end;
+				} else if (event & EPOLLIN) {
+					socklen_t len;
+					struct sockaddr_un sun;
+
+					/* Connection from Bob, accept it so we can handle it. */
+					sock_from_bob = accept(fd, (struct sockaddr *) &sun,
+							&len);
+					ret = add_sock_to_pollset(epfd, sock_from_bob,
+							EPOLLIN | EPOLLERR | EPOLLHUP);
+					if (ret < 0) {
+						goto end;
+					}
+				}
+				continue;
+			} else if (fd == sock_from_bob) {
+				if (event & (EPOLLERR | EPOLLHUP)) {
+					/* Stop since Bob's thread just shut us down. */
+					goto end;
+				} else if (event & EPOLLIN) {
+					(void) recv_otr_msg(sock_from_bob, alice_name, bob_name,
+							&oinfo);
+				}
+				continue;
+			} else {
+				goto end;
+			}
+		}
+	}
+
+end:
+	if (sock_from_bob) {
+		(void) close(sock_from_bob);
+	}
+	(void) close(sock_to_bob);
+sock_error:
+	(void) close(epfd);
+error:
+	(void) close(alice_sock);
+
+	return NULL;
+}
+
+static void *bob_thread(void *data)
+{
+	int sock_to_alice, sock_from_alice = 0, epfd, ret;
+	struct otr_info oinfo;
+
+	memset(&oinfo, 0, sizeof(oinfo));
+
+	/* Poll size is ignored since 2.6.8 */
+	epfd = epoll_create(42);
+	if (epfd < 0) {
+		perror("epoll_create Bob");
+		goto error;
+	}
+
+	sock_to_alice = socket(PF_UNIX, SOCK_STREAM, 0);
+	if (sock_to_alice < 0) {
+		perror("Bob socket to Alice");
+		goto sock_error;
+	}
+	oinfo.sock = sock_to_alice;
+	oinfo.user = bob_name;
+
+	ret = connect(sock_to_alice, (struct sockaddr *) &alice_sun,
+			sizeof(alice_sun));
+	if (ret < 0) {
+		perror("connect to Alice");
+		goto end;
+	}
+
+	/* Add quit pipe to pollset trigger by a cleanup. */
+	ret = add_sock_to_pollset(epfd, quit_pipe[0], EPOLLIN);
+	if (ret < 0) {
+		goto end;
+	}
+
+	/* Add our socket to epoll set. */
+	ret = add_sock_to_pollset(epfd, bob_sock,
+			EPOLLIN | EPOLLERR | EPOLLHUP | EPOLLRDHUP);
+	if (ret < 0) {
+		goto end;
+	}
+
+	while (1) {
+		int i, timeout = 500, nb_fd;
+		struct epoll_event ev[3];
+		memset(ev, 0, sizeof(ev));
+
+		/*
+		 * Set random timeout and when we do timeout, use that to send message
+		 * to Alice.
+		 */
+		timeout = (rand() % (timeout_max - 1));
+
+		ret = epoll_wait(epfd, ev, sizeof(ev), timeout);
+		if (ret < 0) {
+			perror("epoll_wait Bob");
+			goto end;
+		}
+		nb_fd = ret;
+
+		/* No event thus timeout, send message to Alice. */
+		if (nb_fd == 0) {
+			(void) send_otr_msg(sock_to_alice, alice_name, bob_name, &oinfo,
+					NULL);
+			continue;
+		}
+
+		for (i = 0; i < nb_fd; i++) {
+			int fd;
+			uint32_t event;
+
+			fd = ev[i].data.fd;
+			event = ev[i].events;
+
+			if (fd == quit_pipe[0]) {
+				/* Time to leave. */
+				goto end;
+			} else if (fd == bob_sock) {
+				if (event & (EPOLLERR | EPOLLHUP | EPOLLRDHUP)) {
+					goto end;
+				} else if (event & (EPOLLIN | EPOLLHUP)) {
+					socklen_t len;
+					struct sockaddr_un sun;
+
+					/* Connection from Alice, accept it so we can handle it. */
+					sock_from_alice = accept(fd, (struct sockaddr *) &sun,
+							&len);
+					ret = add_sock_to_pollset(epfd, sock_from_alice,
+							EPOLLIN | EPOLLERR | EPOLLHUP);
+					if (ret < 0) {
+						goto end;
+					}
+				}
+				continue;
+			} else if (fd == sock_from_alice) {
+				if (event & (EPOLLERR | EPOLLHUP)) {
+					goto end;
+				} else if (event & EPOLLIN) {
+					(void) recv_otr_msg(sock_from_alice, bob_name,
+							alice_name, &oinfo);
+				}
+				continue;
+			} else {
+				goto end;
+			}
+		}
+	}
+
+end:
+	if (sock_from_alice) {
+		(void) close(sock_from_alice);
+	}
+	(void) close(sock_to_alice);
+sock_error:
+	(void) close(epfd);
+error:
+	(void) close(bob_sock);
+	return NULL;
+}
+
+static void run(void)
+{
+	int ret;
+	void *status;
+	pthread_t alice_th, bob_th;
+
+	/* Init quit pipe. */
+	ret = pipe(quit_pipe);
+	if (ret < 0) {
+		perror("pipe quit pipe");
+		goto end;
+	}
+
+	ret = pthread_create(&alice_th, NULL, alice_thread, NULL);
+	if (ret) {
+		fail("pthread_create sender thread failed (errno: %d)", errno);
+		goto end;
+	}
+
+	ret = pthread_create(&bob_th, NULL, bob_thread, NULL);
+	if (ret) {
+		fail("pthread_create receiver thread failed (errno: %d)", errno);
+		goto exit_receiver;
+	}
+
+	(void) pthread_join(bob_th, &status);
+
+exit_receiver:
+	(void) pthread_join(alice_th, &status);
+end:
+	/* Get rid of the quit pipe. */
+	close(quit_pipe[0]);
+	close(quit_pipe[1]);
+	return;
+}
+
+/*
+ * Load OTR instag using the given opt argument.
+ */
+static void load_instag(void)
+{
+	int ret;
+	gcry_error_t err;
+
+	ret = access(opt_instag_path, R_OK);
+	if (ret < 0) {
+		fail("Instag file %s is not readable", opt_instag_path);
+		return;
+	}
+
+	err = otrl_instag_read(user_state, opt_instag_path);
+	OK(err == GPG_ERR_NO_ERROR, "Loading instag from given file");
+}
+
+/*
+ * Load private key file using the given opt argument.
+ */
+static void load_key(void)
+{
+	int ret;
+	gcry_error_t err;
+
+	ret = access(opt_key_path, R_OK);
+	if (ret < 0) {
+		fail("Key file %s is not readable", opt_key_path);
+		return;
+	}
+
+	err = otrl_privkey_read(user_state, opt_key_path);
+	OK(err == GPG_ERR_NO_ERROR, "Loading key from given file");
+}
+
+/*
+ * Load private key fingerprint file using the given opt argument.
+ */
+static void load_key_fp(void)
+{
+	int ret;
+	gcry_error_t err;
+
+	ret = access(opt_key_fp_path, R_OK);
+	if (ret < 0) {
+		fail("Key fingerprints file %s is not readable", opt_key_fp_path);
+		return;
+	}
+
+	err = otrl_privkey_read_fingerprints(user_state, opt_key_fp_path, NULL,
+			NULL);
+	OK(err == GPG_ERR_NO_ERROR, "Loading key fingerprints from given file");
+}
+
+static int create_unix_socket(const char *pathname,
+		struct sockaddr_un *sun)
+{
+	int sock, ret;
+
+	/* Create both Unix socket. */
+	if ((sock = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) {
+		ret = -errno;
+		perror("Unix socket");
+		goto error;
+	}
+
+	memset(sun, 0, sizeof(struct sockaddr_un));
+	sun->sun_family = AF_UNIX;
+	strncpy(sun->sun_path, pathname, sizeof(sun->sun_path));
+	sun->sun_path[sizeof(sun->sun_path) - 1] = '\0';
+
+	ret = bind(sock, (struct sockaddr *) sun, sizeof(struct sockaddr_un));
+	if (ret < 0) {
+		perror("bind unix sock");
+		goto error;
+	}
+
+	ret = listen(sock, 10);
+	if (ret < 0) {
+		perror("listen unix sock");
+		goto error;
+	}
+
+	return sock;
+error:
+	return ret;
+}
+
+/*
+ * Bootstrap client by initializing the OTR library and creating an OTR user
+ * state.
+ *
+ * Return 0 on success else a negative value on error.
+ */
+static int init_client(void)
+{
+	int ret;
+
+	/* Init libgcrypt threading system. */
+	gcry_control(GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread);
+
+	/* Init OTR library. */
+	OTRL_INIT;
+	OK(1, "OTR library initialization done.");
+
+	user_state = otrl_userstate_create();
+	OK(user_state, "OTR userstate creation done.");
+	if (!user_state) {
+		fail("Out of memory on userstate create");
+		ret = -ENOMEM;
+		goto error;
+	}
+
+	/* Seed the prng. */
+	srand(time(NULL));
+
+	/* Cleanup Unix socket file before creating them. */
+	unlink(unix_sock_alice_path);
+	unlink(unix_sock_bob_path);
+
+	alice_sock = create_unix_socket(unix_sock_alice_path, &alice_sun);
+	bob_sock = create_unix_socket(unix_sock_bob_path, &bob_sun);
+	if (alice_sock < 0 || bob_sock < 0) {
+		ret = -EINVAL;
+		goto error;
+	}
+
+	if (opt_fragment) {
+		fragPolicy = OTRL_FRAGMENT_SEND_ALL;
+	}
+
+	return 0;
+
+error:
+	return ret;
+}
+
+static void sighandler(int sig)
+{
+	switch (sig) {
+	case SIGPIPE:
+	case SIGINT:
+	case SIGTERM:
+		cleanup();
+		break;
+	default:
+		break;
+	}
+}
+
+/*
+ * main entry point.
+ */
+int main(int argc, char **argv)
+{
+	int ret, opt;
+	struct sigaction sa;
+	sigset_t sigset;
+
+	plan_no_plan();
+
+	if ((ret = sigemptyset(&sigset)) < 0) {
+		perror("sigemptyset");
+		goto error;
+	}
+
+	sa.sa_handler = sighandler;
+	sa.sa_mask = sigset;
+	sa.sa_flags = 0;
+
+	if ((ret = sigaction(SIGTERM, &sa, NULL)) < 0) {
+		perror("sigaction");
+		goto error;
+	}
+	if ((ret = sigaction(SIGINT, &sa, NULL)) < 0) {
+		perror("sigaction");
+		goto error;
+	}
+	if ((ret = sigaction(SIGPIPE, &sa, NULL)) < 0) {
+		perror("sigaction");
+		goto error;
+	}
+
+	while ((opt = getopt_long(argc, argv, "+i:k:f:t:m:daF", long_opts, NULL)) != -1) {
+		switch (opt) {
+		case 'i':
+			opt_instag_path = strdup(optarg);
+			break;
+		case 'k':
+			opt_key_path = strdup(optarg);
+			break;
+		case 'f':
+			opt_key_fp_path = strdup(optarg);
+			break;
+		case 't':
+			timeout_max = atoi(optarg);
+			break;
+		case 'm':
+			opt_max_num_msg = atoi(optarg);
+			break;
+		case 'd':
+			opt_disconnect = 1;
+			break;
+		case 'a':
+			opt_auth = 1;
+			break;
+		case 'F':
+			opt_fragment = 1;
+			break;
+		default:
+			goto error;
+		}
+	}
+
+	if (!opt_key_path) {
+		fail("No key file, failing");
+		goto error;
+	}
+
+	/* Running OTR tests. */
+	ret = init_client();
+	if (ret < 0) {
+		goto error;
+	}
+
+	if (opt_instag_path) {
+		load_instag();
+	}
+	if (opt_key_fp_path) {
+		load_key_fp();
+	}
+	load_key();
+
+	run();
+
+	return exit_status();
+
+error:
+	return -1;
+}