2
* Off-the-Record Messaging plugin for pidgin
3
* Copyright (C) 2004-2005 Nikita Borisov and Ian Goldberg
6
* This program is free software; you can redistribute it and/or modify
7
* it under the terms of version 2 of the GNU General Public License as
8
* published by the Free Software Foundation.
10
* This program is distributed in the hope that it will be useful,
11
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
* GNU General Public License for more details.
15
* You should have received a copy of the GNU General Public License
16
* along with this program; if not, write to the Free Software
17
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
30
/* libgcrypt headers */
42
/* purple GTK headers */
43
#include "gtkplugin.h"
47
#include <libotr/privkey.h>
48
#include <libotr/proto.h>
49
#include <libotr/message.h>
50
#include <libotr/userstate.h>
52
/* purple-otr headers */
55
#include "otr-plugin.h"
58
/* purple-otr GTK headers */
60
#include "gtk-dialog.h"
63
/* If we're using glib on Windows, we need to use g_fopen to open files.
64
* On other platforms, it's also safe to use it. If we're not using
65
* glib, just use fopen. */
67
/* If we're cross-compiling, this might be wrong, so fix it. */
72
#include <glib/gstdio.h>
77
PurplePlugin *otrg_plugin_handle;
79
/* We'll only use the one OtrlUserState. */
80
OtrlUserState otrg_plugin_userstate = NULL;
82
/* Send an IM from the given account to the given recipient. Display an
83
* error dialog if that account isn't currently logged in. */
84
void otrg_plugin_inject_message(PurpleAccount *account, const char *recipient,
87
PurpleConnection *connection;
89
connection = purple_account_get_connection(account);
91
const char *protocol = purple_account_get_protocol_id(account);
92
const char *accountname = purple_account_get_username(account);
93
PurplePlugin *p = purple_find_prpl(protocol);
94
char *msg = g_strdup_printf("You are not currently connected to "
95
"account %s (%s).", accountname,
96
(p && p->info->name) ? p->info->name : "Unknown");
97
otrg_dialog_notify_error(accountname, protocol, recipient,
98
"Not connected", msg, NULL);
102
serv_send_im(connection, recipient, message, 0);
105
static OtrlPolicy policy_cb(void *opdata, ConnContext *context)
107
PurpleAccount *account;
108
OtrlPolicy policy = OTRL_POLICY_DEFAULT;
110
if (!context) return policy;
112
account = purple_accounts_find(context->accountname, context->protocol);
113
if (!account) return policy;
115
return otrg_ui_find_policy(account, context->username);
118
static const char *protocol_name_cb(void *opdata, const char *protocol)
120
PurplePlugin *p = purple_find_prpl(protocol);
122
return p->info->name;
125
static void protocol_name_free_cb(void *opdata, const char *protocol_name)
127
/* Do nothing, since we didn't actually allocate any memory in
128
* protocol_name_cb. */
131
/* Generate a private key for the given accountname/protocol */
132
void otrg_plugin_create_privkey(const char *accountname,
133
const char *protocol)
135
OtrgDialogWaitHandle waithandle;
138
gchar *privkeyfile = g_build_filename(purple_user_dir(), PRIVKEYFNAME, NULL);
140
fprintf(stderr, "Out of memory building filenames!\n");
143
privf = g_fopen(privkeyfile, "w+b");
146
fprintf(stderr, "Could not write private key file\n");
150
waithandle = otrg_dialog_private_key_wait_start(accountname, protocol);
152
/* Generate the key */
153
otrl_privkey_generate_FILEp(otrg_plugin_userstate, privf,
154
accountname, protocol);
156
otrg_ui_update_fingerprint();
158
/* Mark the dialog as done. */
159
otrg_dialog_private_key_wait_done(waithandle);
162
static void create_privkey_cb(void *opdata, const char *accountname,
163
const char *protocol)
165
otrg_plugin_create_privkey(accountname, protocol);
168
static int is_logged_in_cb(void *opdata, const char *accountname,
169
const char *protocol, const char *recipient)
171
PurpleAccount *account;
174
account = purple_accounts_find(accountname, protocol);
175
if (!account) return -1;
177
buddy = purple_find_buddy(account, recipient);
178
if (!buddy) return -1;
180
return (PURPLE_BUDDY_IS_ONLINE(buddy));
183
static void inject_message_cb(void *opdata, const char *accountname,
184
const char *protocol, const char *recipient, const char *message)
186
PurpleAccount *account = purple_accounts_find(accountname, protocol);
188
PurplePlugin *p = purple_find_prpl(protocol);
189
char *msg = g_strdup_printf("Unknown account %s (%s).", accountname,
190
(p && p->info->name) ? p->info->name : "Unknown");
191
otrg_dialog_notify_error(accountname, protocol, recipient,
192
"Unknown account", msg, NULL);
196
otrg_plugin_inject_message(account, recipient, message);
199
static void notify_cb(void *opdata, OtrlNotifyLevel level,
200
const char *accountname, const char *protocol, const char *username,
201
const char *title, const char *primary, const char *secondary)
203
PurpleNotifyMsgType purplelevel = PURPLE_NOTIFY_MSG_ERROR;
206
case OTRL_NOTIFY_ERROR:
207
purplelevel = PURPLE_NOTIFY_MSG_ERROR;
209
case OTRL_NOTIFY_WARNING:
210
purplelevel = PURPLE_NOTIFY_MSG_WARNING;
212
case OTRL_NOTIFY_INFO:
213
purplelevel = PURPLE_NOTIFY_MSG_INFO;
217
otrg_dialog_notify_message(purplelevel, accountname, protocol,
218
username, title, primary, secondary);
221
static int display_otr_message_cb(void *opdata, const char *accountname,
222
const char *protocol, const char *username, const char *msg)
224
return otrg_dialog_display_otr_message(accountname, protocol,
228
static void update_context_list_cb(void *opdata)
230
otrg_ui_update_keylist();
233
static void confirm_fingerprint_cb(void *opdata, OtrlUserState us,
234
const char *accountname, const char *protocol, const char *username,
235
unsigned char fingerprint[20])
237
otrg_dialog_unknown_fingerprint(us, accountname, protocol, username,
241
static void write_fingerprints_cb(void *opdata)
243
otrg_plugin_write_fingerprints();
246
static void gone_secure_cb(void *opdata, ConnContext *context)
248
otrg_dialog_connected(context);
251
static void gone_insecure_cb(void *opdata, ConnContext *context)
253
otrg_dialog_disconnected(context);
256
static void still_secure_cb(void *opdata, ConnContext *context, int is_reply)
259
otrg_dialog_stillconnected(context);
263
static void log_message_cb(void *opdata, const char *message)
265
purple_debug_info("otr", message);
268
static OtrlMessageAppOps ui_ops = {
274
display_otr_message_cb,
275
update_context_list_cb,
277
protocol_name_free_cb,
278
confirm_fingerprint_cb,
279
write_fingerprints_cb,
286
static void process_sending_im(PurpleAccount *account, char *who, char **message,
289
char *newmessage = NULL;
290
const char *accountname = purple_account_get_username(account);
291
const char *protocol = purple_account_get_protocol_id(account);
295
if (!who || !message || !*message)
298
username = strdup(purple_normalize(account, who));
300
err = otrl_message_sending(otrg_plugin_userstate, &ui_ops, NULL,
301
accountname, protocol, username, *message, NULL, &newmessage,
304
if (err && newmessage == NULL) {
305
/* Be *sure* not to send out plaintext */
306
char *ourm = strdup("");
309
} else if (newmessage) {
310
char *ourm = malloc(strlen(newmessage) + 1);
312
strcpy(ourm, newmessage);
314
otrl_message_free(newmessage);
321
/* Send the default OTR Query message to the correspondent of the given
322
* context, from the given account. [account is actually a
323
* PurpleAccount*, but it's declared here as void* so this can be passed
325
void otrg_plugin_send_default_query(ConnContext *context, void *vaccount)
327
PurpleAccount *account = vaccount;
328
char *msg = otrl_proto_default_query_msg(context->accountname,
329
otrg_ui_find_policy(account, context->username));
330
otrg_plugin_inject_message(account, context->username,
331
msg ? msg : "?OTRv2?");
335
/* Send the default OTR Query message to the correspondent of the given
337
void otrg_plugin_send_default_query_conv(PurpleConversation *conv)
339
PurpleAccount *account;
340
const char *username, *accountname;
343
account = purple_conversation_get_account(conv);
344
accountname = purple_account_get_username(account);
345
username = purple_conversation_get_name(conv);
347
msg = otrl_proto_default_query_msg(accountname,
348
otrg_ui_find_policy(account, username));
349
otrg_plugin_inject_message(account, username, msg ? msg : "?OTRv2?");
353
static gboolean process_receiving_im(PurpleAccount *account, char **who,
354
char **message, int *flags, void *m)
356
char *newmessage = NULL;
357
OtrlTLV *tlvs = NULL;
361
const char *accountname;
362
const char *protocol;
364
if (!who || !*who || !message || !*message)
367
username = strdup(purple_normalize(account, *who));
368
accountname = purple_account_get_username(account);
369
protocol = purple_account_get_protocol_id(account);
371
res = otrl_message_receiving(otrg_plugin_userstate, &ui_ops, NULL,
372
accountname, protocol, username, *message,
373
&newmessage, &tlvs, NULL, NULL);
376
char *ourm = malloc(strlen(newmessage) + 1);
378
strcpy(ourm, newmessage);
380
otrl_message_free(newmessage);
385
tlv = otrl_tlv_find(tlvs, OTRL_TLV_DISCONNECTED);
387
/* Notify the user that the other side disconnected. */
388
otrg_dialog_finished(accountname, protocol, username);
389
otrg_ui_update_keylist();
396
/* If we're supposed to ignore this incoming message (because it's a
397
* protocol message), set it to NULL, so that other plugins that
398
* catch receiving-im-msg don't return 0, and cause it to be
399
* displayed anyway. */
407
static void process_conv_create(PurpleConversation *conv, void *data)
409
if (conv) otrg_dialog_new_conv(conv);
412
static void process_connection_change(PurpleConnection *conn, void *data)
414
/* If we log in or out of a connection, make sure all of the OTR
415
* buttons are in the appropriate sensitive/insensitive state. */
416
otrg_dialog_resensitize_all();
419
static void otr_options_cb(PurpleBlistNode *node, gpointer user_data)
421
/* We've already checked PURPLE_BLIST_NODE_IS_BUDDY(node) */
422
PurpleBuddy *buddy = (PurpleBuddy *)node;
424
/* Modify the settings for this buddy */
425
otrg_ui_config_buddy(buddy);
428
static void supply_extended_menu(PurpleBlistNode *node, GList **menu)
430
PurpleMenuAction *act;
435
if (!PURPLE_BLIST_NODE_IS_BUDDY(node)) return;
437
/* Extract the account, and then the protocol, for this buddy */
438
buddy = (PurpleBuddy *)node;
439
acct = buddy->account;
440
if (acct == NULL) return;
441
proto = purple_account_get_protocol_id(acct);
442
if (!otrg_plugin_proto_supports_otr(proto)) return;
444
act = purple_menu_action_new("OTR Settings", (PurpleCallback)otr_options_cb,
446
*menu = g_list_append(*menu, act);
449
/* Disconnect a context, sending a notice to the other side, if
451
void otrg_plugin_disconnect(ConnContext *context)
453
otrl_message_disconnect(otrg_plugin_userstate, &ui_ops, NULL,
454
context->accountname, context->protocol, context->username);
457
/* Write the fingerprints to disk. */
458
void otrg_plugin_write_fingerprints(void)
461
gchar *storefile = g_build_filename(purple_user_dir(), STOREFNAME, NULL);
462
storef = g_fopen(storefile, "wb");
465
otrl_privkey_write_fingerprints_FILEp(otrg_plugin_userstate, storef);
469
/* Find the ConnContext appropriate to a given PurpleConversation. */
470
ConnContext *otrg_plugin_conv_to_context(PurpleConversation *conv)
472
PurpleAccount *account;
474
const char *accountname, *proto;
475
ConnContext *context;
477
account = purple_conversation_get_account(conv);
478
accountname = purple_account_get_username(account);
479
proto = purple_account_get_protocol_id(account);
481
purple_normalize(account, purple_conversation_get_name(conv)));
483
context = otrl_context_find(otrg_plugin_userstate, username, accountname,
484
proto, 0, NULL, NULL, NULL);
490
/* Find the PurpleConversation appropriate to the given ConnContext. If
491
* one doesn't yet exist, create it if force_create is true. */
492
PurpleConversation *otrg_plugin_context_to_conv(ConnContext *context,
495
PurpleAccount *account;
496
PurpleConversation *conv;
498
account = purple_accounts_find(context->accountname, context->protocol);
499
if (account == NULL) return NULL;
501
conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, context->username, account);
502
if (conv == NULL && force_create) {
503
conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, context->username);
509
/* What level of trust do we have in the privacy of this ConnContext? */
510
TrustLevel otrg_plugin_context_to_trust(ConnContext *context)
512
TrustLevel level = TRUST_NOT_PRIVATE;
514
if (context && context->msgstate == OTRL_MSGSTATE_ENCRYPTED) {
515
if (context->active_fingerprint->trust &&
516
context->active_fingerprint->trust[0] != '\0') {
517
level = TRUST_PRIVATE;
519
level = TRUST_UNVERIFIED;
521
} else if (context && context->msgstate == OTRL_MSGSTATE_FINISHED) {
522
level = TRUST_FINISHED;
528
/* Send the OTRL_TLV_DISCONNECTED packets when we're about to quit. */
529
static void process_quitting(void)
531
ConnContext *context = otrg_plugin_userstate->context_root;
533
ConnContext *next = context->next;
534
if (context->msgstate == OTRL_MSGSTATE_ENCRYPTED &&
535
context->protocol_version > 1) {
536
otrg_plugin_disconnect(context);
542
static gboolean otr_plugin_load(PurplePlugin *handle)
544
gchar *privkeyfile = g_build_filename(purple_user_dir(), PRIVKEYFNAME,
546
gchar *storefile = g_build_filename(purple_user_dir(), STOREFNAME, NULL);
547
void *conv_handle = purple_conversations_get_handle();
548
void *conn_handle = purple_connections_get_handle();
549
void *blist_handle = purple_blist_get_handle();
550
void *core_handle = purple_get_core();
554
if (!privkeyfile || !storefile) {
560
privf = g_fopen(privkeyfile, "rb");
561
storef = g_fopen(storefile, "rb");
565
otrg_plugin_handle = handle;
567
/* Make our OtrlUserState; we'll only use the one. */
568
otrg_plugin_userstate = otrl_userstate_create();
570
otrl_privkey_read_FILEp(otrg_plugin_userstate, privf);
571
otrl_privkey_read_fingerprints_FILEp(otrg_plugin_userstate, storef,
573
if (privf) fclose(privf);
574
if (storef) fclose(storef);
576
otrg_ui_update_fingerprint();
578
purple_signal_connect(core_handle, "quitting", otrg_plugin_handle,
579
PURPLE_CALLBACK(process_quitting), NULL);
580
purple_signal_connect(conv_handle, "sending-im-msg", otrg_plugin_handle,
581
PURPLE_CALLBACK(process_sending_im), NULL);
582
purple_signal_connect(conv_handle, "receiving-im-msg", otrg_plugin_handle,
583
PURPLE_CALLBACK(process_receiving_im), NULL);
584
purple_signal_connect(conv_handle, "conversation-created",
585
otrg_plugin_handle, PURPLE_CALLBACK(process_conv_create), NULL);
586
purple_signal_connect(conn_handle, "signed-on", otrg_plugin_handle,
587
PURPLE_CALLBACK(process_connection_change), NULL);
588
purple_signal_connect(conn_handle, "signed-off", otrg_plugin_handle,
589
PURPLE_CALLBACK(process_connection_change), NULL);
590
purple_signal_connect(blist_handle, "blist-node-extended-menu",
591
otrg_plugin_handle, PURPLE_CALLBACK(supply_extended_menu), NULL);
593
purple_conversation_foreach(otrg_dialog_new_conv);
598
static gboolean otr_plugin_unload(PurplePlugin *handle)
600
void *conv_handle = purple_conversations_get_handle();
601
void *conn_handle = purple_connections_get_handle();
602
void *blist_handle = purple_blist_get_handle();
603
void *core_handle = purple_get_core();
605
/* Clean up all of our state. */
606
otrl_userstate_free(otrg_plugin_userstate);
607
otrg_plugin_userstate = NULL;
609
purple_signal_disconnect(core_handle, "quitting", otrg_plugin_handle,
610
PURPLE_CALLBACK(process_quitting));
611
purple_signal_disconnect(conv_handle, "sending-im-msg", otrg_plugin_handle,
612
PURPLE_CALLBACK(process_sending_im));
613
purple_signal_disconnect(conv_handle, "receiving-im-msg", otrg_plugin_handle,
614
PURPLE_CALLBACK(process_receiving_im));
615
purple_signal_disconnect(conv_handle, "conversation-created",
616
otrg_plugin_handle, PURPLE_CALLBACK(process_conv_create));
617
purple_signal_disconnect(conn_handle, "signed-on", otrg_plugin_handle,
618
PURPLE_CALLBACK(process_connection_change));
619
purple_signal_disconnect(conn_handle, "signed-off", otrg_plugin_handle,
620
PURPLE_CALLBACK(process_connection_change));
621
purple_signal_disconnect(blist_handle, "blist-node-extended-menu",
622
otrg_plugin_handle, PURPLE_CALLBACK(supply_extended_menu));
624
purple_conversation_foreach(otrg_dialog_remove_conv);
629
/* Return 1 if the given protocol supports OTR, 0 otherwise. */
630
int otrg_plugin_proto_supports_otr(const char *proto)
632
/* IRC is the only protocol we know of that OTR doesn't work on (its
633
* maximum message size is too small to fit a Key Exchange Message). */
634
if (proto && !strcmp(proto, "prpl-irc")) {
642
static PurplePluginUiInfo ui_info =
644
otrg_gtk_ui_make_widget
647
#define UI_INFO &ui_info
648
#define PLUGIN_TYPE PIDGIN_PLUGIN_TYPE
653
#define PLUGIN_TYPE ""
657
static PurplePluginInfo info =
661
/* Use the 2.0.x API */
662
2, /* major version */
663
0, /* minor version */
665
PURPLE_PLUGIN_STANDARD, /* type */
666
PLUGIN_TYPE, /* ui_requirement */
668
NULL, /* dependencies */
669
PURPLE_PRIORITY_DEFAULT, /* priority */
671
"Off-the-Record Messaging", /* name */
672
PIDGIN_OTR_VERSION, /* version */
674
"Provides private and secure conversations",
676
"Preserves the privacy of IM communications by providing "
677
"encryption, authentication, deniability, and perfect "
680
"Nikita Borisov and Ian Goldberg\n\t\t\t<otr@cypherpunks.ca>",
681
"http://www.cypherpunks.ca/otr/", /* homepage */
683
otr_plugin_load, /* load */
684
otr_plugin_unload, /* unload */
687
UI_INFO, /* ui_info */
688
NULL, /* extra_info */
689
NULL, /* prefs_info */
694
__init_plugin(PurplePlugin *plugin)
696
/* Set up the UI ops */
698
otrg_ui_set_ui_ops(otrg_gtk_ui_get_ui_ops());
699
otrg_dialog_set_ui_ops(otrg_gtk_dialog_get_ui_ops());
702
/* Initialize the OTR library */
706
PURPLE_INIT_PLUGIN(otr, __init_plugin, info)