summaryrefslogtreecommitdiff
path: root/src/otr/key.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/otr/key.c')
-rw-r--r--src/otr/key.c404
1 files changed, 404 insertions, 0 deletions
diff --git a/src/otr/key.c b/src/otr/key.c
new file mode 100644
index 00000000..7fee4084
--- /dev/null
+++ b/src/otr/key.c
@@ -0,0 +1,404 @@
+/*
+ * Off-the-Record Messaging (OTR) modules for IRC
+ *
+ * Copyright (C) 2008 - Uli Meis <a.sporto+bee@gmail.com>
+ * 2012 - 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 as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * 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 <glib.h>
+#include <libgen.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/poll.h>
+#include <signal.h>
+#include <unistd.h>
+
+#include "key.h"
+
+#include "levels.h"
+#include "network.h"
+#include "pidwait.h"
+#include "printtext.h"
+
+#include "irssi-otr.h"
+#include "otr-formats.h"
+
+/*
+ * Status of key generation.
+ */
+enum key_gen_status {
+ KEY_GEN_IDLE = 0,
+ KEY_GEN_STARTED = 1,
+ KEY_GEN_RUNNING = 2,
+ KEY_GEN_FINISHED = 3,
+ KEY_GEN_ERROR = 4,
+};
+
+/*
+ * Data of the state of key generation.
+ */
+struct key_gen_data {
+ struct otr_user_state *ustate;
+ char *account_name;
+ char *key_file_path;
+ enum key_gen_status status;
+ gcry_error_t gcry_error;
+};
+
+/*
+ * Event from the key generation process.
+ */
+struct key_gen_event {
+ enum key_gen_status status;
+ gcry_error_t error;
+};
+
+/*
+ * Key generation process.
+ */
+struct key_gen_worker {
+ int tag;
+ GIOChannel *pipes[2];
+};
+
+/*
+ * Key generation data for the thread in charge of creating the key.
+ */
+static struct key_gen_data key_gen_state = {
+ .status = KEY_GEN_IDLE,
+ .gcry_error = GPG_ERR_NO_ERROR,
+};
+
+/*
+ * Build file path concatenate to the irssi config dir.
+ */
+static char *file_path_build(const char *path)
+{
+ g_return_val_if_fail(path != NULL, NULL);
+
+ /* Either NULL or the filename is returned here which is valid. */
+ return g_strdup_printf("%s/%s", get_irssi_dir(), path);
+}
+
+/*
+ * Emit a key generation status event.
+ */
+static void emit_event(GIOChannel *pipe, enum key_gen_status status, gcry_error_t error)
+{
+ struct key_gen_event event;
+
+ g_return_if_fail(pipe != NULL);
+
+ event.status = status;
+ event.error = error;
+
+ g_io_channel_write_block(pipe, &event, sizeof(event));
+}
+
+/*
+ * Reset key generation state and status is IDLE.
+ */
+static void reset_key_gen_state(void)
+{
+ /* Safety. */
+ g_free(key_gen_state.key_file_path);
+ g_free(key_gen_state.account_name);
+
+ /* Nullify everything. */
+ memset(&key_gen_state, 0, sizeof(key_gen_state));
+ key_gen_state.status = KEY_GEN_IDLE;
+ key_gen_state.gcry_error = GPG_ERR_NO_ERROR;
+}
+
+/*
+ * Read status event from key generation worker.
+ */
+static void read_key_gen_status(struct key_gen_worker *worker, GIOChannel *pipe)
+{
+ struct key_gen_event event;
+ gcry_error_t err;
+
+ g_return_if_fail(worker != NULL);
+
+ fcntl(g_io_channel_unix_get_fd(pipe), F_SETFL, O_NONBLOCK);
+
+ if (g_io_channel_read_block(pipe, &event, sizeof(event)) == -1) {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTERROR,
+ TXT_OTR_KEYGEN_FAILED,
+ key_gen_state.account_name,
+ g_strerror(errno));
+ return;
+ }
+
+ key_gen_state.status = event.status;
+ key_gen_state.gcry_error = event.error;
+
+ if (event.status == KEY_GEN_FINISHED || event.status == KEY_GEN_ERROR) {
+ /* Worker is done. */
+ g_source_remove(worker->tag);
+
+ g_io_channel_shutdown(worker->pipes[0], TRUE, NULL);
+ g_io_channel_unref(worker->pipes[0]);
+
+ g_io_channel_shutdown(worker->pipes[1], TRUE, NULL);
+ g_io_channel_unref(worker->pipes[1]);
+
+ g_free(worker);
+
+ if (event.status == KEY_GEN_ERROR) {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTERROR,
+ TXT_OTR_KEYGEN_FAILED,
+ key_gen_state.account_name,
+ gcry_strerror(key_gen_state.gcry_error));
+ reset_key_gen_state();
+ return;
+ }
+
+ err = otrl_privkey_read(key_gen_state.ustate->otr_state, key_gen_state.key_file_path);
+
+ if (err != GPG_ERR_NO_ERROR) {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTERROR,
+ TXT_OTR_KEYGEN_FAILED,
+ key_gen_state.account_name,
+ gcry_strerror(key_gen_state.gcry_error));
+ } else {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
+ TXT_OTR_KEYGEN_COMPLETED,
+ key_gen_state.account_name);
+ }
+
+ reset_key_gen_state();
+ }
+}
+
+/*
+ * Run key generation in a seperate process (takes ages). The other process
+ * will rewrite the key file, we shouldn't change anything till it's done and
+ * we've reloaded the keys.
+ */
+void key_gen_run(struct otr_user_state *ustate, const char *account_name)
+{
+ struct key_gen_worker *worker;
+ int fd[2];
+ gcry_error_t err;
+ pid_t pid;
+
+ g_return_if_fail(ustate != NULL);
+ g_return_if_fail(account_name != NULL);
+
+ if (key_gen_state.status != KEY_GEN_IDLE) {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_OTR_KEYGEN_RUNNING, key_gen_state.account_name);
+ return;
+ }
+
+ /* Make sure the pointer does not go away during the proess. */
+ key_gen_state.account_name = strdup(account_name);
+ key_gen_state.ustate = ustate;
+ key_gen_state.status = KEY_GEN_STARTED;
+
+ /* Creating key file path. */
+ key_gen_state.key_file_path = file_path_build(OTR_KEYFILE);
+ if (key_gen_state.key_file_path == NULL) {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTERROR,
+ TXT_OTR_KEYGEN_FAILED,
+ key_gen_state.account_name,
+ g_strerror(errno));
+ reset_key_gen_state();
+ return;
+ }
+
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_OTR_KEYGEN_STARTED, key_gen_state.account_name);
+
+ if (pipe(fd) != 0) {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTERROR,
+ TXT_OTR_KEYGEN_FAILED,
+ key_gen_state.account_name,
+ g_strerror(errno));
+ reset_key_gen_state();
+ return;
+ }
+
+ worker = g_new0(struct key_gen_worker, 1);
+
+ if (worker == NULL) {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTERROR,
+ TXT_OTR_KEYGEN_FAILED,
+ key_gen_state.account_name,
+ g_strerror(errno));
+ reset_key_gen_state();
+ return;
+ }
+
+ worker->pipes[0] = g_io_channel_new(fd[0]);
+ worker->pipes[1] = g_io_channel_new(fd[1]);
+
+ pid = fork();
+
+ if (pid > 0) {
+ /* Parent process */
+ pidwait_add(pid);
+ worker->tag = g_input_add(worker->pipes[0], G_INPUT_READ, (GInputFunction)read_key_gen_status, worker);
+ return;
+ }
+
+ if (pid != 0) {
+ /* error */
+ g_warning("Key generation failed: %s", g_strerror(errno));
+
+ g_source_remove(worker->tag);
+
+ g_io_channel_shutdown(worker->pipes[0], TRUE, NULL);
+ g_io_channel_unref(worker->pipes[0]);
+
+ g_io_channel_shutdown(worker->pipes[1], TRUE, NULL);
+ g_io_channel_unref(worker->pipes[1]);
+
+ g_free(worker);
+
+ return;
+ }
+
+ /* Child process */
+ key_gen_state.status = KEY_GEN_RUNNING;
+ emit_event(worker->pipes[1], KEY_GEN_RUNNING, GPG_ERR_NO_ERROR);
+
+ err = otrl_privkey_generate(key_gen_state.ustate->otr_state, key_gen_state.key_file_path, key_gen_state.account_name, OTR_PROTOCOL_ID);
+
+ if (err != GPG_ERR_NO_ERROR) {
+ emit_event(worker->pipes[1], KEY_GEN_ERROR, err);
+ _exit(99);
+ return;
+ }
+
+ emit_event(worker->pipes[1], KEY_GEN_FINISHED, GPG_ERR_NO_ERROR);
+
+ _exit(99);
+}
+
+/*
+ * Write fingerprints to file.
+ */
+void key_write_fingerprints(struct otr_user_state *ustate)
+{
+ gcry_error_t err;
+ char *filename;
+
+ g_return_if_fail(ustate != NULL);
+
+ filename = file_path_build(OTR_FINGERPRINTS_FILE);
+ g_return_if_fail(filename != NULL);
+
+ err = otrl_privkey_write_fingerprints(ustate->otr_state, filename);
+ if (err == GPG_ERR_NO_ERROR) {
+ IRSSI_OTR_DEBUG("Fingerprints saved to %9%s%9", filename);
+ } else {
+ IRSSI_OTR_DEBUG("Error writing fingerprints: %d (%d)",
+ gcry_strerror(err), gcry_strsource(err));
+ }
+
+ g_free(filename);
+}
+
+/*
+ * Write instance tags to file.
+ */
+void key_write_instags(struct otr_user_state *ustate)
+{
+ gcry_error_t err;
+ char *filename;
+
+ g_return_if_fail(ustate != NULL);
+
+ filename = file_path_build(OTR_INSTAG_FILE);
+ g_return_if_fail(filename != NULL);
+
+ err = otrl_instag_write(ustate->otr_state, filename);
+ if (err == GPG_ERR_NO_ERROR) {
+ IRSSI_OTR_DEBUG("Instance tags saved in %9%s%9", filename);
+ } else {
+ IRSSI_OTR_DEBUG("Error saving instance tags: %d (%d)",
+ gcry_strerror(err), gcry_strsource(err));
+ }
+
+ g_free(filename);
+}
+
+/*
+ * Load private keys.
+ */
+void key_load(struct otr_user_state *ustate)
+{
+ int ret;
+ gcry_error_t err;
+ char *filename;
+
+ g_return_if_fail(ustate != NULL);
+
+ filename = file_path_build(OTR_KEYFILE);
+ g_return_if_fail(filename != NULL);
+
+ ret = access(filename, F_OK);
+ if (ret < 0) {
+ IRSSI_OTR_DEBUG("No private keys found in %9%s%9", filename);
+ g_free(filename);
+ return;
+ }
+
+ err = otrl_privkey_read(ustate->otr_state, filename);
+ if (err == GPG_ERR_NO_ERROR) {
+ IRSSI_OTR_DEBUG("Private keys loaded from %9%s%9", filename);
+ } else {
+ IRSSI_OTR_DEBUG("Error loading private keys: %d (%d)",
+ gcry_strerror(err), gcry_strsource(err));
+ }
+
+ g_free(filename);
+}
+
+/*
+ * Load fingerprints.
+ */
+void key_load_fingerprints(struct otr_user_state *ustate)
+{
+ int ret;
+ gcry_error_t err;
+ char *filename;
+
+ g_return_if_fail(ustate != NULL);
+
+ filename = file_path_build(OTR_FINGERPRINTS_FILE);
+ g_return_if_fail(filename != NULL);
+
+ ret = access(filename, F_OK);
+ if (ret < 0) {
+ IRSSI_OTR_DEBUG("No fingerprints found in %9%s%9", filename);
+ g_free(filename);
+ return;
+ }
+
+ err = otrl_privkey_read_fingerprints(ustate->otr_state, filename, NULL,
+ NULL);
+ if (err == GPG_ERR_NO_ERROR) {
+ IRSSI_OTR_DEBUG("Fingerprints loaded from %9%s%9", filename);
+ } else {
+ IRSSI_OTR_DEBUG("Error loading fingerprints: %d (%d)",
+ gcry_strerror(err), gcry_strsource(err));
+ }
+
+ g_free(filename);
+}