forked from PsychoticNinja/irssi
Fun race condition there, got this when testing with the jabber xmlconsole open in bitlbee
405 lines
9.5 KiB
C
405 lines
9.5 KiB
C
/*
|
|
* 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);
|
|
}
|