summaryrefslogtreecommitdiffstats
path: root/comm/third_party/libotr/tests/regression/client/client.c
diff options
context:
space:
mode:
Diffstat (limited to 'comm/third_party/libotr/tests/regression/client/client.c')
-rw-r--r--comm/third_party/libotr/tests/regression/client/client.c1158
1 files changed, 1158 insertions, 0 deletions
diff --git a/comm/third_party/libotr/tests/regression/client/client.c b/comm/third_party/libotr/tests/regression/client/client.c
new file mode 100644
index 0000000000..e72b6613e9
--- /dev/null
+++ b/comm/third_party/libotr/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;
+}