mirror of
https://github.com/irssi/irssi.git
synced 2024-11-03 04:27:19 -05:00
363 lines
11 KiB
C
363 lines
11 KiB
C
/*
|
|
* Off-the-Record Messaging (OTR) modules for IRC
|
|
* Copyright (C) 2008 Uli Meis <a.sporto+bee@gmail.com>
|
|
*
|
|
* 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
|
|
*/
|
|
|
|
#include <irssi/src/common.h>
|
|
|
|
#include <irssi/src/core/signals.h>
|
|
#include <irssi/src/core/levels.h>
|
|
#include <irssi/src/fe-common/core/printtext.h>
|
|
#include <irssi/src/fe-common/core/fe-windows.h>
|
|
|
|
#include <irssi/src/otr/key.h>
|
|
#include "module.h"
|
|
#include <irssi/src/otr/otr-formats.h>
|
|
#include <irssi/src/otr/irssi-otr.h>
|
|
|
|
static OtrlPolicy OTR_DEFAULT_POLICY = OTRL_POLICY_MANUAL | OTRL_POLICY_WHITESPACE_START_AKE;
|
|
|
|
/*
|
|
* Return default policy for now.
|
|
*/
|
|
static OtrlPolicy ops_policy(void *opdata, ConnContext *context)
|
|
{
|
|
return OTR_DEFAULT_POLICY;
|
|
}
|
|
|
|
/*
|
|
* Request for key generation.
|
|
*
|
|
* The lib actually expects us to be finished before the call returns. Since
|
|
* this can take more than an hour on some systems there isn't even a point in
|
|
* trying...
|
|
*/
|
|
static void ops_create_privkey(void *opdata, const char *accountname,
|
|
const char *protocol)
|
|
{
|
|
key_gen_run(user_state_global, accountname);
|
|
}
|
|
|
|
/*
|
|
* Inject OTR message.
|
|
*/
|
|
static void ops_inject_msg(void *opdata, const char *accountname,
|
|
const char *protocol, const char *recipient, const char *message)
|
|
{
|
|
SERVER_REC *server = opdata;
|
|
|
|
IRSSI_OTR_DEBUG("Inject msg:\n[%s]", message);
|
|
otr_send_message(server, recipient, message);
|
|
}
|
|
|
|
/*
|
|
* Gone secure.
|
|
*/
|
|
static void ops_secure(void *opdata, ConnContext *context)
|
|
{
|
|
char ownfp[OTRL_PRIVKEY_FPRINT_HUMAN_LEN];
|
|
char peerfp[OTRL_PRIVKEY_FPRINT_HUMAN_LEN];
|
|
SERVER_REC *server = opdata;
|
|
struct otr_peer_context *opc;
|
|
|
|
g_return_if_fail(context != NULL);
|
|
/* This should *really* not happened */
|
|
g_return_if_fail(context->msgstate == OTRL_MSGSTATE_ENCRYPTED);
|
|
|
|
printformat(server, context->username, MSGLEVEL_CLIENTCRAP, TXT_OTR_SESSION_SECURE);
|
|
otr_status_change(server, context->username, OTR_STATUS_GONE_SECURE);
|
|
|
|
opc = context->app_data;
|
|
opc->active_fingerprint = context->active_fingerprint;
|
|
|
|
if (otrl_context_is_fingerprint_trusted(context->active_fingerprint)) {
|
|
/* Secure and trusted */
|
|
return;
|
|
}
|
|
|
|
/* Not authenticated. Let's print out the fingerprints for comparison. */
|
|
otrl_privkey_hash_to_human(peerfp, context->active_fingerprint->fingerprint);
|
|
otrl_privkey_fingerprint(user_state_global->otr_state, ownfp, context->accountname, OTR_PROTOCOL_ID);
|
|
|
|
printformat(server, context->username, MSGLEVEL_CLIENTCRAP, TXT_OTR_SESSION_UNAUTHENTICATED_WARNING);
|
|
printformat(server, context->username, MSGLEVEL_CLIENTCRAP, TXT_OTR_FP_INFO, server->nick, ownfp);
|
|
printformat(server, context->username, MSGLEVEL_CLIENTCRAP, TXT_OTR_FP_INFO, context->username, peerfp);
|
|
}
|
|
|
|
/*
|
|
* Gone insecure.
|
|
*/
|
|
static void ops_insecure(void *opdata, ConnContext *context)
|
|
{
|
|
SERVER_REC *server = opdata;
|
|
|
|
printformat(server, context->username, MSGLEVEL_CLIENTCRAP, TXT_OTR_SESSION_INSECURE);
|
|
otr_status_change(server, context->username, OTR_STATUS_GONE_INSECURE);
|
|
}
|
|
|
|
/*
|
|
* Really critical with IRC. Unfortunately, we can't tell our peer which size
|
|
* to use.
|
|
*/
|
|
static int ops_max_msg(void *opdata, ConnContext *context)
|
|
{
|
|
return OTR_MAX_MSG_SIZE;
|
|
}
|
|
|
|
static void ops_handle_msg_event(void *opdata, OtrlMessageEvent msg_event, ConnContext *context, const char *message, gcry_error_t err)
|
|
{
|
|
SERVER_REC *server = opdata;
|
|
char *username = context->username;
|
|
|
|
switch (msg_event) {
|
|
case OTRL_MSGEVENT_NONE:
|
|
break;
|
|
case OTRL_MSGEVENT_ENCRYPTION_REQUIRED:
|
|
printformat(server, username, MSGLEVEL_CLIENTERROR, TXT_OTR_MSG_ENCRYPTION_REQUIRED);
|
|
break;
|
|
case OTRL_MSGEVENT_ENCRYPTION_ERROR:
|
|
printformat(server, username, MSGLEVEL_CLIENTERROR, TXT_OTR_MSG_ENCRYPTION_ERROR);
|
|
break;
|
|
case OTRL_MSGEVENT_CONNECTION_ENDED:
|
|
printformat(server, username, MSGLEVEL_CLIENTERROR, TXT_OTR_MSG_ENCRYPTION_ENDED, username);
|
|
break;
|
|
case OTRL_MSGEVENT_SETUP_ERROR:
|
|
if (!err) {
|
|
err = GPG_ERR_INV_VALUE;
|
|
}
|
|
switch (err) {
|
|
case GPG_ERR_INV_VALUE:
|
|
printformat(server, username, MSGLEVEL_CLIENTERROR, TXT_OTR_MSG_MALFORMED, username);
|
|
break;
|
|
default:
|
|
printformat(server, username, MSGLEVEL_CLIENTERROR, TXT_OTR_MSG_ERROR, gcry_strerror(err));
|
|
break;
|
|
}
|
|
break;
|
|
case OTRL_MSGEVENT_MSG_REFLECTED:
|
|
printformat(server, username, MSGLEVEL_CLIENTERROR, TXT_OTR_MSG_REFLECTED, username);
|
|
break;
|
|
case OTRL_MSGEVENT_MSG_RESENT:
|
|
printformat(server, username, MSGLEVEL_CLIENTERROR, TXT_OTR_MSG_RESENT, username, message);
|
|
break;
|
|
case OTRL_MSGEVENT_RCVDMSG_NOT_IN_PRIVATE:
|
|
printformat(server, username, MSGLEVEL_CLIENTERROR, TXT_OTR_MSG_NOT_IN_PRIVATE, username);
|
|
break;
|
|
case OTRL_MSGEVENT_RCVDMSG_UNREADABLE:
|
|
printformat(server, username, MSGLEVEL_CLIENTERROR, TXT_OTR_MSG_UNREADABLE, username);
|
|
break;
|
|
case OTRL_MSGEVENT_RCVDMSG_MALFORMED:
|
|
printformat(server, username, MSGLEVEL_CLIENTERROR, TXT_OTR_MSG_MALFORMED, username);
|
|
break;
|
|
case OTRL_MSGEVENT_LOG_HEARTBEAT_RCVD:
|
|
IRSSI_OTR_DEBUG("Heartbeat received from %s.", username);
|
|
break;
|
|
case OTRL_MSGEVENT_LOG_HEARTBEAT_SENT:
|
|
IRSSI_OTR_DEBUG("Heartbeat sent to %s.", username);
|
|
break;
|
|
case OTRL_MSGEVENT_RCVDMSG_GENERAL_ERR:
|
|
printformat(server, username, MSGLEVEL_CLIENTERROR, TXT_OTR_MSG_ERROR, message);
|
|
break;
|
|
case OTRL_MSGEVENT_RCVDMSG_UNENCRYPTED:
|
|
printformat(server, username, MSGLEVEL_CLIENTERROR, TXT_OTR_MSG_UNENCRYPTED, username);
|
|
|
|
/*
|
|
* This is a hack I found to send the message in a private window of
|
|
* the username without creating an infinite loop since the 'message
|
|
* private' signal is hijacked in this module. If someone is able to
|
|
* clean this up with a more elegant solution, by all means PLEASE
|
|
* submit a patch or email me a better way.
|
|
*/
|
|
signal_remove("message private", (SIGNAL_FUNC) sig_message_private);
|
|
signal_emit("message private", 5, server, message, username, server->connrec->address, server->nick);
|
|
signal_add_first("message private", (SIGNAL_FUNC) sig_message_private);
|
|
break;
|
|
case OTRL_MSGEVENT_RCVDMSG_UNRECOGNIZED:
|
|
printformat(server, username, MSGLEVEL_CLIENTERROR, TXT_OTR_MSG_UNRECOGNIZED, username);
|
|
break;
|
|
case OTRL_MSGEVENT_RCVDMSG_FOR_OTHER_INSTANCE:
|
|
IRSSI_OTR_DEBUG("%s has sent a message for a different instance.", username);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* A context changed.
|
|
*/
|
|
static void ops_up_ctx_list(void *opdata)
|
|
{
|
|
otr_status_change(opdata, NULL, OTR_STATUS_CTX_UPDATE);
|
|
}
|
|
|
|
/*
|
|
* Save fingerprint changes.
|
|
*/
|
|
static void ops_write_fingerprints(void *data)
|
|
{
|
|
key_write_fingerprints(user_state_global);
|
|
}
|
|
|
|
static int ops_is_logged_in(void *opdata, const char *accountname, const char *protocol, const char *recipient)
|
|
{
|
|
int ret;
|
|
SERVER_REC *server = opdata;
|
|
|
|
/* Logged in? */
|
|
ret = server != NULL;
|
|
|
|
IRSSI_OTR_DEBUG("User %s %s logged in", accountname, ret ? "" : "not");
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void ops_create_instag(void *opdata, const char *accountname,
|
|
const char *protocol)
|
|
{
|
|
otrl_instag_generate(user_state_global->otr_state, "/dev/null", accountname, protocol);
|
|
key_write_instags(user_state_global);
|
|
}
|
|
|
|
static void ops_smp_event(void *opdata, OtrlSMPEvent smp_event,
|
|
ConnContext *context, unsigned short progress_percent, char *question)
|
|
{
|
|
SERVER_REC *server = opdata;
|
|
const char *from = context->username;
|
|
struct otr_peer_context *opc = context->app_data;
|
|
|
|
/*
|
|
* Without a peer context, we can't update the status bar. Code flow error
|
|
* if none is found. This context is created automatically by an otrl_*
|
|
* call or if non existent when returned from
|
|
* otrl_message_sending/receiving.
|
|
*/
|
|
g_return_if_fail(opc != NULL);
|
|
|
|
opc->smp_event = smp_event;
|
|
|
|
switch (smp_event) {
|
|
case OTRL_SMPEVENT_ASK_FOR_SECRET:
|
|
printformat(server, from, MSGLEVEL_CLIENTCRAP, TXT_OTR_SMP_SECRET_QUESTION, from);
|
|
opc->ask_secret = 1;
|
|
otr_status_change(server, from, OTR_STATUS_SMP_INCOMING);
|
|
break;
|
|
case OTRL_SMPEVENT_ASK_FOR_ANSWER:
|
|
printformat(server, from, MSGLEVEL_CLIENTCRAP, TXT_OTR_SMP_ANSWER_HEADER, from);
|
|
printformat(server, from, MSGLEVEL_CLIENTCRAP, TXT_OTR_SMP_ANSWER_QUESTION, question);
|
|
printformat(server, from, MSGLEVEL_CLIENTCRAP, TXT_OTR_SMP_ANSWER_FOOTER);
|
|
opc->ask_secret = 1;
|
|
otr_status_change(server, from, OTR_STATUS_SMP_INCOMING);
|
|
break;
|
|
case OTRL_SMPEVENT_IN_PROGRESS:
|
|
printformat(server, from, MSGLEVEL_CLIENTCRAP, TXT_OTR_SMP_IN_PROGRESS, from);
|
|
otr_status_change(server, from, OTR_STATUS_SMP_FINALIZE);
|
|
break;
|
|
case OTRL_SMPEVENT_SUCCESS:
|
|
printformat(server, from, MSGLEVEL_CLIENTCRAP, TXT_OTR_SMP_SUCCESS, from);
|
|
otr_status_change(server, from, OTR_STATUS_SMP_SUCCESS);
|
|
break;
|
|
case OTRL_SMPEVENT_ABORT:
|
|
otr_auth_abort(server, context->username);
|
|
otr_status_change(server, from, OTR_STATUS_SMP_ABORTED);
|
|
break;
|
|
case OTRL_SMPEVENT_FAILURE:
|
|
case OTRL_SMPEVENT_CHEATED:
|
|
case OTRL_SMPEVENT_ERROR:
|
|
printformat(server, from, MSGLEVEL_CLIENTERROR, TXT_OTR_SMP_FAILURE, from);
|
|
otr_status_change(server, from, OTR_STATUS_SMP_FAILED);
|
|
break;
|
|
default:
|
|
g_warning("Received unknown SMP event: %d", smp_event);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* timer_control callback.
|
|
*/
|
|
static void ops_timer_control(void *opdata, unsigned int interval)
|
|
{
|
|
otr_control_timer(interval, opdata);
|
|
}
|
|
|
|
/*
|
|
* Handle otr error message.
|
|
*/
|
|
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("Error occurred encrypting message.");
|
|
break;
|
|
case OTRL_ERRCODE_MSG_NOT_IN_PRIVATE:
|
|
if (context) {
|
|
msg = strdup("You sent encrypted data which was unexpected");
|
|
}
|
|
break;
|
|
case OTRL_ERRCODE_MSG_UNREADABLE:
|
|
msg = strdup("You transmitted an unreadable encrypted message");
|
|
break;
|
|
case OTRL_ERRCODE_MSG_MALFORMED:
|
|
msg = strdup("You transmitted a malformed data message.");
|
|
break;
|
|
}
|
|
|
|
return msg;
|
|
}
|
|
|
|
/*
|
|
* Free otr error message callback.
|
|
*/
|
|
static void ops_otr_error_message_free(void *opdata, const char *err_msg)
|
|
{
|
|
g_free_not_null((char *)err_msg);
|
|
}
|
|
|
|
/*
|
|
* Assign OTR message operations.
|
|
*/
|
|
OtrlMessageAppOps otr_ops = {
|
|
ops_policy,
|
|
ops_create_privkey,
|
|
ops_is_logged_in,
|
|
ops_inject_msg,
|
|
ops_up_ctx_list,
|
|
NULL, /* new_fingerprint */
|
|
ops_write_fingerprints,
|
|
ops_secure,
|
|
ops_insecure,
|
|
NULL, /* still_secure */
|
|
ops_max_msg,
|
|
NULL, /* account_name */
|
|
NULL, /* account_name_free */
|
|
NULL, /* received_symkey */
|
|
ops_otr_error_message,
|
|
ops_otr_error_message_free,
|
|
NULL, /* resent_msg_prefix */
|
|
NULL, /* resent_msg_prefix_free */
|
|
ops_smp_event,
|
|
ops_handle_msg_event,
|
|
ops_create_instag,
|
|
NULL, /* convert_msg */
|
|
NULL, /* convert_free */
|
|
ops_timer_control,
|
|
};
|