diff --git a/.gitignore b/.gitignore index 57f928aa..2050cb77 100644 --- a/.gitignore +++ b/.gitignore @@ -30,7 +30,11 @@ core bugs/ TODO plugins/ +*_key.txt +*_fingerprints.txt src/gitversion.c +*_key.txt +*_fingerprints.txt TAGS .libs/ _configs.sed diff --git a/Makefile.am b/Makefile.am index 3dc034db..7b8a94c8 100644 --- a/Makefile.am +++ b/Makefile.am @@ -82,6 +82,9 @@ main_source = src/main.c git_sources = \ src/gitversion.c +otr_sources = \ + src/otr.c src/otr.h + if INCLUDE_GIT_VERSION with_git_sources = $(git_sources) $(core_sources) tests_with_git_sources = $(git_sources) $(test_sources) @@ -90,12 +93,20 @@ with_git_sources = $(core_sources) tests_with_git_sources = $(test_sources) endif +if BUILD_OTR +with_otr_sources = $(with_git_sources) $(otr_sources) +tests_with_otr_sources = $(tests_with_git_sources) $(otr_sources) +else +with_otr_sources = $(with_git_sources) +tests_with_otr_sources = $(tests_with_git_sources) +endif + bin_PROGRAMS = profanity -profanity_SOURCES = $(with_git_sources) $(main_source) +profanity_SOURCES = $(with_otr_sources) $(main_source) TESTS = tests/testsuite check_PROGRAMS = tests/testsuite -tests_testsuite_SOURCES = $(tests_with_git_sources) +tests_testsuite_SOURCES = $(tests_with_otr_sources) tests_testsuite_LDADD = -lcmocka man_MANS = docs/profanity.1 diff --git a/configure.ac b/configure.ac index c94e2c20..708432b1 100644 --- a/configure.ac +++ b/configure.ac @@ -29,6 +29,8 @@ AS_IF([test "x$host_os" = xcygwin], ### Options AC_ARG_ENABLE([notifications], [AS_HELP_STRING([--enable-notifications], [enable desktop notifications])]) +AC_ARG_ENABLE([otr], + [AS_HELP_STRING([--enable-otr], [enable otr encryption])]) AC_ARG_WITH([libxml2], [AS_HELP_STRING([--with-libxml2], [link with libxml2 instead of expat])]) AC_ARG_WITH([xscreensaver], @@ -128,6 +130,15 @@ elif test "x$with_xscreensaver" = x; then [AC_MSG_NOTICE([libX11 not found, falling back to profanity auto-away])]) fi +AM_CONDITIONAL([BUILD_OTR], [true]) +if test "x$enable_otr" = xyes; then + AC_CHECK_LIB([otr], [main], [], + [AC_MSG_ERROR([libotr is required for otr encryption support])]) +elif test "x$enable_otr" = x; then + AC_CHECK_LIB([otr], [main], [], + [AM_CONDITIONAL([BUILD_OTR], [false]) AC_MSG_NOTICE([libotr not found, otr entryption support not enabled])]) +fi + ### cmocka is required only for tests, profanity shouldn't be linked with it ### TODO: pass cmocka_CFLAGS and cmocka_LIBS to Makefile.am PKG_CHECK_MODULES([cmocka], [cmocka], [], diff --git a/install-all.sh b/install-all.sh index 42deaf1a..656d9d90 100755 --- a/install-all.sh +++ b/install-all.sh @@ -12,7 +12,7 @@ debian_prepare() echo echo Profanity installer... installing dependencies echo - sudo apt-get -y install git automake autoconf libssl-dev libexpat1-dev libncursesw5-dev libglib2.0-dev libnotify-dev libcurl3-dev libxss-dev + sudo apt-get -y install git automake autoconf libssl-dev libexpat1-dev libncursesw5-dev libglib2.0-dev libnotify-dev libcurl3-dev libxss-dev libotr2-dev } @@ -24,7 +24,7 @@ fedora_prepare() ARCH=`arch` - sudo yum -y install gcc git autoconf automake openssl-devel.$ARCH expat-devel.$ARCH ncurses-devel.$ARCH glib2-devel.$ARCH libnotify-devel.$ARCH libcurl-devel.$ARCH libXScrnSaver-devel.$ARCH + sudo yum -y install gcc git autoconf automake openssl-devel.$ARCH expat-devel.$ARCH ncurses-devel.$ARCH glib2-devel.$ARCH libnotify-devel.$ARCH libcurl-devel.$ARCH libXScrnSaver-devel.$ARCH libotr3-devel.$ARCH } cygwin_prepare() diff --git a/src/command/command.c b/src/command/command.c index 6d7aa627..ba976023 100644 --- a/src/command/command.c +++ b/src/command/command.c @@ -41,6 +41,7 @@ #include "jid.h" #include "log.h" #include "muc.h" +#include "otr.h" #include "profanity.h" #include "tools/autocomplete.h" #include "tools/parser.h" @@ -567,6 +568,20 @@ static struct cmd_t command_defs[] = "Such as whether you have become inactive, or have closed the chat window.", NULL } } }, + { "/otr", + cmd_otr, parse_args, 1, 2, NULL, + { "/otr gen|myfp|theirfp|start|end|trust|untrust", "Off The Record encryption commands.", + { "/otr gen|myfp|theirfp|start|end|trust|untrust", + "---------------------------------------------", + "gen - Generate your private key.", + "myfp - Show your fingerprint.", + "theirfp - Show contacts fingerprint.", + "start - Start an OTR session with the current recipient.", + "end - End the current OTR session,", + "trust - Indicate that you have verified the contact's fingerprint.", + "untrust - Indicate the the contact's fingerprint is not verified,", + NULL } } }, + { "/outtype", cmd_outtype, parse_args, 1, 1, &cons_outtype_setting, { "/outtype on|off", "Send typing notification to recipient.", @@ -817,6 +832,7 @@ static Autocomplete wins_ac; static Autocomplete roster_ac; static Autocomplete group_ac; static Autocomplete bookmark_ac; +static Autocomplete otr_ac; /* * Initialise command autocompleter and history @@ -969,6 +985,15 @@ cmd_init(void) autocomplete_add(bookmark_ac, "list"); autocomplete_add(bookmark_ac, "remove"); + otr_ac = autocomplete_new(); + autocomplete_add(otr_ac, "gen"); + autocomplete_add(otr_ac, "start"); + autocomplete_add(otr_ac, "end"); + autocomplete_add(otr_ac, "myfp"); + autocomplete_add(otr_ac, "theirfp"); + autocomplete_add(otr_ac, "trust"); + autocomplete_add(otr_ac, "untrust"); + cmd_history_init(); } @@ -999,6 +1024,7 @@ cmd_uninit(void) autocomplete_free(roster_ac); autocomplete_free(group_ac); autocomplete_free(bookmark_ac); + autocomplete_free(otr_ac); } // Command autocompletion functions @@ -1072,6 +1098,7 @@ cmd_reset_autocomplete() autocomplete_reset(roster_ac); autocomplete_reset(group_ac); autocomplete_reset(bookmark_ac); + autocomplete_reset(otr_ac); bookmark_autocomplete_reset(); } @@ -1129,8 +1156,36 @@ cmd_execute_default(const char * const inp) if (status != JABBER_CONNECTED) { ui_current_print_line("You are not currently connected."); } else { - message_send(inp, recipient); +#ifdef HAVE_LIBOTR + if (otr_is_secure(recipient)) { + char *encrypted = otr_encrypt_message(recipient, inp); + if (encrypted != NULL) { + message_send(encrypted, recipient); + otr_free_message(encrypted); + if (prefs_get_boolean(PREF_CHLOG)) { + const char *jid = jabber_get_fulljid(); + Jid *jidp = jid_create(jid); + chat_log_chat(jidp->barejid, recipient, inp, PROF_OUT_LOG, NULL); + jid_destroy(jidp); + } + ui_outgoing_msg("me", recipient, inp); + } else { + cons_show_error("Failed to send message."); + } + } else { + message_send(inp, recipient); + if (prefs_get_boolean(PREF_CHLOG)) { + const char *jid = jabber_get_fulljid(); + Jid *jidp = jid_create(jid); + chat_log_chat(jidp->barejid, recipient, inp, PROF_OUT_LOG, NULL); + jid_destroy(jidp); + } + + ui_outgoing_msg("me", recipient, inp); + } +#else + message_send(inp, recipient); if (prefs_get_boolean(PREF_CHLOG)) { const char *jid = jabber_get_fulljid(); Jid *jidp = jid_create(jid); @@ -1139,6 +1194,7 @@ cmd_execute_default(const char * const inp) } ui_outgoing_msg("me", recipient, inp); +#endif } break; @@ -1267,8 +1323,8 @@ _cmd_complete_parameters(char *input, int *size) return; } - gchar *cmds[] = { "/help", "/prefs", "/log", "/disco", "/close", "/wins" }; - Autocomplete completers[] = { help_ac, prefs_ac, log_ac, disco_ac, close_ac, wins_ac }; + gchar *cmds[] = { "/help", "/prefs", "/log", "/disco", "/close", "/wins", "/otr" }; + Autocomplete completers[] = { help_ac, prefs_ac, log_ac, disco_ac, close_ac, wins_ac, otr_ac }; for (i = 0; i < ARRAY_SIZE(cmds); i++) { result = autocomplete_param_with_ac(input, size, cmds[i], completers[i]); diff --git a/src/command/commands.c b/src/command/commands.c index e6588c11..d7ce2742 100644 --- a/src/command/commands.c +++ b/src/command/commands.c @@ -36,11 +36,14 @@ #include "jid.h" #include "log.h" #include "muc.h" +#include "otr.h" #include "profanity.h" #include "tools/autocomplete.h" #include "tools/parser.h" #include "tools/tinyurl.h" #include "ui/ui.h" +#include "ui/window.h" +#include "ui/windows.h" #include "xmpp/xmpp.h" #include "xmpp/bookmark.h" @@ -915,6 +918,36 @@ cmd_msg(gchar **args, struct cmd_help_t help) usr_jid = usr; } if (msg != NULL) { +#ifdef HAVE_LIBOTR + if (otr_is_secure(usr_jid)) { + char *encrypted = otr_encrypt_message(usr_jid, msg); + if (encrypted != NULL) { + message_send(encrypted, usr_jid); + otr_free_message(encrypted); + ui_outgoing_msg("me", usr_jid, msg); + + if (((win_type == WIN_CHAT) || (win_type == WIN_CONSOLE)) && prefs_get_boolean(PREF_CHLOG)) { + const char *jid = jabber_get_fulljid(); + Jid *jidp = jid_create(jid); + chat_log_chat(jidp->barejid, usr_jid, msg, PROF_OUT_LOG, NULL); + jid_destroy(jidp); + } + } else { + cons_show_error("Failed to encrypt and send message,"); + } + } else { + message_send(msg, usr_jid); + ui_outgoing_msg("me", usr_jid, msg); + + if (((win_type == WIN_CHAT) || (win_type == WIN_CONSOLE)) && prefs_get_boolean(PREF_CHLOG)) { + const char *jid = jabber_get_fulljid(); + Jid *jidp = jid_create(jid); + chat_log_chat(jidp->barejid, usr_jid, msg, PROF_OUT_LOG, NULL); + jid_destroy(jidp); + } + } + return TRUE; +#else message_send(msg, usr_jid); ui_outgoing_msg("me", usr_jid, msg); @@ -924,8 +957,9 @@ cmd_msg(gchar **args, struct cmd_help_t help) chat_log_chat(jidp->barejid, usr_jid, msg, PROF_OUT_LOG, NULL); jid_destroy(jidp); } - return TRUE; +#endif + } else { const char * jid = NULL; @@ -2264,6 +2298,104 @@ cmd_xa(gchar **args, struct cmd_help_t help) return TRUE; } +gboolean +cmd_otr(gchar **args, struct cmd_help_t help) +{ +#ifdef HAVE_LIBOTR + if (jabber_get_connection_status() != JABBER_CONNECTED) { + cons_show("You must be connected with an account to load OTR information."); + return TRUE; + } + + if (strcmp(args[0], "gen") == 0) { + ProfAccount *account = accounts_get_account(jabber_get_account_name()); + otr_keygen(account); + return TRUE; + } else if (strcmp(args[0], "myfp") == 0) { + char *fingerprint = otr_get_my_fingerprint(); + ui_current_print_line("Your OTR fingerprint: %s", fingerprint); + free(fingerprint); + return TRUE; + } else if (strcmp(args[0], "theirfp") == 0) { + win_type_t win_type = ui_current_win_type(); + + if (win_type != WIN_CHAT) { + ui_current_print_line("You must be in a regular chat window to view a recipient's fingerprint."); + } else if (!ui_current_win_is_otr()) { + ui_current_print_line("You not currently in an OTR session with this recipient."); + } else { + char *recipient = ui_current_recipient(); + char *fingerprint = otr_get_their_fingerprint(recipient); + ui_current_print_line("OTR fingerprint for %s: %s", recipient, fingerprint); + free(fingerprint); + } + return TRUE; + } else if (strcmp(args[0], "start") == 0) { + win_type_t win_type = ui_current_win_type(); + + if (win_type != WIN_CHAT) { + ui_current_print_line("You must be in a regular chat window to start an OTR session."); + } else if (ui_current_win_is_otr()) { + ui_current_print_line("You are already in an OTR session."); + } else { + if (!otr_key_loaded()) { + ui_current_print_line("You have not generated or loaded a private key, use '/otr gen'"); + } else { + char *recipient = ui_current_recipient(); + message_send("?OTR?", recipient); + } + } + return TRUE; + } else if (strcmp(args[0], "end") == 0) { + win_type_t win_type = ui_current_win_type(); + + if (win_type != WIN_CHAT) { + ui_current_print_line("You must be in a regular chat window to use OTR."); + } else if (!ui_current_win_is_otr()) { + ui_current_print_line("You are not currently in an OTR session."); + } else { + char *recipient = ui_current_recipient(); + ui_gone_insecure(recipient); + otr_end_session(recipient); + } + return TRUE; + } else if (strcmp(args[0], "trust") == 0) { + win_type_t win_type = ui_current_win_type(); + + if (win_type != WIN_CHAT) { + ui_current_print_line("You must be in an OTR session to trust a recipient."); + } else if (!ui_current_win_is_otr()) { + ui_current_print_line("You are not currently in an OTR session."); + } else { + char *recipient = ui_current_recipient(); + ui_trust(recipient); + otr_trust(recipient); + } + return TRUE; + } else if (strcmp(args[0], "untrust") == 0) { + win_type_t win_type = ui_current_win_type(); + + if (win_type != WIN_CHAT) { + ui_current_print_line("You must be in an OTR session to untrust a recipient."); + } else if (!ui_current_win_is_otr()) { + ui_current_print_line("You are not currently in an OTR session."); + } else { + char *recipient = ui_current_recipient(); + ui_untrust(recipient); + otr_untrust(recipient); + } + return TRUE; + + } else { + cons_show("Usage: %s", help.usage); + return TRUE; + } +#else + cons_show("This version of Profanity has not been built with OTR support enabled"); + return TRUE; +#endif +} + // helper function for status change commands static void _update_presence(const resource_presence_t resource_presence, diff --git a/src/command/commands.h b/src/command/commands.h index 27a02249..741ab638 100644 --- a/src/command/commands.h +++ b/src/command/commands.h @@ -86,6 +86,7 @@ gboolean cmd_msg(gchar **args, struct cmd_help_t help); gboolean cmd_nick(gchar **args, struct cmd_help_t help); gboolean cmd_notify(gchar **args, struct cmd_help_t help); gboolean cmd_online(gchar **args, struct cmd_help_t help); +gboolean cmd_otr(gchar **args, struct cmd_help_t help); gboolean cmd_outtype(gchar **args, struct cmd_help_t help); gboolean cmd_prefs(gchar **args, struct cmd_help_t help); gboolean cmd_priority(gchar **args, struct cmd_help_t help); diff --git a/src/otr.c b/src/otr.c new file mode 100644 index 00000000..149a81be --- /dev/null +++ b/src/otr.c @@ -0,0 +1,526 @@ +/* + * otr.c + * + * Copyright (C) 2012, 2013 James Booth + * + * This file is part of Profanity. + * + * Profanity is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Profanity 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 Profanity. If not, see . + * + */ + +#include +#include +#include +#include + +#include "otr.h" +#include "log.h" +#include "roster_list.h" +#include "contact.h" +#include "ui/ui.h" + +static OtrlUserState user_state; +static OtrlMessageAppOps ops; +static char *jid; +static gboolean data_loaded; + +// ops callbacks +static OtrlPolicy +cb_policy(void *opdata, ConnContext *context) +{ + return OTRL_POLICY_DEFAULT ; +} + +static void +cb_create_privkey(void *opdata, const char *accountname, + const char *protocol) +{ +} + +static int +cb_is_logged_in(void *opdata, const char *accountname, + const char *protocol, const char *recipient) +{ + PContact contact = roster_get_contact(recipient); + if (g_strcmp0(p_contact_presence(contact), "offline") == 0) { + return 0; + } else { + return 1; + } +} + +static void +cb_inject_message(void *opdata, const char *accountname, + const char *protocol, const char *recipient, const char *message) +{ + message_send(message, recipient); +} + +static void +cb_notify(void *opdata, OtrlNotifyLevel level, + const char *accountname, const char *protocol, const char *username, + const char *title, const char *primary, const char *secondary) +{ +} + +static int +cb_display_otr_message(void *opdata, const char *accountname, + const char *protocol, const char *username, const char *msg) +{ + cons_show_error("%s", msg); + return 0; +} + +static const char * +cb_protocol_name(void *opdata, const char *protocol) +{ + return "xmpp"; +} + +static void +cb_new_fingerprint(void *opdata, OtrlUserState us, const char *accountname, + const char *protocol, const char *username, unsigned char fingerprint[20]) +{ +} + +static void +cb_protocol_name_free(void *opdata, const char *protocol_name) +{ +} + +static void +cb_update_context_list(void *opdata) +{ +} + +static void +cb_write_fingerprints(void *opdata) +{ + gcry_error_t err = 0; + gchar *data_home = xdg_get_data_home(); + gchar *account_dir = str_replace(jid, "@", "_at_"); + + GString *basedir = g_string_new(data_home); + g_string_append(basedir, "/profanity/otr/"); + g_string_append(basedir, account_dir); + g_string_append(basedir, "/"); + + GString *fpsfilename = g_string_new(basedir->str); + g_string_append(fpsfilename, "fingerprints.txt"); + err = otrl_privkey_write_fingerprints(user_state, fpsfilename->str); + if (!err == GPG_ERR_NO_ERROR) { + log_error("Failed to write fingerprints file"); + cons_show_error("Failed to create fingerprints file"); + } + g_string_free(basedir, TRUE); + g_string_free(fpsfilename, TRUE); +} + +static void +cb_gone_secure(void *opdata, ConnContext *context) +{ + ui_gone_secure(context->username, otr_is_trusted(context->username)); +} + +static void +cb_gone_insecure(void *opdata, ConnContext *context) +{ +} + +static void +cb_still_secure(void *opdata, ConnContext *context, int is_reply) +{ +} + +static void +cb_log_message(void *opdata, const char *message) +{ +} + +void +otr_init(void) +{ + log_info("Initialising OTR"); + OTRL_INIT; + + ops.policy = cb_policy; + ops.create_privkey = cb_create_privkey; + ops.is_logged_in = cb_is_logged_in; + ops.inject_message = cb_inject_message; + ops.notify = cb_notify; + ops.display_otr_message = cb_display_otr_message; + ops.update_context_list = cb_update_context_list; + ops.protocol_name = cb_protocol_name; + ops.protocol_name_free = cb_protocol_name_free; + ops.new_fingerprint = cb_new_fingerprint; + ops.write_fingerprints = cb_write_fingerprints; + ops.gone_secure = cb_gone_secure; + ops.gone_insecure = cb_gone_insecure; + ops.still_secure = cb_still_secure; + ops.log_message = cb_log_message; + + data_loaded = FALSE; +} + +void +otr_on_connect(ProfAccount *account) +{ + jid = strdup(account->jid); + log_info("Loading OTR key for %s", jid); + + gchar *data_home = xdg_get_data_home(); + gchar *account_dir = str_replace(jid, "@", "_at_"); + + GString *basedir = g_string_new(data_home); + g_string_append(basedir, "/profanity/otr/"); + g_string_append(basedir, account_dir); + g_string_append(basedir, "/"); + + if (!mkdir_recursive(basedir->str)) { + g_string_free(basedir, TRUE); + log_error("Could not create %s for account %s.", basedir->str, jid); + cons_show_error("Could not create %s for account %s.", basedir->str, jid); + return; + } + + user_state = otrl_userstate_create(); + + gcry_error_t err = 0; + + GString *keysfilename = g_string_new(basedir->str); + g_string_append(keysfilename, "keys.txt"); + if (!g_file_test(keysfilename->str, G_FILE_TEST_IS_REGULAR)) { + log_info("No private key file found %s", keysfilename->str); + data_loaded = FALSE; + } else { + log_info("Loading OTR private key %s", keysfilename->str); + err = otrl_privkey_read(user_state, keysfilename->str); + if (!err == GPG_ERR_NO_ERROR) { + g_string_free(basedir, TRUE); + g_string_free(keysfilename, TRUE); + log_error("Failed to load private key"); + return; + } else { + log_info("Loaded private key"); + data_loaded = TRUE; + } + } + + GString *fpsfilename = g_string_new(basedir->str); + g_string_append(fpsfilename, "fingerprints.txt"); + if (!g_file_test(fpsfilename->str, G_FILE_TEST_IS_REGULAR)) { + log_info("No fingerprints file found %s", fpsfilename->str); + data_loaded = FALSE; + } else { + log_info("Loading fingerprints %s", fpsfilename->str); + err = otrl_privkey_read_fingerprints(user_state, fpsfilename->str, NULL, NULL); + if (!err == GPG_ERR_NO_ERROR) { + g_string_free(basedir, TRUE); + g_string_free(keysfilename, TRUE); + g_string_free(fpsfilename, TRUE); + log_error("Failed to load fingerprints"); + return; + } else { + log_info("Loaded fingerprints"); + data_loaded = TRUE; + } + } + + if (data_loaded) { + cons_show("Loaded OTR private key for %s", jid); + } + + g_string_free(basedir, TRUE); + g_string_free(keysfilename, TRUE); + g_string_free(fpsfilename, TRUE); + return; +} + +void +otr_keygen(ProfAccount *account) +{ + if (data_loaded) { + cons_show("OTR key already generated."); + return; + } + + jid = strdup(account->jid); + log_info("Generating OTR key for %s", jid); + + jid = strdup(account->jid); + + gchar *data_home = xdg_get_data_home(); + gchar *account_dir = str_replace(jid, "@", "_at_"); + + GString *basedir = g_string_new(data_home); + g_string_append(basedir, "/profanity/otr/"); + g_string_append(basedir, account_dir); + g_string_append(basedir, "/"); + + if (!mkdir_recursive(basedir->str)) { + g_string_free(basedir, TRUE); + log_error("Could not create %s for account %s.", basedir->str, jid); + cons_show_error("Could not create %s for account %s.", basedir->str, jid); + return; + } + + gcry_error_t err = 0; + + GString *keysfilename = g_string_new(basedir->str); + g_string_append(keysfilename, "keys.txt"); + log_debug("Generating private key file %s for %s", keysfilename->str, jid); + cons_show("Generating private key, this may take some time."); + cons_show("Moving the mouse randomly around the screen may speed up the process!"); + ui_current_page_off(); + ui_refresh(); + err = otrl_privkey_generate(user_state, keysfilename->str, account->jid, "xmpp"); + if (!err == GPG_ERR_NO_ERROR) { + g_string_free(basedir, TRUE); + g_string_free(keysfilename, TRUE); + log_error("Failed to generate private key"); + cons_show_error("Failed to generate private key"); + return; + } + log_info("Private key generated"); + cons_show(""); + cons_show("Private key generation complete."); + + GString *fpsfilename = g_string_new(basedir->str); + g_string_append(fpsfilename, "fingerprints.txt"); + log_debug("Generating fingerprints file %s for %s", fpsfilename->str, jid); + err = otrl_privkey_write_fingerprints(user_state, fpsfilename->str); + if (!err == GPG_ERR_NO_ERROR) { + g_string_free(basedir, TRUE); + g_string_free(keysfilename, TRUE); + log_error("Failed to create fingerprints file"); + cons_show_error("Failed to create fingerprints file"); + return; + } + log_info("Fingerprints file created"); + + err = otrl_privkey_read(user_state, keysfilename->str); + if (!err == GPG_ERR_NO_ERROR) { + g_string_free(basedir, TRUE); + g_string_free(keysfilename, TRUE); + log_error("Failed to load private key"); + data_loaded = FALSE; + return; + } + + err = otrl_privkey_read_fingerprints(user_state, fpsfilename->str, NULL, NULL); + if (!err == GPG_ERR_NO_ERROR) { + g_string_free(basedir, TRUE); + g_string_free(keysfilename, TRUE); + log_error("Failed to load fingerprints"); + data_loaded = FALSE; + return; + } + + data_loaded = TRUE; + + g_string_free(basedir, TRUE); + g_string_free(keysfilename, TRUE); + g_string_free(fpsfilename, TRUE); + return; +} + +gboolean +otr_key_loaded(void) +{ + return data_loaded; +} + +gboolean +otr_is_secure(const char * const recipient) +{ + ConnContext *context = otrl_context_find(user_state, recipient, jid, "xmpp", + 0, NULL, NULL, NULL); + + if (context == NULL) { + return FALSE; + } + + if (context->msgstate != OTRL_MSGSTATE_ENCRYPTED) { + return FALSE; + } else { + return TRUE; + } +} + +gboolean +otr_is_trusted(const char * const recipient) +{ + ConnContext *context = otrl_context_find(user_state, recipient, jid, "xmpp", + 0, NULL, NULL, NULL); + + if (context == NULL) { + return FALSE; + } + + if (context->msgstate != OTRL_MSGSTATE_ENCRYPTED) { + return TRUE; + } + + if (context->active_fingerprint && + g_strcmp0(context->active_fingerprint->trust, "trusted") == 0) { + return TRUE; + } + + return FALSE; +} + +void +otr_trust(const char * const recipient) +{ + ConnContext *context = otrl_context_find(user_state, recipient, jid, "xmpp", + 0, NULL, NULL, NULL); + + if (context == NULL) { + return; + } + + if (context->msgstate != OTRL_MSGSTATE_ENCRYPTED) { + return; + } + + if (context->active_fingerprint) { + context->active_fingerprint->trust = "trusted"; + cb_write_fingerprints(NULL); + } + + return; +} + +void +otr_untrust(const char * const recipient) +{ + ConnContext *context = otrl_context_find(user_state, recipient, jid, "xmpp", + 0, NULL, NULL, NULL); + + if (context == NULL) { + return; + } + + if (context->msgstate != OTRL_MSGSTATE_ENCRYPTED) { + return; + } + + if (context->active_fingerprint) { + context->active_fingerprint->trust = NULL; + cb_write_fingerprints(NULL); + } + + return; +} + +void +otr_end_session(const char * const recipient) +{ + ConnContext *context = otrl_context_find(user_state, recipient, jid, "xmpp", + 0, NULL, NULL, NULL); + + if (context != NULL) { + otrl_message_disconnect(user_state, &ops, NULL, jid, "xmpp", recipient); + } +} + +char * +otr_get_my_fingerprint(void) +{ + char fingerprint[45]; + otrl_privkey_fingerprint(user_state, fingerprint, jid, "xmpp"); + char *result = strdup(fingerprint); + + return result; +} + +char * +otr_get_their_fingerprint(const char * const recipient) +{ + ConnContext *context = otrl_context_find(user_state, recipient, jid, "xmpp", + 0, NULL, NULL, NULL); + + if (context != NULL) { + Fingerprint *fingerprint = context->active_fingerprint; + char readable[45]; + otrl_privkey_hash_to_human(readable, fingerprint->fingerprint); + return strdup(readable); + } else { + return NULL; + } +} + +char * +otr_encrypt_message(const char * const to, const char * const message) +{ + gcry_error_t err; + char *newmessage = NULL; + + err = otrl_message_sending( + user_state, + &ops, + NULL, + jid, + "xmpp", + to, + message, + 0, + &newmessage, + NULL, + NULL); + if (!err == GPG_ERR_NO_ERROR) { + return NULL; + } else { + return newmessage; + } +} + +char * +otr_decrypt_message(const char * const from, const char * const message) +{ + char *decrypted = NULL; + OtrlTLV *tlvs = NULL; + OtrlTLV *tlv = NULL; + int result = otrl_message_receiving(user_state, &ops, NULL, jid, "xmpp", from, message, &decrypted, &tlvs, NULL, NULL); + + // internal libotr message, ignore + if (result == 1) { + tlv = otrl_tlv_find(tlvs, OTRL_TLV_DISCONNECTED); + if (tlv) { + ConnContext *context = otrl_context_find(user_state, from, jid, "xmpp", + 0, NULL, NULL, NULL); + + if (context != NULL) { + otrl_context_force_plaintext(context); + ui_gone_insecure(from); + } + } + return NULL; + + // message was decrypted, return to user + } else if (decrypted != NULL) { + return decrypted; + + // normal non OTR message + } else { + return strdup(message); + } +} + +void +otr_free_message(char *message) +{ + otrl_message_free(message); +} diff --git a/src/otr.h b/src/otr.h new file mode 100644 index 00000000..ff1ad5a5 --- /dev/null +++ b/src/otr.h @@ -0,0 +1,49 @@ +/* + * otr.h + * + * Copyright (C) 2012, 2013 James Booth + * + * This file is part of Profanity. + * + * Profanity is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Profanity 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 Profanity. If not, see . + * + */ + +#ifndef OTR_H +#define OTR_H + +#include "config/accounts.h" + +void otr_init(void); +void otr_on_connect(ProfAccount *account); +void otr_keygen(ProfAccount *account); + +gboolean otr_key_loaded(void); +gboolean otr_is_secure(const char * const recipient); + +gboolean otr_is_trusted(const char * const recipient); +void otr_trust(const char * const recipient); +void otr_untrust(const char * const recipient); + +void otr_end_session(const char * const recipient); + +char * otr_get_my_fingerprint(void); +char * otr_get_their_fingerprint(const char * const recipient); + +char * otr_encrypt_message(const char * const to, const char * const message); +char * otr_decrypt_message(const char * const from, const char * const message); + +void otr_free_message(char *message); + +#endif diff --git a/src/profanity.c b/src/profanity.c index dae91aea..049d4bc4 100644 --- a/src/profanity.c +++ b/src/profanity.c @@ -19,7 +19,6 @@ * along with Profanity. If not, see . * */ - #include "config.h" #ifdef HAVE_GIT_VERSION @@ -45,6 +44,9 @@ #include "roster_list.h" #include "log.h" #include "muc.h" +#ifdef HAVE_LIBOTR +#include "otr.h" +#endif #include "resource.h" #include "ui/ui.h" #include "xmpp/xmpp.h" @@ -300,6 +302,9 @@ _init(const int disable_tls, char *log_level) log_info("Initialising contact list"); roster_init(); muc_init(); +#ifdef HAVE_LIBOTR + otr_init(); +#endif atexit(_shutdown); } diff --git a/src/server_events.c b/src/server_events.c index ac940c86..792415be 100644 --- a/src/server_events.c +++ b/src/server_events.c @@ -28,6 +28,9 @@ #include "config/preferences.h" #include "roster_list.h" #include "ui/ui.h" +#ifdef HAVE_LIBOTR +#include "otr.h" +#endif void handle_error_message(const char *from, const char *err_msg) @@ -48,6 +51,9 @@ void handle_login_account_success(char *account_name) { ProfAccount *account = accounts_get_account(account_name); +#ifdef HAVE_LIBOTR + otr_on_connect(account); +#endif resource_presence_t resource_presence = accounts_get_login_presence(account->name); contact_presence_t contact_presence = contact_presence_from_resource_presence(resource_presence); cons_show_login_success(account); @@ -169,6 +175,32 @@ handle_duck_result(const char * const result) void handle_incoming_message(char *from, char *message, gboolean priv) { +#ifdef HAVE_LIBOTR + char *newmessage; + if (!priv) { + newmessage = otr_decrypt_message(from, message); + if (newmessage == NULL) { + return; + } + } else { + newmessage = message; + } + + ui_incoming_msg(from, newmessage, NULL, priv); + ui_current_page_off(); + + if (prefs_get_boolean(PREF_CHLOG) && !priv) { + Jid *from_jid = jid_create(from); + const char *jid = jabber_get_fulljid(); + Jid *jidp = jid_create(jid); + chat_log_chat(jidp->barejid, from_jid->barejid, newmessage, PROF_IN_LOG, NULL); + jid_destroy(jidp); + jid_destroy(from_jid); + } + + if (!priv) + otr_free_message(newmessage); +#else ui_incoming_msg(from, message, NULL, priv); ui_current_page_off(); @@ -180,6 +212,7 @@ handle_incoming_message(char *from, char *message, gboolean priv) jid_destroy(jidp); jid_destroy(from_jid); } +#endif } void diff --git a/src/ui/console.c b/src/ui/console.c index c02201f9..afdc1776 100644 --- a/src/ui/console.c +++ b/src/ui/console.c @@ -131,7 +131,7 @@ _cons_show_typing(const char * const barejid) display_usr = barejid; } - win_print_line(console, '-', COLOUR_TYPING, "!! %s is typing a message...", display_usr); + win_vprint_line(console, '-', COLOUR_TYPING, "!! %s is typing a message...", display_usr); wins_refresh_console(); cons_alert(); diff --git a/src/ui/core.c b/src/ui/core.c index afda184a..e0b934ae 100644 --- a/src/ui/core.c +++ b/src/ui/core.c @@ -48,6 +48,7 @@ #include "jid.h" #include "log.h" #include "muc.h" +#include "otr.h" #include "ui/ui.h" #include "ui/window.h" #include "ui/windows.h" @@ -245,6 +246,11 @@ _ui_incoming_msg(const char * const from, const char * const message, ProfWin *window = wins_get_by_recipient(from); if (window == NULL) { window = wins_new(from, win_type); +#ifdef HAVE_LIBOTR + if (otr_is_secure(from)) { + window->is_otr = TRUE; + } +#endif win_created = TRUE; } @@ -433,7 +439,12 @@ _ui_close_connected_win(int index) char *room_jid = ui_recipient(index); presence_leave_chat_room(room_jid); } else if ((win_type == WIN_CHAT) || (win_type == WIN_PRIVATE)) { - +#ifdef HAVE_LIBOTR + ProfWin *window = wins_get_by_num(index); + if (window->is_otr) { + otr_end_session(window->from); + } +#endif if (prefs_get_boolean(PREF_STATES)) { char *recipient = ui_recipient(index); @@ -501,6 +512,33 @@ _ui_close_read_wins(void) return count; } +GString * +_get_recipient_string(ProfWin *window) +{ + GString *result = g_string_new(""); + PContact contact = roster_get_contact(window->from); + if (contact != NULL) { + if (p_contact_name(contact) != NULL) { + g_string_append(result, p_contact_name(contact)); + } else { + g_string_append(result, window->from); + } + } else { + g_string_append(result, window->from); + } + + if (window->is_otr) { + g_string_append(result, " [OTR]"); + if (window->is_trusted) { + g_string_append(result, " (trusted)"); + } else { + g_string_append(result, " (untrusted)"); + } + } + + return result; +} + static void _ui_switch_win(const int i) { @@ -517,16 +555,9 @@ _ui_switch_win(const int i) status_bar_current(1); status_bar_active(1); } else { - PContact contact = roster_get_contact(new_current->from); - if (contact != NULL) { - if (p_contact_name(contact) != NULL) { - title_bar_set_recipient(p_contact_name(contact)); - } else { - title_bar_set_recipient(new_current->from); - } - } else { - title_bar_set_recipient(new_current->from); - } + GString *recipient_str = _get_recipient_string(new_current); + title_bar_set_recipient(recipient_str->str); + g_string_free(recipient_str, TRUE); title_bar_draw(); status_bar_current(i); status_bar_active(i); @@ -551,16 +582,9 @@ _ui_next_win(void) status_bar_current(1); status_bar_active(1); } else { - PContact contact = roster_get_contact(new_current->from); - if (contact != NULL) { - if (p_contact_name(contact) != NULL) { - title_bar_set_recipient(p_contact_name(contact)); - } else { - title_bar_set_recipient(new_current->from); - } - } else { - title_bar_set_recipient(new_current->from); - } + GString *recipient_str = _get_recipient_string(new_current); + title_bar_set_recipient(recipient_str->str); + g_string_free(recipient_str, TRUE); title_bar_draw(); status_bar_current(i); status_bar_active(i); @@ -568,6 +592,80 @@ _ui_next_win(void) wins_refresh_current(); } +static void +_ui_gone_secure(const char * const recipient, gboolean trusted) +{ + ProfWin *window = wins_get_by_recipient(recipient); + if (window != NULL) { + window->is_otr = TRUE; + window->is_trusted = trusted; + win_vprint_line(window, '!', 0, "OTR session started."); + + if (wins_is_current(window)) { + GString *recipient_str = _get_recipient_string(window); + title_bar_set_recipient(recipient_str->str); + g_string_free(recipient_str, TRUE); + title_bar_draw(); + wins_refresh_current(); + } + } +} + +static void +_ui_gone_insecure(const char * const recipient) +{ + ProfWin *window = wins_get_by_recipient(recipient); + if (window != NULL) { + window->is_otr = FALSE; + window->is_trusted = FALSE; + win_vprint_line(window, '!', 0, "OTR session ended."); + + if (wins_is_current(window)) { + GString *recipient_str = _get_recipient_string(window); + title_bar_set_recipient(recipient_str->str); + g_string_free(recipient_str, TRUE); + title_bar_draw(); + wins_refresh_current(); + } + } +} + +static void +_ui_trust(const char * const recipient) +{ + ProfWin *window = wins_get_by_recipient(recipient); + if (window != NULL) { + window->is_otr = TRUE; + window->is_trusted = TRUE; + + if (wins_is_current(window)) { + GString *recipient_str = _get_recipient_string(window); + title_bar_set_recipient(recipient_str->str); + g_string_free(recipient_str, TRUE); + title_bar_draw(); + wins_refresh_current(); + } + } +} + +static void +_ui_untrust(const char * const recipient) +{ + ProfWin *window = wins_get_by_recipient(recipient); + if (window != NULL) { + window->is_otr = TRUE; + window->is_trusted = FALSE; + + if (wins_is_current(window)) { + GString *recipient_str = _get_recipient_string(window); + title_bar_set_recipient(recipient_str->str); + g_string_free(recipient_str, TRUE); + title_bar_draw(); + wins_refresh_current(); + } + } +} + static void _ui_previous_win(void) { @@ -584,16 +682,9 @@ _ui_previous_win(void) status_bar_current(1); status_bar_active(1); } else { - PContact contact = roster_get_contact(new_current->from); - if (contact != NULL) { - if (p_contact_name(contact) != NULL) { - title_bar_set_recipient(p_contact_name(contact)); - } else { - title_bar_set_recipient(new_current->from); - } - } else { - title_bar_set_recipient(new_current->from); - } + GString *recipient_str = _get_recipient_string(new_current); + title_bar_set_recipient(recipient_str->str); + g_string_free(recipient_str, TRUE); title_bar_draw(); status_bar_current(i); status_bar_active(i); @@ -693,6 +784,20 @@ _ui_current_win_type(void) return current->type; } +static gboolean +_ui_current_win_is_otr(void) +{ + ProfWin *current = wins_get_current(); + return current->is_otr; +} + +static void +_ui_current_set_otr(gboolean value) +{ + ProfWin *current = wins_get_current(); + current->is_otr = value; +} + static int _ui_current_win_index(void) { @@ -726,8 +831,11 @@ _ui_current_print_line(const char * const msg, ...) ProfWin *current = wins_get_current(); va_list arg; va_start(arg, msg); - win_print_line(current, '-', 0, msg, arg); + GString *fmt_msg = g_string_new(NULL); + g_string_vprintf(fmt_msg, msg, arg); + win_print_line(current, '-', 0, fmt_msg->str); va_end(arg); + g_string_free(fmt_msg, TRUE); win_refresh(current); } @@ -755,7 +863,7 @@ _ui_print_error_from_recipient(const char * const from, const char *err_msg) ProfWin *window = wins_get_by_recipient(from); if (window != NULL) { - win_print_line(window, '-', COLOUR_ERROR, "%s", err_msg); + win_vprint_line(window, '-', COLOUR_ERROR, "%s", err_msg); if (wins_is_current(window)) { wins_refresh_current(); } @@ -813,7 +921,7 @@ _ui_recipient_gone(const char * const barejid) ProfWin *window = wins_get_by_recipient(barejid); if (window != NULL) { - win_print_line(window, '!', COLOUR_GONE, "<- %s has left the conversation.", display_usr); + win_vprint_line(window, '!', COLOUR_GONE, "<- %s has left the conversation.", display_usr); if (wins_is_current(window)) { wins_refresh_current(); } @@ -943,6 +1051,11 @@ _ui_outgoing_msg(const char * const from, const char * const to, window = wins_new(to, WIN_PRIVATE); } else { window = wins_new(to, WIN_CHAT); +#ifdef HAVE_LIBOTR + if (otr_is_secure(to)) { + window->is_otr = TRUE; + } +#endif } jid_destroy(jid); @@ -1309,7 +1422,7 @@ _ui_status_room(const char * const contact) if (pcontact != NULL) { win_show_contact(current, pcontact); } else { - win_print_line(current, '-', 0, "No such participant \"%s\" in room.", contact); + win_vprint_line(current, '-', 0, "No such participant \"%s\" in room.", contact); } } @@ -1609,4 +1722,10 @@ ui_init_module(void) ui_unread = _ui_unread; ui_win_unread = _ui_win_unread; ui_ask_password = _ui_ask_password; + ui_current_win_is_otr = _ui_current_win_is_otr; + ui_current_set_otr = _ui_current_set_otr; + ui_gone_secure = _ui_gone_secure; + ui_gone_insecure = _ui_gone_insecure; + ui_trust = _ui_trust; + ui_untrust = _ui_untrust; } diff --git a/src/ui/titlebar.c b/src/ui/titlebar.c index 04492b62..37827979 100644 --- a/src/ui/titlebar.c +++ b/src/ui/titlebar.c @@ -29,7 +29,7 @@ static WINDOW *title_bar; static char *current_title = NULL; -static const char *recipient = NULL; +static char *recipient = NULL; static GTimer *typing_elapsed; static int dirty; static contact_presence_t current_status; @@ -132,7 +132,8 @@ _title_bar_set_recipient(const char * const from) g_timer_destroy(typing_elapsed); typing_elapsed = NULL; } - recipient = from; + free(recipient); + recipient = strdup(from); if (current_title != NULL) { free(current_title); diff --git a/src/ui/ui.h b/src/ui/ui.h index e5d4118d..240f6b4b 100644 --- a/src/ui/ui.h +++ b/src/ui/ui.h @@ -61,6 +61,10 @@ void (*ui_handle_special_keys)(const wint_t * const ch, const char * const inp, void (*ui_switch_win)(const int i); void (*ui_next_win)(void); void (*ui_previous_win)(void); +void (*ui_gone_secure)(const char * const recipient, gboolean trusted); +void (*ui_gone_insecure)(const char * const recipient); +void (*ui_trust)(const char * const recipient); +void (*ui_untrust)(const char * const recipient); unsigned long (*ui_get_idle_time)(void); void (*ui_reset_idle_time)(void); void (*ui_new_chat_win)(const char * const to); @@ -77,6 +81,8 @@ void (*ui_close_current)(void); void (*ui_clear_current)(void); win_type_t (*ui_current_win_type)(void); int (*ui_current_win_index)(void); +gboolean (*ui_current_win_is_otr)(void); +void (*ui_current_set_otr)(gboolean value); char* (*ui_current_recipient)(void); void (*ui_current_print_line)(const char * const msg, ...); void (*ui_current_error_line)(const char * const msg); diff --git a/src/ui/window.c b/src/ui/window.c index 557cffb0..a74479d8 100644 --- a/src/ui/window.c +++ b/src/ui/window.c @@ -54,6 +54,8 @@ win_create(const char * const title, int cols, win_type_t type) new_win->unread = 0; new_win->history_shown = 0; new_win->type = type; + new_win->is_otr = FALSE; + new_win->is_trusted = FALSE; scrollok(new_win->win, TRUE); return new_win; @@ -82,6 +84,16 @@ win_print_time(ProfWin* window, char show_char) void win_print_line(ProfWin *window, const char show_char, int attrs, + const char * const msg) +{ + win_print_time(window, show_char); + wattron(window->win, attrs); + wprintw(window->win, "%s\n", msg); + wattroff(window->win, attrs); +} + +void +win_vprint_line(ProfWin *window, const char show_char, int attrs, const char * const msg, ...) { va_list arg; diff --git a/src/ui/window.h b/src/ui/window.h index 4c97429a..752787dc 100644 --- a/src/ui/window.h +++ b/src/ui/window.h @@ -48,6 +48,8 @@ typedef struct prof_win_t { char *from; WINDOW *win; win_type_t type; + gboolean is_otr; + gboolean is_trusted; int y_pos; int paged; int unread; @@ -56,8 +58,10 @@ typedef struct prof_win_t { ProfWin* win_create(const char * const title, int cols, win_type_t type); void win_free(ProfWin *window); -void win_print_line(ProfWin *self, const char show_char, int attrs, +void win_vprint_line(ProfWin *self, const char show_char, int attrs, const char * const msg, ...); +void win_print_line(ProfWin *self, const char show_char, int attrs, + const char * const msg); void win_refresh(ProfWin *window); void win_page_off(ProfWin *window); void win_print_time(ProfWin *window, char show_char);