1 /*
2 * Off-the-Record Messaging plugin for pidgin
3 * Copyright (C) 2004-2016 Ian Goldberg, Rob Smits,
4 * Chris Alexander, Willy Lew,
5 * Lisa Du, Nikita Borisov
6 * <otr@cypherpunks.ca>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of version 2 of the GNU General Public License as
10 * published by the Free Software Foundation.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 */
21
22 /* config.h */
23 #ifdef HAVE_CONFIG_H
24 #include "config.h"
25 #endif
26
27 /* system headers */
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <gtk/gtk.h>
31
32 /* gcrypt headers */
33 #include <gcrypt.h>
34
35 /* purple headers */
36 #include "version.h"
37 #include "pidginstock.h"
38 #include "plugin.h"
39 #include "notify.h"
40 #include "gtkconv.h"
41 #include "gtkutils.h"
42 #include "gtkimhtml.h"
43 #include "util.h"
44 #include "core.h"
45 #include "gtkmenutray.h"
46 #include "tooltipmenu.h"
47
48 #ifdef ENABLE_NLS
49 /* internationalisation headers */
50 #include <glib/gi18n-lib.h>
51 #else
52 #define _(x) (x)
53 #define N_(x) (x)
54 #endif
55
56 /* libotr headers */
57 #include <libotr/dh.h>
58 #include <libotr/privkey.h>
59 #include <libotr/proto.h>
60 #include <libotr/message.h>
61 #include <libotr/userstate.h>
62 #include <libotr/instag.h>
63
64 /* purple-otr headers */
65 #include "otr-plugin.h"
66 #include "dialogs.h"
67 #include "gtk-dialog.h"
68 #include "ui.h"
69 #include "otr-icons.h"
70
71 static GHashTable * otr_win_menus = 0;
72 static GHashTable * otr_win_status = 0;
73
74 static int img_id_not_private = 0;
75 static int img_id_unverified = 0;
76 static int img_id_private = 0;
77 static int img_id_finished = 0;
78
79
80 typedef struct {
81 ConnContext *context; /* The context used to fire library code */
82 GtkEntry* question_entry; /* The text entry field containing the user
83 * question */
84 GtkEntry *entry; /* The text entry field containing the secret */
85 int smp_type; /* Whether the SMP type is based on question
86 * challenge (0) or shared secret (1) */
87 gboolean responder; /* Whether or not this is the first side to give
88 * their secret */
89 } SmpResponsePair;
90
91 /* Information used by the plugin that is specific to both the
92 * application and connection. */
93 typedef struct dialog_context_data {
94 GtkWidget *smp_secret_dialog;
95 SmpResponsePair *smp_secret_smppair;
96 GtkWidget *smp_progress_dialog;
97 GtkWidget *smp_progress_bar;
98 GtkWidget *smp_progress_label;
99 otrl_instag_t their_instance;
100 } SMPData;
101
102 typedef struct {
103 SmpResponsePair *smppair;
104 GtkEntry *one_way_entry;
105 GtkEntry *two_way_entry;
106 GtkWidget *notebook;
107 } AuthSignalData;
108
109 typedef struct {
110 enum {
111 convctx_none,
112 convctx_conv,
113 convctx_ctx
114 } convctx_type;
115 PurpleConversation *conv;
116 ConnContext *context;
117 } ConvOrContext;
118
get_new_instance_index(PurpleConversation * conv)119 static gint get_new_instance_index(PurpleConversation *conv)
120 {
121 gint * max_index = (gint *)
122 purple_conversation_get_data(conv, "otr-max_idx");
123 *max_index = (*max_index) + 1;
124 return *max_index;
125 }
126
get_context_instance_to_index(PurpleConversation * conv,ConnContext * context)127 static gint get_context_instance_to_index(PurpleConversation *conv,
128 ConnContext *context) {
129 GHashTable * conv_to_idx_map =
130 purple_conversation_get_data(conv, "otr-conv_to_idx");
131 gpointer index = NULL;
132
133 if (!g_hash_table_lookup_extended(conv_to_idx_map, context, NULL, &index)) {
134 index = g_malloc(sizeof(gint));
135 *(gint *)index = get_new_instance_index(conv);
136 g_hash_table_replace(conv_to_idx_map, context, index);
137 }
138
139 return *(gint *)index;
140 }
141
close_progress_window(SMPData * smp_data)142 static void close_progress_window(SMPData *smp_data)
143 {
144 if (smp_data->smp_progress_dialog) {
145 gtk_dialog_response(GTK_DIALOG(smp_data->smp_progress_dialog),
146 GTK_RESPONSE_REJECT);
147 }
148 smp_data->smp_progress_dialog = NULL;
149 smp_data->smp_progress_bar = NULL;
150 smp_data->smp_progress_label = NULL;
151 }
152
otrg_gtk_dialog_free_smp_data(PurpleConversation * conv)153 static void otrg_gtk_dialog_free_smp_data(PurpleConversation *conv)
154 {
155 SMPData *smp_data = purple_conversation_get_data(conv, "otr-smpdata");
156 if (!smp_data) return;
157
158 if (smp_data->smp_secret_dialog) {
159 gtk_dialog_response(GTK_DIALOG(smp_data->smp_secret_dialog),
160 GTK_RESPONSE_REJECT);
161 }
162 smp_data->smp_secret_dialog = NULL;
163 smp_data->smp_secret_smppair = NULL;
164
165 close_progress_window(smp_data);
166
167 free(smp_data);
168
169 g_hash_table_remove(conv->data, "otr-smpdata");
170 }
171
otrg_gtk_dialog_add_smp_data(PurpleConversation * conv)172 static SMPData* otrg_gtk_dialog_add_smp_data(PurpleConversation *conv)
173 {
174 SMPData *smp_data = malloc(sizeof(SMPData));
175 smp_data->smp_secret_dialog = NULL;
176 smp_data->smp_secret_smppair = NULL;
177 smp_data->smp_progress_dialog = NULL;
178 smp_data->smp_progress_bar = NULL;
179 smp_data->smp_progress_label = NULL;
180 /* Chosen as initialized value since libotr should never allow
181 * this as a "their_instance" value */
182 smp_data->their_instance = OTRL_INSTAG_BEST;
183
184 purple_conversation_set_data(conv, "otr-smpdata", smp_data);
185
186 return smp_data;
187 }
188
otr_icon(GtkWidget * image,TrustLevel level,gboolean sensitivity)189 static GtkWidget *otr_icon(GtkWidget *image, TrustLevel level,
190 gboolean sensitivity)
191 {
192 GdkPixbuf *pixbuf = NULL;
193 const guint8 *data = NULL;
194
195 switch(level) {
196 case TRUST_NOT_PRIVATE:
197 data = not_private_pixbuf;
198 break;
199 case TRUST_UNVERIFIED:
200 data = unverified_pixbuf;
201 break;
202 case TRUST_PRIVATE:
203 data = private_pixbuf;
204 break;
205 case TRUST_FINISHED:
206 data = finished_pixbuf;
207 break;
208 }
209
210 pixbuf = gdk_pixbuf_new_from_inline(-1, data, FALSE, NULL);
211 if (image) {
212 gtk_image_set_from_pixbuf(GTK_IMAGE(image), pixbuf);
213 } else {
214 image = gtk_image_new_from_pixbuf(pixbuf);
215 }
216 g_object_unref(G_OBJECT(pixbuf));
217
218 gtk_widget_set_sensitive (image, sensitivity);
219
220 return image;
221 }
222
message_response_cb(GtkDialog * dialog,gint id,GtkWidget * widget)223 static void message_response_cb(GtkDialog *dialog, gint id, GtkWidget *widget)
224 {
225 gtk_widget_destroy(GTK_WIDGET(widget));
226 }
227
228 /* Forward declarations for the benefit of smp_message_response_cb/redraw
229 * authvbox */
230 static void verify_fingerprint(GtkWindow *parent, Fingerprint *fprint);
231 static void add_vrfy_fingerprint(GtkWidget *vbox, void *data);
232 static struct vrfy_fingerprint_data* vrfy_fingerprint_data_new(
233 Fingerprint *fprint);
234 static void vrfy_fingerprint_destroyed(GtkWidget *w,
235 struct vrfy_fingerprint_data *vfd);
236 static void conversation_switched ( PurpleConversation *conv, void * data );
237
238 static GtkWidget *create_smp_progress_dialog(GtkWindow *parent,
239 ConnContext *context);
240
241 /* Called when a button is pressed on the "progress bar" smp dialog */
smp_progress_response_cb(GtkDialog * dialog,gint response,ConnContext * context)242 static void smp_progress_response_cb(GtkDialog *dialog, gint response,
243 ConnContext *context)
244 {
245 PurpleConversation *conv = otrg_plugin_context_to_conv(context, 0);
246 SMPData *smp_data = NULL;
247
248 if (conv) {
249 gdouble frac;
250
251 smp_data = purple_conversation_get_data(conv, "otr-smpdata");
252 frac = gtk_progress_bar_get_fraction(
253 GTK_PROGRESS_BAR(smp_data->smp_progress_bar));
254
255 if (frac != 0.0 && frac != 1.0 && response == GTK_RESPONSE_REJECT) {
256 otrg_plugin_abort_smp(context);
257 }
258 }
259 /* In all cases, destroy the current window */
260 gtk_widget_destroy(GTK_WIDGET(dialog));
261
262 /* Clean up variables pointing to the destroyed objects */
263
264 if (smp_data) {
265 smp_data->smp_progress_bar = NULL;
266 smp_data->smp_progress_label = NULL;
267 smp_data->smp_progress_dialog = NULL;
268 }
269 }
270
271 /* Called when a button is pressed on the "enter the secret" smp dialog
272 * The data passed contains a pointer to the text entry field containing
273 * the entered secret as well as the current context.
274 */
smp_secret_response_cb(GtkDialog * dialog,gint response,AuthSignalData * auth_opt_data)275 static void smp_secret_response_cb(GtkDialog *dialog, gint response,
276 AuthSignalData *auth_opt_data)
277 {
278 ConnContext* context;
279 PurpleConversation *conv;
280 SMPData *smp_data;
281 SmpResponsePair *smppair;
282
283 if (!auth_opt_data) return;
284
285 smppair = auth_opt_data->smppair;
286
287 if (!smppair) return;
288
289 context = smppair->context;
290
291 if (response == GTK_RESPONSE_ACCEPT && smppair->entry) {
292 GtkEntry* entry = smppair->entry;
293 char *secret;
294 size_t secret_len;
295
296 GtkEntry* question_entry = smppair->question_entry;
297
298 const char *user_question = NULL;
299
300
301 if (context == NULL || context->msgstate != OTRL_MSGSTATE_ENCRYPTED) {
302 return;
303 }
304
305 secret = g_strdup(gtk_entry_get_text(entry));
306 secret_len = strlen(secret);
307
308 if (smppair->responder) {
309 otrg_plugin_continue_smp(context, (const unsigned char *)secret,
310 secret_len);
311
312 } else {
313
314 if (smppair->smp_type == 0) {
315 if (!question_entry) {
316 return;
317 }
318
319 user_question = gtk_entry_get_text(question_entry);
320
321 if (user_question == NULL || strlen(user_question) == 0) {
322 return;
323 }
324 }
325
326 /* pass user question here */
327 otrg_plugin_start_smp(context, user_question,
328 (const unsigned char *)secret, secret_len);
329
330 }
331
332 g_free(secret);
333
334 /* launch progress bar window */
335 create_smp_progress_dialog(GTK_WINDOW(dialog), context);
336 } else if (response == GTK_RESPONSE_HELP) {
337 char *helpurl = g_strdup_printf("%s%s&context=%s",
338 AUTHENTICATE_HELPURL, _("?lang=en"),
339 auth_opt_data->smppair->smp_type == 0 ?
340 ( /* Question and Answer */
341 auth_opt_data->smppair->responder ?
342 "answer" : "question" ) :
343 auth_opt_data->smppair->smp_type == 1 ?
344 ( /* Shared secret */
345 auth_opt_data->smppair->responder ?
346 "secretresp" : "secret" ) :
347 /* Fingerprint */
348 "fingerprint"
349 );
350 purple_notify_uri(otrg_plugin_handle, helpurl);
351 g_free(helpurl);
352
353 /* Don't destroy the window */
354 return;
355 } else {
356 otrg_plugin_abort_smp(context);
357 }
358
359 /* In all cases except HELP, destroy the current window */
360 gtk_widget_destroy(GTK_WIDGET(dialog));
361
362 /* Clean up references to this window */
363 conv = otrg_plugin_context_to_conv(smppair->context, 0);
364 smp_data = purple_conversation_get_data(conv, "otr-smpdata");
365
366 if (smp_data) {
367 smp_data->smp_secret_dialog = NULL;
368 smp_data->smp_secret_smppair = NULL;
369 }
370
371 /* Free memory */
372 free(auth_opt_data);
373 free(smppair);
374 }
375
close_smp_window(PurpleConversation * conv)376 static void close_smp_window(PurpleConversation *conv)
377 {
378 SMPData *smp_data = purple_conversation_get_data(conv, "otr-smpdata");
379 if (smp_data && smp_data->smp_secret_dialog) {
380 gtk_dialog_response(GTK_DIALOG(smp_data->smp_secret_dialog),
381 GTK_RESPONSE_REJECT);
382 }
383 }
384
create_dialog(GtkWindow * parent,PurpleNotifyMsgType type,const char * title,const char * primary,const char * secondary,int sensitive,GtkWidget ** labelp,void (* add_custom)(GtkWidget * vbox,void * data),void * add_custom_data)385 static GtkWidget *create_dialog(GtkWindow *parent,
386 PurpleNotifyMsgType type, const char *title,
387 const char *primary, const char *secondary, int sensitive,
388 GtkWidget **labelp, void (*add_custom)(GtkWidget *vbox, void *data),
389 void *add_custom_data)
390 {
391 GtkWidget *dialog;
392 GtkWidget *hbox;
393 GtkWidget *vbox;
394 GtkWidget *label;
395 GtkWidget *img = NULL;
396 char *label_text;
397 const char *icon_name = NULL;
398
399 switch (type) {
400 case PURPLE_NOTIFY_MSG_ERROR:
401 icon_name = PIDGIN_STOCK_DIALOG_ERROR;
402 break;
403
404 case PURPLE_NOTIFY_MSG_WARNING:
405 icon_name = PIDGIN_STOCK_DIALOG_WARNING;
406 break;
407
408 case PURPLE_NOTIFY_MSG_INFO:
409 icon_name = PIDGIN_STOCK_DIALOG_INFO;
410 break;
411
412 default:
413 icon_name = NULL;
414 break;
415 }
416
417 if (icon_name != NULL) {
418 img = gtk_image_new_from_stock(icon_name,
419 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_HUGE));
420 gtk_misc_set_alignment(GTK_MISC(img), 0, 0);
421 }
422
423 dialog = gtk_dialog_new_with_buttons(
424 title ? title : PIDGIN_ALERT_TITLE, parent, 0,
425 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, NULL);
426
427 gtk_window_set_focus_on_map(GTK_WINDOW(dialog), FALSE);
428 gtk_window_set_role(GTK_WINDOW(dialog), "notify_dialog");
429
430 g_signal_connect(G_OBJECT(dialog), "response",
431 G_CALLBACK(message_response_cb), dialog);
432 gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT,
433 sensitive);
434
435 gtk_container_set_border_width(GTK_CONTAINER(dialog), 6);
436 gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE);
437 gtk_dialog_set_has_separator(GTK_DIALOG(dialog), FALSE);
438 gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(dialog)->vbox), 12);
439 gtk_container_set_border_width(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), 6);
440
441 hbox = gtk_hbox_new(FALSE, 12);
442 vbox = gtk_vbox_new(FALSE, 0);
443 gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), hbox);
444
445 if (img != NULL) {
446 gtk_box_pack_start(GTK_BOX(hbox), img, FALSE, FALSE, 0);
447 }
448
449 label_text = g_strdup_printf(
450 "<span weight=\"bold\" size=\"larger\">%s</span>%s%s",
451 (primary ? primary : ""),
452 (primary ? "\n\n" : ""),
453 (secondary ? secondary : ""));
454
455 label = gtk_label_new(NULL);
456
457 gtk_label_set_markup(GTK_LABEL(label), label_text);
458 gtk_label_set_selectable(GTK_LABEL(label), 1);
459 g_free(label_text);
460 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
461 gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
462 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
463 if (add_custom) {
464 add_custom(vbox, add_custom_data);
465 }
466 gtk_box_pack_start(GTK_BOX(hbox), vbox, FALSE, FALSE, 0);
467
468 gtk_widget_show_all(dialog);
469
470 if (labelp) *labelp = label;
471 return dialog;
472 }
473
add_to_vbox_init_one_way_auth(GtkWidget * vbox,ConnContext * context,AuthSignalData * auth_opt_data,char * question)474 static void add_to_vbox_init_one_way_auth(GtkWidget *vbox,
475 ConnContext *context, AuthSignalData *auth_opt_data, char *question) {
476 GtkWidget *question_entry;
477 GtkWidget *entry;
478 GtkWidget *label;
479 GtkWidget *label2;
480 char *label_text;
481
482 SmpResponsePair* smppair = auth_opt_data->smppair;
483
484 if (smppair->responder) {
485 label_text = g_strdup_printf("<small><i>\n%s\n</i></small>",
486 _("Your buddy is attempting to determine if he or she is really "
487 "talking to you, or if it's someone pretending to be you. "
488 "Your buddy has asked a question, indicated below. "
489 "To authenticate to your buddy, enter the answer and "
490 "click OK."));
491 } else {
492 label_text = g_strdup_printf("<small><i>\n%s\n</i></small>",
493 _("To authenticate using a question, pick a question whose "
494 "answer is known only to you and your buddy. Enter this "
495 "question and this answer, then wait for your buddy to "
496 "enter the answer too. If the answers "
497 "don't match, then you may be talking to an imposter."));
498 }
499
500 label = gtk_label_new(NULL);
501
502 gtk_label_set_markup(GTK_LABEL(label), label_text);
503 gtk_label_set_selectable(GTK_LABEL(label), FALSE);
504 g_free(label_text);
505 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
506 gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
507 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
508
509
510 if (smppair->responder) {
511 label_text = g_strdup_printf(_("This is the question asked by "
512 "your buddy:"));
513 } else {
514 label_text = g_strdup_printf(_("Enter question here:"));
515 }
516
517 label = gtk_label_new(label_text);
518 gtk_label_set_selectable(GTK_LABEL(label), FALSE);
519 g_free(label_text);
520 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
521 gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
522 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
523
524
525
526 if (smppair->responder && question) {
527 label_text = g_markup_printf_escaped("<span background=\"white\" "
528 "foreground=\"black\" weight=\"bold\">%s</span>", question);
529 label = gtk_label_new(NULL);
530 gtk_label_set_markup (GTK_LABEL(label), label_text);
531 gtk_label_set_selectable(GTK_LABEL(label), FALSE);
532 g_free(label_text);
533 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
534 gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
535 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
536 smppair->question_entry = NULL;
537 } else {
538 /* Create the text view where the user enters their question */
539 question_entry = gtk_entry_new ();
540 smppair->question_entry = GTK_ENTRY(question_entry);
541 gtk_box_pack_start(GTK_BOX(vbox), question_entry, FALSE, FALSE, 0);
542 }
543
544 if (context->active_fingerprint->trust &&
545 context->active_fingerprint->trust[0] && !(smppair->responder)) {
546 label2 = gtk_label_new(_("This buddy is already authenticated."));
547 } else {
548 label2 = NULL;
549 }
550
551
552 /* Leave a blank line */
553 gtk_box_pack_start(GTK_BOX(vbox), gtk_label_new(NULL), FALSE,
554 FALSE, 0);
555
556 label_text = g_strdup_printf(_("Enter secret answer here "
557 "(case sensitive):"));
558
559 label = gtk_label_new(NULL);
560
561 gtk_label_set_markup(GTK_LABEL(label), label_text);
562 gtk_label_set_selectable(GTK_LABEL(label), FALSE);
563 g_free(label_text);
564 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
565 gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
566 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
567
568 /* Create the text view where the user enters their secret */
569 entry = gtk_entry_new();
570 gtk_entry_set_text(GTK_ENTRY(entry), "");
571
572 auth_opt_data->one_way_entry = GTK_ENTRY(entry);
573 gtk_entry_set_activates_default(GTK_ENTRY(entry), smppair->responder);
574
575 gtk_box_pack_start(GTK_BOX(vbox), entry, FALSE, FALSE, 0);
576
577 /* Leave a blank line */
578 gtk_box_pack_start(GTK_BOX(vbox), gtk_label_new(NULL), FALSE,
579 FALSE, 0);
580
581 if (label2) {
582 gtk_box_pack_start(GTK_BOX(vbox), label2, FALSE, FALSE, 0);
583 gtk_box_pack_start(GTK_BOX(vbox), gtk_label_new(NULL), FALSE,
584 FALSE, 0);
585 }
586 }
587
add_to_vbox_init_two_way_auth(GtkWidget * vbox,ConnContext * context,AuthSignalData * auth_opt_data)588 static void add_to_vbox_init_two_way_auth(GtkWidget *vbox,
589 ConnContext *context, AuthSignalData *auth_opt_data) {
590 GtkWidget *entry;
591 GtkWidget *label;
592 GtkWidget *label2;
593 char *label_text;
594
595 label_text = g_strdup_printf("<small><i>\n%s\n</i></small>",
596 _("To authenticate, pick a secret known "
597 "only to you and your buddy. Enter this secret, then "
598 "wait for your buddy to enter it too. If the secrets "
599 "don't match, then you may be talking to an imposter."));
600
601 label = gtk_label_new(NULL);
602
603 gtk_label_set_markup(GTK_LABEL(label), label_text);
604 gtk_label_set_selectable(GTK_LABEL(label), FALSE);
605 g_free(label_text);
606 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
607 gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
608 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
609
610 label_text = g_strdup_printf(_("Enter secret here:"));
611 label = gtk_label_new(label_text);
612 gtk_label_set_selectable(GTK_LABEL(label), FALSE);
613 g_free(label_text);
614 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
615 gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
616 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
617
618
619 /* Create the text view where the user enters their secret */
620 entry = gtk_entry_new();
621 gtk_entry_set_text(GTK_ENTRY(entry), "");
622 gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE);
623 auth_opt_data->two_way_entry = GTK_ENTRY(entry);
624
625 if (context->active_fingerprint->trust &&
626 context->active_fingerprint->trust[0]) {
627 label2 = gtk_label_new(_("This buddy is already authenticated."));
628 } else {
629 label2 = NULL;
630 }
631
632 gtk_box_pack_start(GTK_BOX(vbox), entry, FALSE, FALSE, 0);
633
634 /* Leave a blank line */
635 gtk_box_pack_start(GTK_BOX(vbox), gtk_label_new(NULL), FALSE,
636 FALSE, 0);
637
638 if (label2) {
639 gtk_box_pack_start(GTK_BOX(vbox), label2, FALSE, FALSE, 0);
640 gtk_box_pack_start(GTK_BOX(vbox), gtk_label_new(NULL), FALSE,
641 FALSE, 0);
642 }
643 }
644
add_to_vbox_verify_fingerprint(GtkWidget * vbox,ConnContext * context,SmpResponsePair * smppair)645 static void add_to_vbox_verify_fingerprint(GtkWidget *vbox,
646 ConnContext *context, SmpResponsePair* smppair) {
647 char our_hash[OTRL_PRIVKEY_FPRINT_HUMAN_LEN],
648 their_hash[OTRL_PRIVKEY_FPRINT_HUMAN_LEN];
649 GtkWidget *label;
650 char *label_text;
651 struct vrfy_fingerprint_data *vfd;
652 PurplePlugin *p;
653 char *proto_name;
654 Fingerprint *fprint = context->active_fingerprint;
655
656 if (fprint == NULL) return;
657 if (fprint->fingerprint == NULL) return;
658
659 label_text = g_strdup_printf("<small><i>\n%s %s\n</i></small>",
660 _("To verify the fingerprint, contact your buddy via some "
661 "<i>other</i> authenticated channel, such as the telephone "
662 "or GPG-signed email. Each of you should tell your fingerprint "
663 "to the other."),
664 _("If everything matches up, you should choose <b>I have</b> "
665 "in the menu below."));
666 label = gtk_label_new(NULL);
667 gtk_label_set_markup(GTK_LABEL(label), label_text);
668 gtk_label_set_selectable(GTK_LABEL(label), FALSE);
669 g_free(label_text);
670 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
671 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
672
673 vfd = vrfy_fingerprint_data_new(fprint);
674
675 strncpy(our_hash, _("[none]"), 44);
676 our_hash[44] = '\0';
677 otrl_privkey_fingerprint(otrg_plugin_userstate, our_hash,
678 context->accountname, context->protocol);
679
680 otrl_privkey_hash_to_human(their_hash, fprint->fingerprint);
681
682 p = purple_find_prpl(context->protocol);
683 proto_name = (p && p->info->name) ? p->info->name : _("Unknown");
684 label_text = g_strdup_printf(_("Fingerprint for you, %s (%s):\n%s\n\n"
685 "Purported fingerprint for %s:\n%s\n"), context->accountname,
686 proto_name, our_hash, context->username, their_hash);
687
688 label = gtk_label_new(NULL);
689
690 gtk_label_set_markup(GTK_LABEL(label), label_text);
691 /* Make the label containing the fingerprints selectable, but
692 * not auto-selected. */
693 gtk_label_set_selectable(GTK_LABEL(label), TRUE);
694 g_object_set(label, "can-focus", FALSE, NULL);
695
696 g_free(label_text);
697 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
698 gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
699 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
700
701 add_vrfy_fingerprint(vbox, vfd);
702 g_signal_connect(G_OBJECT(vbox), "destroy",
703 G_CALLBACK(vrfy_fingerprint_destroyed), vfd);
704 }
705
redraw_auth_vbox(GtkComboBox * combo,void * data)706 static void redraw_auth_vbox(GtkComboBox *combo, void *data)
707 {
708 AuthSignalData *auth_data = (AuthSignalData*) data;
709
710 GtkWidget *notebook = auth_data ? auth_data->notebook : NULL;
711
712 int selected;
713
714 if (auth_data == NULL) return;
715
716 selected = gtk_combo_box_get_active(combo);
717
718 if (selected == 0) {
719 gtk_notebook_set_current_page (GTK_NOTEBOOK(notebook), 0);
720 auth_data->smppair->entry = auth_data->one_way_entry;
721 auth_data->smppair->smp_type = 0;
722 } else if (selected == 1) {
723 gtk_notebook_set_current_page (GTK_NOTEBOOK(notebook), 1);
724 auth_data->smppair->entry = auth_data->two_way_entry;
725 auth_data->smppair->smp_type = 1;
726 } else if (selected == 2) {
727 auth_data->smppair->entry = NULL;
728 gtk_notebook_set_current_page (GTK_NOTEBOOK(notebook), 2);
729 auth_data->smppair->smp_type = -1;
730 }
731
732 }
733
add_other_authentication_options(GtkWidget * vbox,GtkWidget * notebook,ConnContext * context,AuthSignalData * data)734 static void add_other_authentication_options(GtkWidget *vbox,
735 GtkWidget *notebook, ConnContext *context, AuthSignalData *data) {
736 GtkWidget *label;
737 GtkWidget *combo;
738 char *labeltext;
739
740 labeltext = g_strdup_printf("\n%s",
741 _("How would you like to authenticate your buddy?"));
742 label = gtk_label_new(labeltext);
743 g_free(labeltext);
744 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
745 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
746
747 combo = gtk_combo_box_new_text();
748
749 gtk_combo_box_append_text(GTK_COMBO_BOX(combo),
750 _("Question and answer"));
751
752 gtk_combo_box_append_text(GTK_COMBO_BOX(combo),
753 _("Shared secret"));
754
755 gtk_combo_box_append_text(GTK_COMBO_BOX(combo),
756 _("Manual fingerprint verification"));
757
758 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
759 gtk_box_pack_start(GTK_BOX(vbox), combo, FALSE, FALSE, 0);
760
761 data->notebook = notebook;
762
763 g_signal_connect (combo, "changed",
764 G_CALLBACK (redraw_auth_vbox), data);
765 }
766
767
create_smp_dialog(const char * title,const char * primary,ConnContext * context,gboolean responder,char * question)768 static GtkWidget *create_smp_dialog(const char *title, const char *primary,
769 ConnContext *context, gboolean responder, char *question)
770 {
771 GtkWidget *dialog;
772
773 PurpleConversation *conv = otrg_plugin_context_to_conv(context, 1);
774 SMPData *smp_data = purple_conversation_get_data(conv, "otr-smpdata");
775
776 close_progress_window(smp_data);
777
778 /* If you start SMP authentication on a different context, it
779 * will kill any existing SMP */
780 if (smp_data->their_instance != context->their_instance) {
781 otrg_gtk_dialog_free_smp_data(conv);
782 smp_data = otrg_gtk_dialog_add_smp_data(conv);
783 }
784
785 if (!(smp_data->smp_secret_dialog)) {
786 GtkWidget *hbox;
787 GtkWidget *vbox;
788 GtkWidget *auth_vbox;
789 GtkWidget *label;
790 GtkWidget *img = NULL;
791 char *label_text;
792 const char *icon_name = NULL;
793 SmpResponsePair* smppair;
794 GtkWidget *notebook;
795 AuthSignalData *auth_opt_data;
796
797 smp_data->their_instance = context->their_instance;
798 icon_name = PIDGIN_STOCK_DIALOG_INFO;
799 img = gtk_image_new_from_stock(icon_name,
800 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_HUGE));
801 gtk_misc_set_alignment(GTK_MISC(img), 0, 0);
802
803 dialog = gtk_dialog_new_with_buttons(title ? title :
804 PIDGIN_ALERT_TITLE, NULL, 0,
805 GTK_STOCK_HELP, GTK_RESPONSE_HELP,
806 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
807 _("_Authenticate"), GTK_RESPONSE_ACCEPT, NULL);
808 gtk_dialog_set_default_response(GTK_DIALOG(dialog),
809 GTK_RESPONSE_ACCEPT);
810
811 auth_vbox = gtk_vbox_new(FALSE, 0);
812 hbox = gtk_hbox_new(FALSE, 15);
813 vbox = gtk_vbox_new(FALSE, 0);
814
815 smppair = malloc(sizeof(SmpResponsePair));
816 smppair->responder = responder;
817 smppair->context = context;
818
819
820 notebook = gtk_notebook_new();
821 auth_opt_data = malloc(sizeof(AuthSignalData));
822 auth_opt_data->smppair = smppair;
823
824 gtk_window_set_focus_on_map(GTK_WINDOW(dialog), !responder);
825 gtk_window_set_role(GTK_WINDOW(dialog), "notify_dialog");
826
827 gtk_container_set_border_width(GTK_CONTAINER(dialog), 6);
828 gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE);
829 gtk_dialog_set_has_separator(GTK_DIALOG(dialog), FALSE);
830 gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(dialog)->vbox), 12);
831 gtk_container_set_border_width(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox),
832 6);
833
834 gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), hbox);
835
836 gtk_box_pack_start(GTK_BOX(hbox), img, FALSE, FALSE, 0);
837
838 label_text = g_strdup_printf(
839 "<span weight=\"bold\" size=\"larger\">%s</span>\n\n%s",
840 (primary ? primary : ""),
841 _("Authenticating a buddy helps ensure that the person "
842 "you are talking to is who he or she claims to be."));
843
844 label = gtk_label_new(NULL);
845
846 gtk_label_set_markup(GTK_LABEL(label), label_text);
847 gtk_label_set_selectable(GTK_LABEL(label), FALSE);
848 g_free(label_text);
849 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
850 gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
851 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
852
853 if (!responder) {
854 add_other_authentication_options(vbox, notebook, context,
855 auth_opt_data);
856 }
857
858 g_signal_connect(G_OBJECT(dialog), "response",
859 G_CALLBACK(smp_secret_response_cb),
860 auth_opt_data);
861
862 if (!responder || (responder && question != NULL)) {
863 GtkWidget *one_way_vbox = gtk_vbox_new(FALSE, 0);
864 add_to_vbox_init_one_way_auth(one_way_vbox, context,
865 auth_opt_data, question);
866 gtk_notebook_append_page(GTK_NOTEBOOK(notebook), one_way_vbox,
867 gtk_label_new("0"));
868 smppair->entry = auth_opt_data->one_way_entry;
869 smppair->smp_type = 0;
870 }
871
872 if (!responder || (responder && question == NULL)) {
873 GtkWidget *two_way_vbox = gtk_vbox_new(FALSE, 0);
874 add_to_vbox_init_two_way_auth(two_way_vbox, context, auth_opt_data);
875 gtk_notebook_append_page(GTK_NOTEBOOK(notebook), two_way_vbox,
876 gtk_label_new("1"));
877
878 if (responder && question == NULL) {
879 smppair->entry = auth_opt_data->two_way_entry;
880 smppair->smp_type = 1;
881 }
882 }
883
884 if (!responder) {
885 GtkWidget *fingerprint_vbox = gtk_vbox_new(FALSE, 0);
886 add_to_vbox_verify_fingerprint(fingerprint_vbox, context, smppair);
887 gtk_notebook_append_page(GTK_NOTEBOOK(notebook), fingerprint_vbox,
888 gtk_label_new("2"));
889 }
890
891 gtk_notebook_set_show_tabs (GTK_NOTEBOOK(notebook), FALSE);
892
893 gtk_notebook_set_show_border (GTK_NOTEBOOK(notebook), FALSE);
894 gtk_box_pack_start(GTK_BOX(auth_vbox), notebook, FALSE, FALSE, 0);
895 gtk_widget_show(notebook);
896
897
898 gtk_box_pack_start(GTK_BOX(vbox), auth_vbox, FALSE, FALSE, 0);
899
900 gtk_box_pack_start(GTK_BOX(hbox), vbox, FALSE, FALSE, 0);
901
902 gtk_widget_show_all(dialog);
903
904 gtk_notebook_set_current_page (GTK_NOTEBOOK(notebook), 0);
905
906 if (!responder) {
907 gtk_window_set_focus(GTK_WINDOW(dialog),
908 GTK_WIDGET(smppair->question_entry));
909 } else {
910 gtk_window_set_focus(GTK_WINDOW(dialog),
911 GTK_WIDGET(smppair->entry));
912 }
913
914 smp_data->smp_secret_dialog = dialog;
915 smp_data->smp_secret_smppair = smppair;
916
917 } else {
918 /* Set the responder field to TRUE if we were passed that value,
919 * even if the window was already up. */
920 if (responder) {
921 smp_data->smp_secret_smppair->responder = responder;
922 }
923 }
924
925 return smp_data->smp_secret_dialog;
926 }
927
create_smp_progress_dialog(GtkWindow * parent,ConnContext * context)928 static GtkWidget *create_smp_progress_dialog(GtkWindow *parent,
929 ConnContext *context)
930 {
931 GtkWidget *dialog;
932 GtkWidget *hbox;
933 GtkWidget *vbox;
934 GtkWidget *label;
935 GtkWidget *proglabel;
936 GtkWidget *bar;
937 GtkWidget *img = NULL;
938 char *label_text, *label_pat;
939 const char *icon_name = NULL;
940 PurpleConversation *conv;
941 SMPData *smp_data;
942
943 icon_name = PIDGIN_STOCK_DIALOG_INFO;
944 img = gtk_image_new_from_stock(icon_name,
945 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_HUGE));
946 gtk_misc_set_alignment(GTK_MISC(img), 0, 0);
947
948 dialog = gtk_dialog_new_with_buttons(
949 context->smstate->received_question ?
950 /* Translators: you are asked to authenticate yourself */
951 _("Authenticating to Buddy") :
952 /* Translators: you asked your buddy to authenticate him/herself */
953 _("Authenticating Buddy"),
954 parent, 0, GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
955 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, NULL);
956 gtk_dialog_set_default_response(GTK_DIALOG(dialog),
957 GTK_RESPONSE_ACCEPT);
958 gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
959 GTK_RESPONSE_REJECT, 1);
960 gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
961 GTK_RESPONSE_ACCEPT, 0);
962
963 gtk_window_set_focus_on_map(GTK_WINDOW(dialog), FALSE);
964 gtk_window_set_role(GTK_WINDOW(dialog), "notify_dialog");
965
966 gtk_container_set_border_width(GTK_CONTAINER(dialog), 6);
967 gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE);
968 gtk_dialog_set_has_separator(GTK_DIALOG(dialog), FALSE);
969 gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(dialog)->vbox), 12);
970 gtk_container_set_border_width(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), 6);
971
972 hbox = gtk_hbox_new(FALSE, 12);
973 vbox = gtk_vbox_new(FALSE, 0);
974 gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), hbox);
975
976 gtk_box_pack_start(GTK_BOX(hbox), img, FALSE, FALSE, 0);
977
978 label_pat = g_strdup_printf("<span weight=\"bold\" size=\"larger\">"
979 "%s</span>\n", context->smstate->received_question ?
980 _("Authenticating to %s") :
981 _("Authenticating %s"));
982 label_text = g_strdup_printf(label_pat, context->username);
983 g_free(label_pat);
984
985 label = gtk_label_new(NULL);
986
987 gtk_label_set_markup(GTK_LABEL(label), label_text);
988 gtk_label_set_selectable(GTK_LABEL(label), 1);
989 g_free(label_text);
990 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
991 gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
992 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
993
994 proglabel = gtk_label_new(NULL);
995 gtk_label_set_selectable(GTK_LABEL(proglabel), 1);
996 gtk_label_set_line_wrap(GTK_LABEL(proglabel), TRUE);
997 gtk_misc_set_alignment(GTK_MISC(proglabel), 0, 0);
998 gtk_box_pack_start(GTK_BOX(vbox), proglabel, FALSE, FALSE, 0);
999
1000 /* Create the progress bar */
1001 bar = gtk_progress_bar_new();
1002 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(bar), 0.1);
1003 gtk_box_pack_start(GTK_BOX(vbox), bar, FALSE, FALSE, 0);
1004
1005 gtk_box_pack_start(GTK_BOX(hbox), vbox, FALSE, FALSE, 0);
1006
1007 conv = otrg_plugin_context_to_conv(context, 0);
1008 smp_data = purple_conversation_get_data(conv, "otr-smpdata");
1009 if (smp_data) {
1010 smp_data->smp_progress_dialog = dialog;
1011 smp_data->smp_progress_bar = bar;
1012 smp_data->smp_progress_label = proglabel;
1013 }
1014 gtk_label_set_text(GTK_LABEL(proglabel), _("Waiting for buddy..."));
1015
1016 g_signal_connect(G_OBJECT(dialog), "response",
1017 G_CALLBACK(smp_progress_response_cb),
1018 context);
1019
1020 gtk_widget_show_all(dialog);
1021
1022 return dialog;
1023 }
1024
1025 /* This is just like purple_notify_message, except: (a) it doesn't grab
1026 * keyboard focus, (b) the button is "OK" instead of "Close", and (c)
1027 * the labels aren't limited to 2K. */
otrg_gtk_dialog_notify_message(PurpleNotifyMsgType type,const char * accountname,const char * protocol,const char * username,const char * title,const char * primary,const char * secondary)1028 static void otrg_gtk_dialog_notify_message(PurpleNotifyMsgType type,
1029 const char *accountname, const char *protocol, const char *username,
1030 const char *title, const char *primary, const char *secondary)
1031 {
1032 create_dialog(NULL, type, title, primary, secondary, 1, NULL, NULL, NULL);
1033 }
1034
1035 struct s_OtrgDialogWait {
1036 GtkWidget *dialog;
1037 GtkWidget *label;
1038 };
1039
1040 /* Put up a Please Wait dialog, with the "OK" button desensitized.
1041 * Return a handle that must eventually be passed to
1042 * otrg_dialog_private_key_wait_done. */
otrg_gtk_dialog_private_key_wait_start(const char * account,const char * protocol)1043 static OtrgDialogWaitHandle otrg_gtk_dialog_private_key_wait_start(
1044 const char *account, const char *protocol)
1045 {
1046 PurplePlugin *p;
1047 const char *title = _("Generating private key");
1048 const char *primary = _("Please wait");
1049 char *secondary;
1050 const char *protocol_print;
1051 GtkWidget *label;
1052 GtkWidget *dialog;
1053 OtrgDialogWaitHandle handle;
1054
1055 p = purple_find_prpl(protocol);
1056 protocol_print = (p ? p->info->name : _("Unknown"));
1057
1058 /* Create the Please Wait... dialog */
1059 secondary = g_strdup_printf(_("Generating private key for %s (%s)..."),
1060 account, protocol_print);
1061
1062 dialog = create_dialog(NULL, PURPLE_NOTIFY_MSG_INFO, title, primary,
1063 secondary, 0, &label, NULL, NULL);
1064 handle = malloc(sizeof(struct s_OtrgDialogWait));
1065 handle->dialog = dialog;
1066 handle->label = label;
1067
1068 /* Make sure the dialog is actually displayed before doing any
1069 * compute-intensive stuff. */
1070 while (gtk_events_pending ()) {
1071 gtk_main_iteration ();
1072 }
1073
1074 g_free(secondary);
1075
1076 return handle;
1077 }
1078
otrg_gtk_dialog_display_otr_message(const char * accountname,const char * protocol,const char * username,const char * msg,int force_create)1079 static int otrg_gtk_dialog_display_otr_message(const char *accountname,
1080 const char *protocol, const char *username, const char *msg,
1081 int force_create)
1082 {
1083 /* See if there's a conversation window we can put this in. */
1084 PurpleConversation *conv = otrg_plugin_userinfo_to_conv(accountname,
1085 protocol, username, force_create);
1086
1087
1088 if (!conv) return -1;
1089
1090
1091 purple_conversation_write(conv, NULL, msg, PURPLE_MESSAGE_SYSTEM,
1092 time(NULL));
1093
1094 return 0;
1095 }
1096
1097 /* End a Please Wait dialog. */
otrg_gtk_dialog_private_key_wait_done(OtrgDialogWaitHandle handle)1098 static void otrg_gtk_dialog_private_key_wait_done(OtrgDialogWaitHandle handle)
1099 {
1100 const char *oldmarkup;
1101 char *newmarkup;
1102
1103 oldmarkup = gtk_label_get_label(GTK_LABEL(handle->label));
1104 newmarkup = g_strdup_printf(_("%s Done."), oldmarkup);
1105
1106 gtk_label_set_markup(GTK_LABEL(handle->label), newmarkup);
1107 gtk_widget_show(handle->label);
1108 gtk_dialog_set_response_sensitive(GTK_DIALOG(handle->dialog),
1109 GTK_RESPONSE_ACCEPT, 1);
1110
1111 g_free(newmarkup);
1112 free(handle);
1113 }
1114
1115 /* Inform the user that an unknown fingerprint was received. */
otrg_gtk_dialog_unknown_fingerprint(OtrlUserState us,const char * accountname,const char * protocol,const char * who,unsigned char fingerprint[20])1116 static void otrg_gtk_dialog_unknown_fingerprint(OtrlUserState us,
1117 const char *accountname, const char *protocol, const char *who,
1118 unsigned char fingerprint[20])
1119 {
1120 PurpleConversation *conv;
1121 char *buf;
1122 ConnContext *context;
1123 int seenbefore = FALSE;
1124
1125 /* Figure out if this is the first fingerprint we've seen for this
1126 * user. */
1127 context = otrl_context_find(us, who, accountname, protocol,
1128 OTRL_INSTAG_MASTER, 0, NULL, NULL, NULL);
1129
1130 if (context) {
1131 Fingerprint *fp = context->fingerprint_root.next;
1132 while(fp) {
1133 if (memcmp(fingerprint, fp->fingerprint, 20)) {
1134 /* This is a previously seen fingerprint for this user,
1135 * different from the one we were passed. */
1136 seenbefore = TRUE;
1137 break;
1138 }
1139 fp = fp->next;
1140 }
1141 }
1142
1143 if (seenbefore) {
1144 buf = g_strdup_printf(_("%s is contacting you from an unrecognized "
1145 "computer. You should <a href=\"%s%s\">authenticate</a> "
1146 "this buddy."), who, AUTHENTICATE_HELPURL, _("?lang=en"));
1147 } else {
1148 buf = g_strdup_printf(_("%s has not been authenticated yet. You "
1149 "should <a href=\"%s%s\">authenticate</a> this buddy."),
1150 who, AUTHENTICATE_HELPURL, _("?lang=en"));
1151 }
1152
1153 conv = otrg_plugin_userinfo_to_conv(accountname, protocol, who, TRUE);
1154
1155 purple_conversation_write(conv, NULL, buf, PURPLE_MESSAGE_SYSTEM,
1156 time(NULL));
1157
1158 g_free(buf);
1159 }
1160
1161 static void otrg_gtk_dialog_clicked_connect(GtkWidget *widget, gpointer data);
1162
1163 static void build_otr_menu(ConvOrContext *convctx, GtkWidget *menu,
1164 TrustLevel level);
1165 static void otr_refresh_otr_buttons(PurpleConversation *conv);
1166 static void otr_destroy_top_menu_objects(PurpleConversation *conv);
1167 static void otr_add_top_otr_menu(PurpleConversation *conv);
1168 static void otr_add_buddy_top_menus(PurpleConversation *conv);
1169 static void otr_check_conv_status_change(PurpleConversation *conv);
1170
destroy_menuitem(GtkWidget * widget,gpointer data)1171 static void destroy_menuitem(GtkWidget *widget, gpointer data)
1172 {
1173 gtk_widget_destroy(widget);
1174 }
1175
1176 static void otr_build_status_submenu(PidginWindow *win,
1177 ConvOrContext *convctx, GtkWidget *menu, TrustLevel level);
1178
dialog_update_label_conv(PurpleConversation * conv,TrustLevel level)1179 static void dialog_update_label_conv(PurpleConversation *conv, TrustLevel level)
1180 {
1181 GtkWidget *label;
1182 GtkWidget *icon;
1183 GtkWidget *button;
1184 GtkWidget *menu;
1185 ConvOrContext *convctx;
1186 GHashTable * conv_or_ctx_map;
1187 char *markup;
1188 PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv);
1189 label = purple_conversation_get_data(conv, "otr-label");
1190 icon = purple_conversation_get_data(conv, "otr-icon");
1191 button = purple_conversation_get_data(conv, "otr-button");
1192 menu = purple_conversation_get_data(conv, "otr-menu");
1193
1194 otr_icon(icon, level, 1);
1195 markup = g_strdup_printf(" <span color=\"%s\">%s</span>",
1196 level == TRUST_FINISHED ? "#000000" :
1197 level == TRUST_PRIVATE ? "#00a000" :
1198 level == TRUST_UNVERIFIED ? "#a06000" :
1199 "#ff0000",
1200 level == TRUST_FINISHED ? _("Finished") :
1201 level == TRUST_PRIVATE ? _("Private") :
1202 level == TRUST_UNVERIFIED ? _("Unverified") :
1203 _("Not private"));
1204 gtk_label_set_markup(GTK_LABEL(label), markup);
1205 g_free(markup);
1206 gtk_tooltips_set_tip(gtkconv->tooltips, button, _("OTR"), NULL);
1207
1208
1209 /* Use any non-NULL value for "private", NULL for "not private" */
1210 purple_conversation_set_data(conv, "otr-private",
1211 (level == TRUST_NOT_PRIVATE || level == TRUST_FINISHED) ?
1212 NULL : conv);
1213
1214 /* Use any non-NULL value for "unauthenticated", NULL for
1215 * "authenticated" */
1216 purple_conversation_set_data(conv, "otr-authenticated",
1217 (level == TRUST_PRIVATE) ? conv : NULL);
1218
1219 /* Use any non-NULL value for "finished", NULL for "not finished" */
1220 purple_conversation_set_data(conv, "otr-finished",
1221 level == TRUST_FINISHED ? conv : NULL);
1222
1223 conv_or_ctx_map = purple_conversation_get_data(conv, "otr-convorctx");
1224 convctx = g_hash_table_lookup(conv_or_ctx_map, conv);
1225
1226 if (!convctx) {
1227 convctx = malloc(sizeof(ConvOrContext));
1228 g_hash_table_insert(conv_or_ctx_map, conv, (gpointer)convctx);
1229 }
1230
1231 convctx->convctx_type = convctx_conv;
1232 convctx->conv = conv;
1233 build_otr_menu(convctx, menu, level);
1234 otr_build_status_submenu(pidgin_conv_get_window(gtkconv), convctx, menu,
1235 level);
1236
1237 conv = gtkconv->active_conv;
1238 otr_check_conv_status_change(conv);
1239
1240 /* Update other widgets */
1241 if (gtkconv != pidgin_conv_window_get_active_gtkconv(gtkconv->win)) {
1242 return;
1243 }
1244
1245 otr_destroy_top_menu_objects(conv);
1246 otr_add_top_otr_menu(conv);
1247 otr_refresh_otr_buttons(conv);
1248 otr_add_buddy_top_menus(conv);
1249 }
1250
dialog_update_label(ConnContext * context)1251 static void dialog_update_label(ConnContext *context)
1252 {
1253 PurpleAccount *account;
1254 PurpleConversation *conv;
1255 TrustLevel level = otrg_plugin_context_to_trust(context);
1256
1257
1258 account = purple_accounts_find(context->accountname, context->protocol);
1259 if (!account) return;
1260 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM,
1261 context->username, account);
1262 if (!conv) return;
1263 dialog_update_label_conv(conv, level);
1264 }
1265
1266 struct vrfy_fingerprint_data {
1267 Fingerprint *fprint; /* You can use this pointer right away, but
1268 you can't rely on it sticking around for a
1269 while. Use the copied pieces below
1270 instead. */
1271 char *accountname, *username, *protocol;
1272 otrl_instag_t their_instance;
1273 unsigned char fingerprint[20];
1274 };
1275
vrfy_fingerprint_data_free(struct vrfy_fingerprint_data * vfd)1276 static void vrfy_fingerprint_data_free(struct vrfy_fingerprint_data *vfd)
1277 {
1278 free(vfd->accountname);
1279 free(vfd->username);
1280 free(vfd->protocol);
1281 free(vfd);
1282 }
1283
vrfy_fingerprint_data_new(Fingerprint * fprint)1284 static struct vrfy_fingerprint_data* vrfy_fingerprint_data_new(
1285 Fingerprint *fprint)
1286 {
1287 struct vrfy_fingerprint_data *vfd;
1288 ConnContext *context = fprint->context;
1289
1290 vfd = malloc(sizeof(*vfd));
1291 vfd->fprint = fprint;
1292 vfd->accountname = strdup(context->accountname);
1293 vfd->username = strdup(context->username);
1294 vfd->protocol = strdup(context->protocol);
1295 vfd->their_instance = context->their_instance;
1296 memmove(vfd->fingerprint, fprint->fingerprint, 20);
1297
1298 return vfd;
1299 }
1300
vrfy_fingerprint_destroyed(GtkWidget * w,struct vrfy_fingerprint_data * vfd)1301 static void vrfy_fingerprint_destroyed(GtkWidget *w,
1302 struct vrfy_fingerprint_data *vfd)
1303 {
1304 vrfy_fingerprint_data_free(vfd);
1305 }
1306
vrfy_fingerprint_changed(GtkComboBox * combo,void * data)1307 static void vrfy_fingerprint_changed(GtkComboBox *combo, void *data)
1308 {
1309 struct vrfy_fingerprint_data *vfd = data;
1310 ConnContext *context = otrl_context_find(otrg_plugin_userstate,
1311 vfd->username, vfd->accountname, vfd->protocol, vfd->their_instance,
1312 0, NULL, NULL, NULL);
1313 Fingerprint *fprint;
1314 int oldtrust, trust;
1315
1316 if (context == NULL) return;
1317
1318 fprint = otrl_context_find_fingerprint(context, vfd->fingerprint, 0, NULL);
1319
1320 if (fprint == NULL) return;
1321
1322 oldtrust = (fprint->trust && fprint->trust[0]);
1323 trust = gtk_combo_box_get_active(combo) == 1 ? 1 : 0;
1324
1325 /* See if anything's changed */
1326 if (trust != oldtrust) {
1327 otrl_context_set_trust(fprint, trust ? "verified" : "");
1328 /* Write the new info to disk, redraw the ui, and redraw the
1329 * OTR buttons. */
1330 otrg_plugin_write_fingerprints();
1331 otrg_ui_update_keylist();
1332 otrg_dialog_resensitize_all();
1333
1334 }
1335 }
1336
1337 /* Add the verify widget and the help text for the verify fingerprint box. */
add_vrfy_fingerprint(GtkWidget * vbox,void * data)1338 static void add_vrfy_fingerprint(GtkWidget *vbox, void *data)
1339 {
1340 GtkWidget *hbox;
1341 GtkWidget *combo, *label;
1342 struct vrfy_fingerprint_data *vfd = data;
1343 char *labelt;
1344 int verified = 0;
1345
1346 if (vfd->fprint->trust && vfd->fprint->trust[0]) {
1347 verified = 1;
1348 }
1349
1350 hbox = gtk_hbox_new(FALSE, 0);
1351 combo = gtk_combo_box_new_text();
1352 /* Translators: the following four messages should give alternative
1353 * sentences. The user selects the first or second message in a combo box;
1354 * the third message, a new line, a fingerprint, a new line, and
1355 * the fourth message will follow it. */
1356 gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("I have not"));
1357 /* 2nd message */
1358 gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("I have"));
1359 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), verified);
1360 /* 3rd message */
1361 label = gtk_label_new(_(" verified that this is in fact the correct"));
1362 gtk_box_pack_start(GTK_BOX(hbox), combo, FALSE, FALSE, 0);
1363 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
1364 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
1365
1366 g_signal_connect(G_OBJECT(combo), "changed",
1367 G_CALLBACK(vrfy_fingerprint_changed), vfd);
1368
1369 hbox = gtk_hbox_new(FALSE, 0);
1370 /* 4th message */
1371 labelt = g_strdup_printf(_("fingerprint for %s."),
1372 vfd->username);
1373 label = gtk_label_new(labelt);
1374 g_free(labelt);
1375 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
1376 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
1377
1378 /* Leave a blank line */
1379 gtk_box_pack_start(GTK_BOX(vbox), gtk_label_new(NULL), FALSE, FALSE, 0);
1380 }
1381
verify_fingerprint(GtkWindow * parent,Fingerprint * fprint)1382 static void verify_fingerprint(GtkWindow *parent, Fingerprint *fprint)
1383 {
1384 GtkWidget *dialog;
1385 char our_hash[OTRL_PRIVKEY_FPRINT_HUMAN_LEN],
1386 their_hash[OTRL_PRIVKEY_FPRINT_HUMAN_LEN];
1387 char *primary;
1388 char *secondary;
1389 struct vrfy_fingerprint_data *vfd;
1390 ConnContext *context;
1391 PurplePlugin *p;
1392 char *proto_name;
1393
1394 if (fprint == NULL) return;
1395 if (fprint->fingerprint == NULL) return;
1396 context = fprint->context;
1397 if (context == NULL) return;
1398
1399 primary = g_strdup_printf(_("Verify fingerprint for %s"),
1400 context->username);
1401 vfd = vrfy_fingerprint_data_new(fprint);
1402
1403 strncpy(our_hash, _("[none]"), 44);
1404 our_hash[44] = '\0';
1405 otrl_privkey_fingerprint(otrg_plugin_userstate, our_hash,
1406 context->accountname, context->protocol);
1407
1408 otrl_privkey_hash_to_human(their_hash, fprint->fingerprint);
1409
1410 p = purple_find_prpl(context->protocol);
1411 proto_name = (p && p->info->name) ? p->info->name : _("Unknown");
1412 secondary = g_strdup_printf(_("<small><i>%s %s\n\n</i></small>"
1413 "Fingerprint for you, %s (%s):\n%s\n\n"
1414 "Purported fingerprint for %s:\n%s\n"),
1415 _("To verify the fingerprint, contact your buddy via some "
1416 "<i>other</i> authenticated channel, such as the telephone "
1417 "or GPG-signed email. Each of you should tell your fingerprint "
1418 "to the other."),
1419 _("If everything matches up, you should indicate in the above "
1420 "dialog that you <b>have</b> verified the fingerprint."),
1421 context->accountname, proto_name, our_hash,
1422 context->username, their_hash);
1423
1424 dialog = create_dialog(parent, PURPLE_NOTIFY_MSG_INFO,
1425 _("Verify fingerprint"), primary, secondary, 1, NULL,
1426 add_vrfy_fingerprint, vfd);
1427 g_signal_connect(G_OBJECT(dialog), "destroy",
1428 G_CALLBACK(vrfy_fingerprint_destroyed), vfd);
1429
1430 g_free(primary);
1431 g_free(secondary);
1432 }
1433
otrg_gtk_dialog_verify_fingerprint(Fingerprint * fprint)1434 static void otrg_gtk_dialog_verify_fingerprint(Fingerprint *fprint)
1435 {
1436 verify_fingerprint(NULL, fprint);
1437 }
1438
1439 /* Create the SMP dialog. responder is true if this is called in
1440 * response to someone else's run of SMP. */
otrg_gtk_dialog_socialist_millionaires(ConnContext * context,char * question,gboolean responder)1441 static void otrg_gtk_dialog_socialist_millionaires(ConnContext *context,
1442 char *question, gboolean responder)
1443 {
1444 char *primary;
1445
1446 if (context == NULL) return;
1447
1448 if (responder && question) {
1449 primary = g_strdup_printf(_("Authentication from %s"),
1450 context->username);
1451 } else {
1452 primary = g_strdup_printf(_("Authenticate %s"),
1453 context->username);
1454 }
1455
1456 create_smp_dialog(_("Authenticate Buddy"),
1457 primary, context, responder, question);
1458
1459 g_free(primary);
1460 }
1461
1462 /* Call this to update the status of an ongoing socialist millionaires
1463 * protocol. Progress_level is a percentage, from 0.0 (aborted) to
1464 * 1.0 (complete). Any other value represents an intermediate state. */
otrg_gtk_dialog_update_smp(ConnContext * context,OtrlSMPEvent smp_event,double progress_level)1465 static void otrg_gtk_dialog_update_smp(ConnContext *context,
1466 OtrlSMPEvent smp_event, double progress_level)
1467 {
1468 PurpleConversation *conv = otrg_plugin_context_to_conv(context, 0);
1469 GtkProgressBar *bar;
1470 SMPData *smp_data = purple_conversation_get_data(conv, "otr-smpdata");
1471
1472 if (!smp_data) return;
1473
1474 bar = GTK_PROGRESS_BAR(smp_data->smp_progress_bar);
1475 gtk_progress_bar_set_fraction(bar, progress_level);
1476
1477 /* If the counter is reset to absolute zero, the protocol has aborted */
1478 if (progress_level == 0.0) {
1479 GtkDialog *dialog = GTK_DIALOG(smp_data->smp_progress_dialog);
1480
1481 gtk_dialog_set_response_sensitive(dialog, GTK_RESPONSE_ACCEPT, 1);
1482 gtk_dialog_set_response_sensitive(dialog, GTK_RESPONSE_REJECT, 0);
1483 gtk_dialog_set_default_response(GTK_DIALOG(dialog),
1484 GTK_RESPONSE_ACCEPT);
1485
1486 gtk_label_set_text(GTK_LABEL(smp_data->smp_progress_label),
1487 _("An error occurred during authentication."));
1488 return;
1489 } else if (progress_level == 1.0) {
1490 /* If the counter reaches 1.0, the protocol is complete */
1491 GtkDialog *dialog = GTK_DIALOG(smp_data->smp_progress_dialog);
1492
1493 gtk_dialog_set_response_sensitive(dialog, GTK_RESPONSE_ACCEPT, 1);
1494 gtk_dialog_set_response_sensitive(dialog, GTK_RESPONSE_REJECT, 0);
1495 gtk_dialog_set_default_response(GTK_DIALOG(dialog),
1496 GTK_RESPONSE_ACCEPT);
1497
1498 if (smp_event == OTRL_SMPEVENT_SUCCESS) {
1499 if (context->active_fingerprint->trust &&
1500 context->active_fingerprint->trust[0]) {
1501 gtk_label_set_text(GTK_LABEL(smp_data->smp_progress_label),
1502 _("Authentication successful."));
1503 } else {
1504 gtk_label_set_text(GTK_LABEL(smp_data->smp_progress_label),
1505 _("Your buddy has successfully authenticated you. "
1506 "You may want to authenticate your buddy as "
1507 "well by asking your own question."));
1508 }
1509 } else {
1510 gtk_label_set_text(GTK_LABEL(smp_data->smp_progress_label),
1511 _("Authentication failed."));
1512 }
1513 } else {
1514 /* Clear the progress label */
1515 gtk_label_set_text(GTK_LABEL(smp_data->smp_progress_label), "");
1516 }
1517 }
1518
1519 /* Call this when a context transitions to ENCRYPTED. */
otrg_gtk_dialog_connected(ConnContext * context)1520 static void otrg_gtk_dialog_connected(ConnContext *context)
1521 {
1522 PurpleConversation *conv;
1523 char *buf;
1524 char *format_buf;
1525 TrustLevel level;
1526 OtrgUiPrefs prefs;
1527 gboolean * is_multi_inst;
1528
1529 conv = otrg_plugin_context_to_conv(context, TRUE);
1530 level = otrg_plugin_context_to_trust(context);
1531
1532 otrg_ui_get_prefs(&prefs, purple_conversation_get_account(conv),
1533 context->username);
1534 if (prefs.avoid_logging_otr) {
1535 purple_conversation_set_logging(conv, FALSE);
1536 }
1537
1538 switch(level) {
1539 case TRUST_PRIVATE:
1540 format_buf = g_strdup(
1541 _("Private conversation with %s started.%s%s"));
1542 break;
1543
1544 case TRUST_UNVERIFIED:
1545 format_buf = g_strdup_printf(_("<a href=\"%s%s\">Unverified</a> "
1546 "conversation with %%s started.%%s%%s"),
1547 UNVERIFIED_HELPURL, _("?lang=en"));
1548 break;
1549
1550 default:
1551 /* This last case should never happen, since we know
1552 * we're in ENCRYPTED. */
1553 format_buf = g_strdup(_("Not private conversation with %s "
1554 "started.%s%s"));
1555 break;
1556 }
1557 buf = g_strdup_printf(format_buf,
1558 purple_conversation_get_name(conv),
1559 context->protocol_version == 1 ? _(" Warning: using old "
1560 "protocol version 1.") : "", conv->logging ?
1561 _(" Your client is logging this conversation.") :
1562 _(" Your client is not logging this conversation."));
1563
1564 purple_conversation_write(conv, NULL, buf, PURPLE_MESSAGE_SYSTEM,
1565 time(NULL));
1566
1567 g_free(buf);
1568 g_free(format_buf);
1569
1570 dialog_update_label(context);
1571
1572 is_multi_inst = (gboolean *) purple_conversation_get_data(conv,
1573 "otr-conv_multi_instances");
1574
1575 if (*is_multi_inst) {
1576 gboolean * have_warned_instances = (gboolean *)
1577 purple_conversation_get_data(conv, "otr-warned_instances");
1578
1579 if (!*have_warned_instances) {
1580 *have_warned_instances = TRUE;
1581 buf = g_strdup_printf(_("Your buddy is logged in multiple times and"
1582 " OTR has established <a href=\"%s%s\">multiple sessions"
1583 "</a>. Use the icon menu above if you wish to select the "
1584 "outgoing session."), SESSIONS_HELPURL, _("?lang=en"));
1585 otrg_gtk_dialog_display_otr_message(context->accountname,
1586 context->protocol, context->username, buf, 0);
1587 g_free(buf);
1588 }
1589 }
1590 }
1591
1592 /* Call this when a context transitions to PLAINTEXT. */
otrg_gtk_dialog_disconnected(ConnContext * context)1593 static void otrg_gtk_dialog_disconnected(ConnContext *context)
1594 {
1595 PurpleConversation *conv;
1596 char *buf;
1597 OtrgUiPrefs prefs;
1598
1599 conv = otrg_plugin_context_to_conv(context, 1);
1600
1601 buf = g_strdup_printf(_("Private conversation with %s lost."),
1602 purple_conversation_get_name(conv));
1603
1604 purple_conversation_write(conv, NULL, buf, PURPLE_MESSAGE_SYSTEM,
1605 time(NULL));
1606
1607 g_free(buf);
1608
1609 otrg_ui_get_prefs(&prefs, purple_conversation_get_account(conv),
1610 context->username);
1611 if (prefs.avoid_logging_otr) {
1612 if (purple_prefs_get_bool("/purple/logging/log_ims")) {
1613 purple_conversation_set_logging(conv, TRUE);
1614 }
1615 }
1616
1617 dialog_update_label(context);
1618 close_smp_window(conv);
1619 }
1620
1621 /* Call this if the remote user terminates his end of an ENCRYPTED
1622 * connection, and lets us know. */
otrg_gtk_dialog_finished(const char * accountname,const char * protocol,const char * username)1623 static void otrg_gtk_dialog_finished(const char *accountname,
1624 const char *protocol, const char *username)
1625 {
1626 /* See if there's a conversation window we can put this in. */
1627 PurpleAccount *account;
1628 PurpleConversation *conv;
1629 ConnContext *context;
1630 char *buf;
1631
1632 account = purple_accounts_find(accountname, protocol);
1633 if (!account) return;
1634
1635 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM,
1636 username, account);
1637 if (!conv) return;
1638
1639 buf = g_strdup_printf(_("%s has ended his/her private conversation with "
1640 "you; you should do the same."),
1641 purple_conversation_get_name(conv));
1642
1643 purple_conversation_write(conv, NULL, buf, PURPLE_MESSAGE_SYSTEM,
1644 time(NULL));
1645
1646 g_free(buf);
1647
1648 context = otrg_plugin_conv_to_selected_context(conv, 0);
1649 dialog_update_label_conv(conv, otrg_plugin_context_to_trust(context));
1650 close_smp_window(conv);
1651 }
1652
1653 /* Call this when we receive a Key Exchange message that doesn't cause
1654 * our state to change (because it was just the keys we knew already). */
otrg_gtk_dialog_stillconnected(ConnContext * context)1655 static void otrg_gtk_dialog_stillconnected(ConnContext *context)
1656 {
1657 PurpleConversation *conv;
1658 char *buf;
1659 char *format_buf;
1660 TrustLevel level;
1661
1662 conv = otrg_plugin_context_to_conv(context, 1);
1663 level = otrg_plugin_context_to_trust(context);
1664
1665 switch(level) {
1666 case TRUST_PRIVATE:
1667 format_buf = g_strdup(_("Successfully refreshed the private "
1668 "conversation with %s.%s"));
1669 break;
1670
1671 case TRUST_UNVERIFIED:
1672 format_buf = g_strdup_printf(_("Successfully refreshed the "
1673 "<a href=\"%s%s\">unverified</a> conversation with "
1674 "%%s.%%s"),
1675 UNVERIFIED_HELPURL, _("?lang=en"));
1676 break;
1677
1678 default:
1679 /* This last case should never happen, since we know
1680 * we're in ENCRYPTED. */
1681 format_buf = g_strdup(_("Successfully refreshed the not private "
1682 "conversation with %s.%s"));
1683 break;
1684 }
1685
1686 buf = g_strdup_printf(format_buf,
1687 purple_conversation_get_name(conv),
1688 context->protocol_version == 1 ? _(" Warning: using old "
1689 "protocol version 1.") : "");
1690
1691 purple_conversation_write(conv, NULL, buf, PURPLE_MESSAGE_SYSTEM,
1692 time(NULL));
1693
1694 g_free(buf);
1695 g_free(format_buf);
1696
1697 dialog_update_label(context);
1698 }
1699
1700 /* This is called when the OTR button in the button box is clicked, or
1701 * when the appropriate context menu item is selected. */
otrg_gtk_dialog_clicked_connect(GtkWidget * widget,gpointer data)1702 static void otrg_gtk_dialog_clicked_connect(GtkWidget *widget, gpointer data)
1703 {
1704 const char *format;
1705 char *buf;
1706 PurpleConversation *conv = data;
1707 PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv);
1708
1709 if (gtkconv->active_conv != conv) {
1710 pidgin_conv_switch_active_conversation(conv);
1711 }
1712
1713 if (purple_conversation_get_data(conv, "otr-private")) {
1714 format = _("Attempting to refresh the private conversation with %s...");
1715 } else {
1716 format = _("Attempting to start a private conversation with %s...");
1717 }
1718 buf = g_strdup_printf(format, purple_conversation_get_name(conv));
1719
1720 purple_conversation_write(conv, NULL, buf, PURPLE_MESSAGE_SYSTEM,
1721 time(NULL));
1722
1723 g_free(buf);
1724
1725 otrg_plugin_send_default_query_conv(conv);
1726 }
1727
1728 /* Called when SMP verification option selected from menu */
socialist_millionaires(GtkWidget * widget,gpointer data)1729 static void socialist_millionaires(GtkWidget *widget, gpointer data)
1730 {
1731 ConvOrContext *convctx = data;
1732 PurpleConversation *conv;
1733 ConnContext *context = NULL;
1734
1735 if (convctx->convctx_type == convctx_conv) {
1736 conv = convctx->conv;
1737 context = otrg_plugin_conv_to_selected_context(conv, 0);
1738 } else if (convctx->convctx_type == convctx_ctx) {
1739 context = convctx->context;
1740 }
1741
1742 if (context == NULL || context->msgstate != OTRL_MSGSTATE_ENCRYPTED)
1743 return;
1744
1745 otrg_gtk_dialog_socialist_millionaires(context, NULL, FALSE);
1746 }
1747
menu_whatsthis(GtkWidget * widget,gpointer data)1748 static void menu_whatsthis(GtkWidget *widget, gpointer data)
1749 {
1750 char *uri = g_strdup_printf("%s%s", LEVELS_HELPURL, _("?lang=en"));
1751 purple_notify_uri(otrg_plugin_handle, uri);
1752 g_free(uri);
1753 }
1754
menu_end_private_conversation(GtkWidget * widget,gpointer data)1755 static void menu_end_private_conversation(GtkWidget *widget, gpointer data)
1756 {
1757 PurpleConversation *conv;
1758 ConnContext *context = NULL;
1759 ConvOrContext *convctx = data;
1760
1761 if (convctx->convctx_type == convctx_conv) {
1762 conv = convctx->conv;
1763 context = otrg_plugin_conv_to_selected_context(conv, 0);
1764 } else if (convctx->convctx_type == convctx_ctx) {
1765 context = convctx->context;
1766 }
1767
1768
1769 otrg_ui_disconnect_connection(context);
1770 }
1771
1772 static void dialog_resensitize(PurpleConversation *conv);
1773
1774 /* If the OTR button is right-clicked, show the context menu. */
button_pressed(GtkWidget * w,GdkEventButton * event,gpointer data)1775 static gboolean button_pressed(GtkWidget *w, GdkEventButton *event,
1776 gpointer data)
1777 {
1778 PurpleConversation *conv = data;
1779
1780 /* Any button will do */
1781 if (event->type == GDK_BUTTON_PRESS) {
1782 GtkWidget *menu = purple_conversation_get_data(conv, "otr-menu");
1783 if (menu) {
1784 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
1785 3, event->time);
1786 return TRUE;
1787 }
1788 }
1789
1790 return FALSE;
1791 }
1792
1793 static void otrg_gtk_dialog_new_purple_conv(PurpleConversation *conv);
1794
1795
otr_refresh_otr_buttons(PurpleConversation * conv)1796 static void otr_refresh_otr_buttons(PurpleConversation *conv)
1797 {
1798 PidginConversation *gtkconv = PIDGIN_CONVERSATION ( conv );
1799 GList * list_iter = gtkconv->convs;
1800 PurpleConversation * current_conv;
1801 GtkWidget *button;
1802
1803 for (;list_iter;list_iter = list_iter->next) {
1804
1805 current_conv = list_iter->data;
1806 button = purple_conversation_get_data(current_conv, "otr-button");
1807
1808 if (button) {
1809 if (current_conv == gtkconv->active_conv) {
1810 gtk_widget_show (button);
1811 } else {
1812 gtk_widget_hide (button);
1813 }
1814 }
1815 }
1816 }
1817
1818 /* Menu has been destroyed -- let's remove it from the menu_list
1819 * so that it won't be destroyed again. */
otr_menu_destroy(GtkWidget * widget,gpointer pdata)1820 static void otr_menu_destroy(GtkWidget *widget, gpointer pdata)
1821 {
1822 PidginWindow *win = (PidginWindow *) pdata ;
1823 GtkWidget *top_menu = widget;
1824
1825 GList * menu_list = g_hash_table_lookup ( otr_win_menus, win );
1826 menu_list = g_list_remove ( menu_list, top_menu );
1827 g_hash_table_replace ( otr_win_menus, win, menu_list );
1828 }
1829
otr_clear_win_menu_list(PidginWindow * win)1830 static void otr_clear_win_menu_list(PidginWindow *win)
1831 {
1832 GList * head = g_hash_table_lookup ( otr_win_menus, win ); /* menu_list */
1833 GList * old_head = 0;
1834
1835 while(head) {
1836 old_head = head;
1837 gtk_object_destroy ( GTK_OBJECT ( head->data ) );
1838 head = g_hash_table_lookup ( otr_win_menus, win );
1839
1840 if (head && head == old_head) {
1841 /* The head was not removed by the "destroyed" callback
1842 Something is wrong */
1843 break;
1844 }
1845 }
1846
1847 g_hash_table_replace ( otr_win_menus, win, head );
1848 }
1849
otr_destroy_top_menu_objects(PurpleConversation * conv)1850 static void otr_destroy_top_menu_objects(PurpleConversation *conv)
1851 {
1852 PidginConversation *gtkconv = PIDGIN_CONVERSATION ( conv );
1853 PidginWindow *win = pidgin_conv_get_window ( gtkconv );
1854
1855 otr_clear_win_menu_list(win);
1856 }
1857
otr_get_menu_insert_pos(PurpleConversation * conv)1858 static int otr_get_menu_insert_pos(PurpleConversation *conv)
1859 {
1860 PidginConversation *gtkconv = PIDGIN_CONVERSATION ( conv );
1861 PidginWindow *win = pidgin_conv_get_window ( gtkconv );
1862 GtkWidget *menu_bar = win->menu.menubar;
1863
1864 GList * list_iter = gtk_container_get_children(GTK_CONTAINER(menu_bar));
1865 GList * head = list_iter;
1866
1867 int pos = 0;
1868 while ( list_iter ) {
1869 pos++;
1870 list_iter = list_iter->next;
1871 }
1872
1873 if (pos != 0) pos--;
1874
1875 g_list_free ( head );
1876
1877 return pos;
1878 }
1879
otr_set_menu_labels(ConvOrContext * convctx,GtkWidget * query,GtkWidget * end,GtkWidget * smp)1880 static void otr_set_menu_labels(ConvOrContext *convctx, GtkWidget *query,
1881 GtkWidget *end, GtkWidget *smp) {
1882 PurpleConversation *conv;
1883 int insecure = 0;
1884 int authen = 0;
1885 int finished = 0;
1886 TrustLevel level = TRUST_NOT_PRIVATE;
1887
1888
1889 if (convctx->convctx_type == convctx_conv) {
1890 conv = convctx->conv;
1891 insecure = purple_conversation_get_data(conv, "otr-private") ? 0 : 1;
1892 authen = purple_conversation_get_data(conv, "otr-authenticated") ? 1 :0;
1893 finished = purple_conversation_get_data(conv, "otr-finished") ? 1 : 0;
1894 } else if (convctx->convctx_type == convctx_ctx) {
1895 level = otrg_plugin_context_to_trust(convctx->context);
1896 insecure = level == TRUST_UNVERIFIED || level == TRUST_PRIVATE ? 0 : 1;
1897 authen = level == TRUST_PRIVATE ? 1 : 0;
1898 finished = level == TRUST_FINISHED ? 1 : 0;
1899 } else {
1900 return;
1901 }
1902
1903 GtkWidget * label = gtk_bin_get_child(GTK_BIN(query));
1904
1905 gtk_label_set_markup_with_mnemonic(GTK_LABEL(label),
1906 insecure ? _("Start _private conversation") :
1907 _("Refresh _private conversation"));
1908
1909 label = gtk_bin_get_child(GTK_BIN(smp));
1910
1911 gtk_label_set_markup_with_mnemonic(GTK_LABEL(label),
1912 (!insecure && authen) ? _("Re_authenticate buddy") :
1913 _("_Authenticate buddy"));
1914
1915 gtk_widget_set_sensitive(GTK_WIDGET(end), !insecure || finished);
1916 gtk_widget_set_sensitive(GTK_WIDGET(smp), !insecure);
1917 }
1918
force_deselect(GtkItem * item,gpointer data)1919 static void force_deselect(GtkItem *item, gpointer data)
1920 {
1921 gtk_item_deselect(item);
1922 }
1923
otr_build_status_submenu(PidginWindow * win,ConvOrContext * convctx,GtkWidget * menu,TrustLevel level)1924 static void otr_build_status_submenu(PidginWindow *win,
1925 ConvOrContext *convctx, GtkWidget *menu, TrustLevel level) {
1926 char *status = "";
1927 GtkWidget *image;
1928 GtkWidget *levelimage;
1929 GtkWidget *buddy_name;
1930 GtkWidget *buddy_status;
1931 GtkWidget *menusep, *menusep2;
1932 GdkPixbuf *pixbuf;
1933 GtkWidget *whatsthis;
1934
1935 gchar *text = NULL;
1936
1937 PurpleConversation *conv;
1938
1939 if (convctx->convctx_type == convctx_conv) {
1940 conv = convctx->conv;
1941 } else if (convctx->convctx_type == convctx_ctx) {
1942 conv = otrg_plugin_context_to_conv(convctx->context, 0);
1943 } else {
1944 return;
1945 }
1946
1947 text = g_strdup_printf("%s (%s)", conv->name,
1948 purple_account_get_username(conv->account));
1949
1950 buddy_name = gtk_image_menu_item_new_with_label(text);
1951 g_free(text);
1952
1953 /* Create a pixmap for the protocol icon. */
1954 pixbuf = pidgin_create_prpl_icon(conv->account, PIDGIN_PRPL_ICON_SMALL);
1955
1956 /* Now convert it to GtkImage */
1957 if (pixbuf == NULL) {
1958 image = gtk_image_new();
1959 } else {
1960 image = gtk_image_new_from_pixbuf(pixbuf);
1961 }
1962
1963 gtk_image_menu_item_set_image ( GTK_IMAGE_MENU_ITEM ( buddy_name ), image);
1964
1965 switch(level) {
1966 case TRUST_NOT_PRIVATE:
1967 status = _("Not Private");
1968 break;
1969 case TRUST_UNVERIFIED:
1970 status = _("Unverified");
1971 break;
1972 case TRUST_PRIVATE:
1973 status = _("Private");
1974 break;
1975 case TRUST_FINISHED:
1976 status = _("Finished");
1977 break;
1978 }
1979
1980 buddy_status = gtk_image_menu_item_new_with_label(status);
1981
1982 levelimage = otr_icon(NULL, level, 1);
1983
1984 gtk_image_menu_item_set_image ( GTK_IMAGE_MENU_ITEM ( buddy_status ),
1985 levelimage);
1986
1987 menusep = gtk_separator_menu_item_new();
1988 menusep2 = gtk_separator_menu_item_new();
1989 whatsthis = gtk_image_menu_item_new_with_mnemonic(_("_What's this?"));
1990 gtk_image_menu_item_set_image ( GTK_IMAGE_MENU_ITEM ( whatsthis ),
1991 gtk_image_new_from_stock(GTK_STOCK_HELP,
1992 gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL)));
1993
1994 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menusep);
1995 gtk_menu_shell_append(GTK_MENU_SHELL(menu), buddy_name);
1996 gtk_menu_shell_append(GTK_MENU_SHELL(menu), buddy_status);
1997 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menusep2);
1998 gtk_menu_shell_append(GTK_MENU_SHELL(menu), whatsthis);
1999
2000 gtk_widget_show(menusep);
2001 gtk_widget_show_all(buddy_name);
2002 gtk_widget_show_all(buddy_status);
2003 gtk_widget_show(menusep2);
2004 gtk_widget_show_all(whatsthis);
2005
2006 gtk_signal_connect(GTK_OBJECT(buddy_name), "select",
2007 GTK_SIGNAL_FUNC(force_deselect), NULL);
2008 gtk_signal_connect(GTK_OBJECT(buddy_status), "select",
2009 GTK_SIGNAL_FUNC(force_deselect), NULL);
2010 gtk_signal_connect(GTK_OBJECT(whatsthis), "activate",
2011 GTK_SIGNAL_FUNC(menu_whatsthis), conv);
2012 }
2013
build_otr_menu(ConvOrContext * convctx,GtkWidget * menu,TrustLevel level)2014 static void build_otr_menu(ConvOrContext *convctx, GtkWidget *menu,
2015 TrustLevel level)
2016 {
2017 PurpleConversation *conv;
2018
2019 if (convctx->convctx_type == convctx_conv) {
2020 conv = convctx->conv;
2021 } else if (convctx->convctx_type == convctx_ctx) {
2022 conv = otrg_plugin_context_to_conv(convctx->context, 0);
2023 } else {
2024 return;
2025 }
2026
2027 GtkWidget *buddymenuquery = gtk_menu_item_new_with_mnemonic(
2028 _("Start _private conversation"));
2029 GtkWidget *buddymenuend = gtk_menu_item_new_with_mnemonic(
2030 _("_End private conversation"));
2031 GtkWidget *buddymenusmp = gtk_menu_item_new_with_mnemonic(
2032 _("_Authenticate buddy"));
2033
2034 otr_set_menu_labels(convctx, buddymenuquery, buddymenuend, buddymenusmp);
2035
2036 /* Empty out the menu */
2037 gtk_container_foreach(GTK_CONTAINER(menu), destroy_menuitem, NULL);
2038
2039 gtk_menu_shell_append(GTK_MENU_SHELL(menu), buddymenuquery);
2040 gtk_menu_shell_append(GTK_MENU_SHELL(menu), buddymenuend);
2041 gtk_menu_shell_append(GTK_MENU_SHELL(menu), buddymenusmp);
2042
2043 gtk_widget_show(buddymenuquery);
2044 gtk_widget_show(buddymenuend);
2045 gtk_widget_show(buddymenusmp);
2046
2047 gtk_signal_connect(GTK_OBJECT(buddymenuquery), "activate",
2048 GTK_SIGNAL_FUNC(otrg_gtk_dialog_clicked_connect), conv);
2049 gtk_signal_connect(GTK_OBJECT(buddymenuend), "activate",
2050 GTK_SIGNAL_FUNC(menu_end_private_conversation), convctx);
2051 gtk_signal_connect(GTK_OBJECT(buddymenusmp), "activate",
2052 GTK_SIGNAL_FUNC(socialist_millionaires), convctx);
2053
2054 }
2055
otr_add_top_otr_menu(PurpleConversation * conv)2056 static void otr_add_top_otr_menu(PurpleConversation *conv)
2057 {
2058 PidginConversation *gtkconv = PIDGIN_CONVERSATION ( conv );
2059 PidginWindow *win = pidgin_conv_get_window ( gtkconv );
2060 GtkWidget *menu_bar = win->menu.menubar;
2061
2062 GList * menu_list = g_hash_table_lookup ( otr_win_menus, win );
2063
2064 GtkWidget *topmenu;
2065 GtkWidget *topmenuitem;
2066
2067 TrustLevel level = TRUST_NOT_PRIVATE;
2068 ConnContext *context = otrg_plugin_conv_to_selected_context(conv, 1);
2069
2070 ConvOrContext *convctx;
2071
2072 GHashTable * conv_or_ctx_map = purple_conversation_get_data(conv,
2073 "otr-convorctx");
2074
2075 int pos = otr_get_menu_insert_pos(conv);
2076
2077 if (purple_conversation_get_type(conv) != PURPLE_CONV_TYPE_IM) return;
2078
2079 topmenuitem = gtk_menu_item_new_with_label ( "OTR" );
2080 topmenu = gtk_menu_new();
2081
2082 if (context != NULL) {
2083 level = otrg_plugin_context_to_trust(context);
2084 }
2085
2086 convctx = g_hash_table_lookup(conv_or_ctx_map, conv);
2087
2088 if (!convctx) {
2089 convctx = malloc(sizeof(ConvOrContext));
2090 g_hash_table_insert(conv_or_ctx_map, conv, (gpointer)convctx);
2091 }
2092
2093 convctx->convctx_type = convctx_conv;
2094 convctx->conv = conv;
2095 build_otr_menu(convctx, topmenu, level);
2096 otr_build_status_submenu(win, convctx, topmenu, level);
2097
2098 gtk_menu_item_set_submenu ( GTK_MENU_ITEM ( topmenuitem ), topmenu );
2099
2100 gtk_widget_show(topmenuitem);
2101 gtk_widget_show(topmenu);
2102
2103 gtk_menu_shell_insert ( GTK_MENU_SHELL ( menu_bar ), topmenuitem, pos++ );
2104
2105 g_signal_connect(G_OBJECT(topmenuitem), "destroy",
2106 G_CALLBACK(otr_menu_destroy), win);
2107
2108 menu_list = g_list_append(menu_list, topmenuitem);
2109
2110 g_hash_table_replace ( otr_win_menus, win, menu_list );
2111 }
2112
otr_get_full_buddy_list(PurpleConversation * conv)2113 static GList* otr_get_full_buddy_list(PurpleConversation *conv)
2114 {
2115 PidginConversation *gtkconv = PIDGIN_CONVERSATION ( conv );
2116
2117 GList *pres_list = NULL;
2118 GList *conv_list = NULL;
2119
2120 GSList *l, *buds;
2121
2122 /* This code is derived from pidgin's 'generating sendto menu' stuff */
2123 if ( gtkconv->active_conv->type == PURPLE_CONV_TYPE_IM ) {
2124 buds = purple_find_buddies ( gtkconv->active_conv->account,
2125 gtkconv->active_conv->name );
2126
2127 if ( buds == NULL
2128 && !g_list_find(conv_list, conv)) { /* buddy not on list */
2129 conv_list = g_list_prepend ( conv_list, conv);
2130 } else {
2131 for ( l = buds; l != NULL; l = l->next ) {
2132 PurpleBlistNode *node = ( PurpleBlistNode * )
2133 purple_buddy_get_contact ( ( PurpleBuddy * ) l->data );
2134
2135 for ( node = node->child; node != NULL; node = node->next ) {
2136 PurpleBuddy *buddy = ( PurpleBuddy * ) node;
2137 PurpleAccount *account;
2138
2139 if ( !PURPLE_BLIST_NODE_IS_BUDDY ( node ) )
2140 continue;
2141
2142 account = purple_buddy_get_account ( buddy );
2143 if ( purple_account_is_connected ( account ) ) {
2144 /* Use the PurplePresence to get unique buddies. */
2145 PurplePresence *presence =
2146 purple_buddy_get_presence( buddy );
2147 if ( !g_list_find ( pres_list, presence ) ) {
2148
2149 PurpleConversation * currentConv =
2150 purple_find_conversation_with_account(
2151 PURPLE_CONV_TYPE_IM, \
2152 purple_buddy_get_name ( buddy ),
2153 purple_buddy_get_account ( buddy ));
2154
2155 pres_list = g_list_prepend ( pres_list, presence );
2156
2157 if (currentConv != NULL &&
2158 !g_list_find(conv_list, currentConv)) {
2159 conv_list = g_list_prepend ( conv_list,
2160 currentConv );
2161 }
2162
2163 }
2164 }
2165 }
2166 }
2167
2168 g_slist_free ( buds );
2169 g_list_free( pres_list );
2170 }
2171 }
2172
2173 return conv_list;
2174 }
2175
unselect_meta_ctx(PurpleConversation * conv)2176 static void unselect_meta_ctx(PurpleConversation *conv)
2177 {
2178 GtkWidget *select_best = (GtkWidget *) purple_conversation_get_data(conv,
2179 "otr-select_best");
2180 GtkWidget *select_recent = (GtkWidget *) purple_conversation_get_data(conv,
2181 "otr-select_recent");
2182
2183 GTK_CHECK_MENU_ITEM(select_recent)->active = 0;
2184 GTK_CHECK_MENU_ITEM(select_best)->active = 0;
2185 }
2186
select_meta_ctx(GtkWidget * widget,gpointer data)2187 static void select_meta_ctx(GtkWidget *widget, gpointer data)
2188 {
2189 PurpleConversation *conv = (PurpleConversation *) data;
2190 GtkWidget *select_best = (GtkWidget *) purple_conversation_get_data(conv,
2191 "otr-select_best");
2192 GtkWidget *select_recent = (GtkWidget *) purple_conversation_get_data(conv,
2193 "otr-select_recent");
2194 gboolean value = FALSE;
2195 otrl_instag_t * selected_instance = (otrl_instag_t *)
2196 purple_conversation_get_data(conv, "otr-ui_selected_ctx");
2197 ConnContext * context = NULL;
2198 ConnContext * recent_context = NULL;
2199
2200 value = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget));
2201
2202 if (widget == select_best) {
2203 GTK_CHECK_MENU_ITEM(select_recent)->active = !value;
2204
2205 if (value) {
2206 if (selected_instance) {
2207 *selected_instance = OTRL_INSTAG_BEST;
2208 }
2209 context = (ConnContext *) otrg_plugin_conv_to_selected_context(conv,
2210 1);
2211
2212 recent_context = (ConnContext *) otrg_plugin_conv_to_context(conv,
2213 OTRL_INSTAG_RECENT_RECEIVED, 0);
2214 if (context != recent_context) {
2215 gchar *buf = g_strdup_printf(_("Warning: The selected outgoing "
2216 "OTR session (%u) is not the most recently active "
2217 "one (%u). Your buddy may not receive your messages."
2218 " Use the icon menu above to select a different "
2219 "outgoing session."),
2220 get_context_instance_to_index(conv, context),
2221 get_context_instance_to_index(conv, recent_context));
2222 otrg_gtk_dialog_display_otr_message(context->accountname,
2223 context->protocol, context->username, buf, 0);
2224 g_free(buf);
2225 }
2226
2227 }
2228
2229 } else if (widget == select_recent) {
2230 GTK_CHECK_MENU_ITEM(select_best)->active = !value;
2231
2232 if (value && selected_instance) {
2233 *selected_instance = OTRL_INSTAG_RECENT_RECEIVED;
2234 }
2235 }
2236
2237 if (!context) context = (ConnContext *)
2238 otrg_plugin_conv_to_selected_context(conv, 1);
2239
2240 pidgin_conv_switch_active_conversation(conv);
2241 dialog_update_label(context);
2242 }
2243
select_menu_ctx(GtkWidget * widget,gpointer data)2244 static void select_menu_ctx(GtkWidget *widget, gpointer data)
2245 {
2246 ConnContext *context = (ConnContext *) data;
2247 PurpleConversation *conv = otrg_plugin_context_to_conv(context, 1);
2248 ConnContext *recent_context = (ConnContext *) otrg_plugin_conv_to_context(
2249 conv, (otrl_instag_t)OTRL_INSTAG_RECENT_RECEIVED, 0);
2250 otrl_instag_t *selected_instance = (otrl_instag_t *)
2251 purple_conversation_get_data(conv, "otr-ui_selected_ctx");
2252 gboolean *is_multi_instance = purple_conversation_get_data(conv,
2253 "otr-conv_multi_instances");
2254
2255 if (is_multi_instance && *is_multi_instance) {
2256 if (selected_instance) {
2257 *selected_instance = context->their_instance;
2258 }
2259 unselect_meta_ctx(conv);
2260 }
2261
2262 pidgin_conv_switch_active_conversation(conv);
2263 dialog_update_label(context);
2264
2265 if (is_multi_instance && *is_multi_instance && context != recent_context) {
2266 gchar *buf = g_strdup_printf(_("Warning: The selected outgoing OTR "
2267 "session (%u) is not the most recently active one (%u). "
2268 "Your buddy may not receive your messages. Use the icon menu "
2269 "above to select a different outgoing session."),
2270 get_context_instance_to_index(conv, context),
2271 get_context_instance_to_index(conv, recent_context));
2272 otrg_gtk_dialog_display_otr_message(context->accountname,
2273 context->protocol, context->username, buf, 0);
2274 g_free(buf);
2275 }
2276 }
2277
build_meta_instance_submenu(PurpleConversation * conv,GtkWidget * menu)2278 static void build_meta_instance_submenu( PurpleConversation *conv,
2279 GtkWidget *menu) {
2280 GtkWidget *menusep = gtk_separator_menu_item_new();
2281 GtkWidget *select_best = gtk_check_menu_item_new_with_label(
2282 _("Send to most secure"));
2283 GtkWidget *select_recent = gtk_check_menu_item_new_with_label(
2284 _("Send to most recent"));
2285 otrl_instag_t * selected_instance = purple_conversation_get_data(conv,
2286 "otr-ui_selected_ctx");
2287
2288 if (!selected_instance || *selected_instance == OTRL_INSTAG_BEST) {
2289 GTK_CHECK_MENU_ITEM(select_recent)->active = 0;
2290 GTK_CHECK_MENU_ITEM(select_best)->active = 1;
2291 } else if (*selected_instance == OTRL_INSTAG_RECENT_RECEIVED) {
2292 GTK_CHECK_MENU_ITEM(select_recent)->active = 1;
2293 GTK_CHECK_MENU_ITEM(select_best)->active = 0;
2294 } else {
2295 GTK_CHECK_MENU_ITEM(select_recent)->active = 0;
2296 GTK_CHECK_MENU_ITEM(select_best)->active = 0;
2297 }
2298
2299 purple_conversation_set_data(conv, "otr-select_best", select_best);
2300 purple_conversation_set_data(conv, "otr-select_recent", select_recent);
2301
2302 gtk_signal_connect(GTK_OBJECT(select_best), "toggled",
2303 GTK_SIGNAL_FUNC(select_meta_ctx), conv);
2304 gtk_signal_connect(GTK_OBJECT(select_recent), "toggled",
2305 GTK_SIGNAL_FUNC(select_meta_ctx), conv);
2306
2307 gtk_widget_show(menusep);
2308 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menusep);
2309 gtk_widget_show(select_best);
2310 gtk_menu_shell_append(GTK_MENU_SHELL(menu), select_best);
2311 gtk_widget_show(select_recent);
2312 gtk_menu_shell_append(GTK_MENU_SHELL(menu), select_recent);
2313 }
2314
2315 /* Build an OTR buddy menu (where the root menu item is an icon corresponding
2316 * to the conversation status) for a conversation that has multiple instances.
2317 * The ConnContexts are given in the GList "instances". Keep track of the
2318 * position this menu was inserted in the "pos" argument. "active_conv"
2319 * corresponds to whether this conversation is the active PurpleConversation
2320 * for this PidginConversation pane.
2321 */
otr_add_buddy_instances_top_menu(PidginConversation * gtkconv,GList * instances,gboolean active_conv,const char * username,const char * accountname,int * pos)2322 static void otr_add_buddy_instances_top_menu(PidginConversation *gtkconv,
2323 GList *instances, gboolean active_conv, const char *username,
2324 const char *accountname, int *pos) {
2325 PidginWindow *win = pidgin_conv_get_window ( gtkconv );
2326 GtkWidget *menu_bar = win->menu.menubar;
2327 GtkWidget *menu;
2328 GtkWidget *menu_image;
2329 GtkWidget * tooltip_menu;
2330 gchar *tooltip_text;
2331 gpointer gp_instance;
2332 otrl_instag_t * selected_instance = NULL;
2333 gboolean selection_exists = 0;
2334 ConnContext * context = instances->data;
2335 TrustLevel level = TRUST_NOT_PRIVATE;
2336 GHashTable * conv_or_ctx_map;
2337 PurpleConversation * conv = NULL;
2338 ConvOrContext convctx;
2339 GList * menu_list;
2340
2341 conv = otrg_plugin_context_to_conv(context, 0);
2342 selection_exists = g_hash_table_lookup_extended(conv->data,
2343 "otr-ui_selected_ctx", NULL, &gp_instance);
2344
2345 /* Find the selected or default instance */
2346 if (selection_exists) {
2347 selected_instance = gp_instance;
2348 context = otrl_context_find(otrg_plugin_userstate,
2349 context->username, context->accountname, context->protocol,
2350 *selected_instance, 0, NULL, NULL, NULL);
2351 } else {
2352 context = otrl_context_find(otrg_plugin_userstate,
2353 context->username, context->accountname, context->protocol,
2354 OTRL_INSTAG_BEST, 0, NULL, NULL, NULL);
2355 }
2356
2357 menu = gtk_menu_new();
2358
2359 conv_or_ctx_map = purple_conversation_get_data(conv, "otr-convorctx");
2360
2361 for (; instances; instances = instances->next) {
2362 GtkWidget *instance_menu_item;
2363 GtkWidget *instance_submenu;
2364 gchar* text;
2365 ConnContext *curr_context = instances->data;
2366 ConvOrContext * curr_convctx = g_hash_table_lookup(conv_or_ctx_map,
2367 curr_context);
2368 gboolean selected = (curr_context->their_instance ==
2369 context->their_instance);
2370 gint instance_i = -1;
2371
2372 if (curr_context->m_context == curr_context &&
2373 curr_context->msgstate == OTRL_MSGSTATE_PLAINTEXT) {
2374 continue;
2375 }
2376
2377 if (!curr_convctx) {
2378 curr_convctx = malloc(sizeof(ConvOrContext));
2379 g_hash_table_insert(conv_or_ctx_map, curr_context,
2380 (gpointer)curr_convctx);
2381 curr_convctx->convctx_type = convctx_ctx;
2382 curr_convctx->context = curr_context;
2383 }
2384
2385
2386 instance_i = get_context_instance_to_index(conv, curr_context);
2387
2388 text = g_strdup_printf(_("Session %u"), instance_i);
2389
2390 instance_menu_item = gtk_image_menu_item_new_with_label(text);
2391 instance_submenu = gtk_menu_new();
2392
2393 level = otrg_plugin_context_to_trust(curr_context);
2394 menu_image = otr_icon(NULL, level, selected);
2395
2396 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(instance_menu_item),
2397 menu_image);
2398 gtk_image_menu_item_set_always_show_image(
2399 GTK_IMAGE_MENU_ITEM(instance_menu_item), 1);
2400
2401 build_otr_menu(curr_convctx, instance_submenu, level);
2402
2403 g_free(text);
2404
2405 if (!selection_exists ||
2406 *selected_instance != curr_context->their_instance) {
2407 GtkWidget *select_ctx = gtk_menu_item_new_with_label(_("Select"));
2408 GtkWidget *menusep = gtk_separator_menu_item_new();
2409
2410 gtk_signal_connect(GTK_OBJECT(select_ctx), "activate",
2411 GTK_SIGNAL_FUNC(select_menu_ctx), curr_context);
2412
2413 gtk_menu_shell_prepend(GTK_MENU_SHELL(instance_submenu), menusep);
2414 gtk_widget_show(menusep);
2415
2416 gtk_menu_shell_prepend(GTK_MENU_SHELL(instance_submenu),
2417 select_ctx);
2418 gtk_widget_show(select_ctx);
2419 } else if (selection_exists &&
2420 *selected_instance == curr_context->their_instance) {
2421 GtkWidget *selected_ctx =
2422 gtk_menu_item_new_with_label(_("Selected"));
2423 GtkWidget *menusep = gtk_separator_menu_item_new();
2424
2425 gtk_signal_connect(GTK_OBJECT(selected_ctx), "select",
2426 GTK_SIGNAL_FUNC(force_deselect), NULL);
2427
2428 gtk_menu_shell_prepend(GTK_MENU_SHELL(instance_submenu), menusep);
2429 gtk_widget_show(menusep);
2430
2431 gtk_menu_shell_prepend(GTK_MENU_SHELL(instance_submenu),
2432 selected_ctx);
2433 gtk_widget_show(selected_ctx);
2434 }
2435
2436 gtk_widget_show(menu_image);
2437 gtk_widget_show(instance_menu_item);
2438 gtk_widget_show(instance_submenu);
2439 gtk_menu_item_set_submenu (GTK_MENU_ITEM (instance_menu_item),
2440 instance_submenu);
2441
2442 gtk_menu_shell_append(GTK_MENU_SHELL(menu), instance_menu_item);
2443
2444 }
2445
2446
2447 level = otrg_plugin_context_to_trust(context);
2448 menu_image = otr_icon(NULL, level, active_conv);
2449 convctx.convctx_type = convctx_ctx;
2450 convctx.context = context;
2451
2452 build_meta_instance_submenu(conv, menu);
2453
2454 otr_build_status_submenu(win, &convctx, menu, level);
2455
2456 tooltip_menu = tooltip_menu_new();
2457
2458 gtk_widget_show ( menu_image );
2459 gtk_widget_show(tooltip_menu);
2460 gtk_menu_shell_insert ( GTK_MENU_SHELL(menu_bar), tooltip_menu, (*pos)++);
2461 gtk_menu_item_set_submenu ( GTK_MENU_ITEM(tooltip_menu), menu);
2462
2463 tooltip_text = g_strdup_printf("%s (%s)", username, accountname);
2464 tooltip_menu_prepend(TOOLTIP_MENU(tooltip_menu), menu_image, tooltip_text);
2465 g_free(tooltip_text);
2466
2467 menu_list = g_hash_table_lookup ( otr_win_menus, win );
2468
2469 g_signal_connect(G_OBJECT(tooltip_menu), "destroy",
2470 G_CALLBACK(otr_menu_destroy), win);
2471
2472 menu_list = g_list_append(menu_list, tooltip_menu);
2473
2474 g_hash_table_replace ( otr_win_menus, win, menu_list );
2475 }
2476
2477 /* Build an OTR buddy menu (where the root menu item is an icon corresponding
2478 * to the conversation status) for a conversation that does not have multiple
2479 * instances. A pre-allocated ConvOrContext is given. Keep track of the
2480 * position this menu was inserted in the "pos" argument. "active_conv"
2481 * corresponds to whether this conversation is the active PurpleConversation
2482 * for this PidginConversation pane.
2483 */
otr_add_buddy_top_menu(PidginConversation * gtkconv,ConvOrContext * convctx,gboolean active_conv,const char * username,const char * accountname,int * pos)2484 static void otr_add_buddy_top_menu(PidginConversation *gtkconv,
2485 ConvOrContext *convctx, gboolean active_conv, const char *username,
2486 const char *accountname, int *pos) {
2487 PidginWindow *win = pidgin_conv_get_window ( gtkconv );
2488 GtkWidget *menu_bar = win->menu.menubar;
2489 GtkWidget *menu;
2490 GtkWidget *menu_image;
2491 TrustLevel level;
2492 ConnContext *context = NULL;
2493 GList * menu_list;
2494 GtkWidget * tooltip_menu;
2495 gchar *tooltip_text;
2496 GtkWidget *select_ctx = NULL;
2497
2498 if (convctx->convctx_type == convctx_ctx) {
2499 context = convctx->context;
2500 } else if (convctx->convctx_type == convctx_conv) {
2501 context = otrg_plugin_conv_to_selected_context(convctx->conv, 0);
2502 }
2503
2504 level = TRUST_NOT_PRIVATE;
2505
2506 if (context != NULL) {
2507 level = otrg_plugin_context_to_trust(context);
2508 }
2509
2510 menu_image = otr_icon(NULL, level, active_conv);
2511
2512 menu = gtk_menu_new();
2513
2514 build_otr_menu(convctx, menu, level);
2515 otr_build_status_submenu(win, convctx, menu, level);
2516
2517 if (!active_conv) {
2518 select_ctx = gtk_menu_item_new_with_label(_("Select"));
2519
2520 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), select_ctx);
2521 gtk_widget_show(select_ctx);
2522
2523 gtk_signal_connect(GTK_OBJECT(select_ctx), "activate",
2524 GTK_SIGNAL_FUNC(select_menu_ctx), context);
2525 }
2526
2527 tooltip_menu = tooltip_menu_new();
2528
2529 gtk_widget_show ( menu_image );
2530 gtk_widget_show(tooltip_menu);
2531 gtk_menu_shell_insert ( GTK_MENU_SHELL(menu_bar), tooltip_menu, (*pos)++);
2532 gtk_menu_item_set_submenu ( GTK_MENU_ITEM ( tooltip_menu ), menu );
2533
2534 tooltip_text = g_strdup_printf("%s (%s)", username, accountname);
2535 tooltip_menu_prepend(TOOLTIP_MENU(tooltip_menu), menu_image, tooltip_text);
2536 g_free(tooltip_text);
2537
2538 menu_list = g_hash_table_lookup ( otr_win_menus, win );
2539
2540 g_signal_connect(G_OBJECT(tooltip_menu), "destroy",
2541 G_CALLBACK(otr_menu_destroy), win);
2542
2543 menu_list = g_list_append(menu_list, tooltip_menu);
2544
2545 g_hash_table_replace ( otr_win_menus, win, menu_list );
2546 }
2547
otr_add_buddy_top_menus(PurpleConversation * conv)2548 static void otr_add_buddy_top_menus(PurpleConversation *conv)
2549 {
2550 PidginConversation *gtkconv = PIDGIN_CONVERSATION ( conv );
2551
2552 PurpleConversation * currentConv = NULL; /* Auxiliary variables re-used */
2553 ConnContext *currentContext = NULL; /* within loops. */
2554
2555 GList *full_buddy_list = NULL;
2556 GList *list_iter;
2557
2558 int pos = otr_get_menu_insert_pos(conv);
2559
2560
2561 GHashTable *conv_to_context_map = g_hash_table_new(g_direct_hash,
2562 g_direct_equal);
2563
2564 GHashTable * conv_or_ctx_map = purple_conversation_get_data(conv,
2565 "otr-convorctx");
2566
2567 full_buddy_list = otr_get_full_buddy_list(conv);
2568
2569 list_iter = full_buddy_list;
2570
2571 /* First determine how many contexts are associated with each conv */
2572 for (list_iter = g_list_last ( full_buddy_list ); list_iter != NULL;
2573 list_iter = list_iter->prev) {
2574 PurpleAccount *account;
2575 char *username;
2576 const char *accountname, *proto;
2577 GList * contexts = NULL;
2578
2579 currentConv = list_iter->data;
2580
2581 if (currentConv == NULL) {
2582 continue;
2583 }
2584
2585 if (purple_conversation_get_type(currentConv) != PURPLE_CONV_TYPE_IM) {
2586 continue;
2587 }
2588
2589 account = purple_conversation_get_account(currentConv);
2590 accountname = purple_account_get_username(account);
2591 proto = purple_account_get_protocol_id(account);
2592 username = g_strdup(purple_normalize(account,
2593 purple_conversation_get_name(currentConv)));
2594
2595 for (currentContext = otrg_plugin_userstate->context_root;
2596 currentContext != NULL;
2597 currentContext = currentContext->next) {
2598
2599 if (!strcmp(currentContext->accountname, accountname) &&
2600 !strcmp(currentContext->protocol, proto) &&
2601 !strcmp(currentContext->username, username)) {
2602 contexts = g_list_append(contexts, currentContext);
2603 }
2604 }
2605
2606 g_free(username);
2607
2608 g_hash_table_insert(conv_to_context_map,
2609 currentConv, (gpointer) contexts);
2610
2611 }
2612
2613 list_iter = full_buddy_list;
2614
2615 for (list_iter = g_list_last ( full_buddy_list ); list_iter != NULL;
2616 list_iter = list_iter->prev) {
2617 GList * contexts = NULL;
2618 GList * contexts_iter = NULL;
2619 gboolean active_conv = 0;
2620 ConvOrContext * convctx = NULL;
2621 ConnContext * m_context = NULL;
2622 PurpleAccount * account = NULL;
2623 char * username = NULL;
2624 const char * accountname = NULL;
2625 int num_contexts = 0;
2626 gboolean * is_multi_instance;
2627
2628 currentConv = list_iter->data;
2629
2630 if (currentConv == NULL) {
2631 continue;
2632 }
2633
2634 active_conv = (currentConv == gtkconv->active_conv);
2635
2636 contexts = (GList *) g_hash_table_lookup(conv_to_context_map,
2637 currentConv);
2638
2639 if (purple_conversation_get_type(currentConv) != PURPLE_CONV_TYPE_IM) {
2640 continue;
2641 }
2642
2643 num_contexts = g_list_length(contexts);
2644
2645 is_multi_instance = purple_conversation_get_data(currentConv,
2646 "otr-conv_multi_instances");
2647 if (is_multi_instance) {
2648 *is_multi_instance = FALSE;
2649 }
2650
2651 if (num_contexts > 1) {
2652 /* We will need the master context */
2653 currentContext = (ConnContext *) contexts->data;
2654
2655 m_context = currentContext->m_context;
2656 }
2657
2658 if (num_contexts <= 1) {
2659 /* Just add a menu for the possibly not yet created master inst */
2660 convctx = g_hash_table_lookup(conv_or_ctx_map, currentConv);
2661
2662 if (!convctx) {
2663 convctx = malloc(sizeof(ConvOrContext));
2664 g_hash_table_insert(conv_or_ctx_map, currentConv,
2665 (gpointer)convctx);
2666 convctx->convctx_type = convctx_conv;
2667 convctx->conv = currentConv;
2668 }
2669
2670 account = purple_conversation_get_account(currentConv);
2671 accountname = purple_account_get_username(account);
2672 username = g_strdup(
2673 purple_normalize(account,
2674 purple_conversation_get_name(currentConv)));
2675
2676 otr_add_buddy_top_menu(gtkconv, convctx, active_conv, username,
2677 accountname, &pos);
2678 g_free(username);
2679
2680 } else if (num_contexts == 2 &&
2681 m_context->msgstate == OTRL_MSGSTATE_PLAINTEXT) {
2682 /* Just add a menu for the non_master instance */
2683 contexts_iter = contexts;
2684 currentContext = contexts_iter->data;
2685
2686 while (currentContext->m_context == currentContext &&
2687 contexts_iter->next != NULL) {
2688 contexts_iter = contexts_iter->next;
2689 currentContext = contexts_iter->data;
2690 }
2691
2692 convctx = g_hash_table_lookup(conv_or_ctx_map, currentContext);
2693
2694 if (!convctx) {
2695 convctx = malloc(sizeof(ConvOrContext));
2696 g_hash_table_insert(conv_or_ctx_map, currentContext,
2697 (gpointer)convctx);
2698 convctx->convctx_type = convctx_ctx;
2699 convctx->context = currentContext;
2700 }
2701
2702 otr_add_buddy_top_menu(gtkconv, convctx, active_conv,
2703 currentContext->username, currentContext->accountname,
2704 &pos);
2705
2706 } else {
2707 /* Multi-instances */
2708 *is_multi_instance = TRUE;
2709 otr_add_buddy_instances_top_menu(gtkconv, contexts, active_conv,
2710 currentContext->username, currentContext->accountname,
2711 &pos);
2712 }
2713
2714 if (contexts) {
2715 g_list_free(contexts);
2716 }
2717 }
2718
2719 g_hash_table_destroy (conv_to_context_map);
2720 g_list_free ( full_buddy_list );
2721
2722 }
2723
2724
otr_check_conv_status_change(PurpleConversation * conv)2725 static void otr_check_conv_status_change( PurpleConversation *conv)
2726 {
2727 PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv);
2728 TrustLevel current_level = TRUST_NOT_PRIVATE;
2729 ConnContext *context = otrg_plugin_conv_to_context(conv,
2730 OTRL_INSTAG_RECENT, 0);
2731
2732 TrustLevel *previous_level;
2733 char *buf;
2734 char *status = "";
2735
2736 if (context != NULL) {
2737 current_level = otrg_plugin_context_to_trust(context);
2738 }
2739
2740 previous_level = g_hash_table_lookup ( otr_win_status, gtkconv );
2741
2742 if (!previous_level ||
2743 (previous_level && *previous_level == current_level)) {
2744 return;
2745 }
2746
2747 buf = _("The privacy status of the current conversation is now: "
2748 "<a href=\"%s%s\">%s</a>");
2749
2750 switch(current_level) {
2751 case TRUST_NOT_PRIVATE:
2752 status = _("Not Private");
2753 break;
2754 case TRUST_UNVERIFIED:
2755 status = _("Unverified");
2756 break;
2757 case TRUST_PRIVATE:
2758 status = _("Private");
2759 break;
2760 case TRUST_FINISHED:
2761 status = _("Finished");
2762 break;
2763 }
2764
2765 buf = g_strdup_printf(buf, LEVELS_HELPURL, _("?lang=en"), status);
2766
2767 /* Write a new message indicating the level change. The timestamp image will
2768 * be appended as the message timestamp signal is caught, which will also
2769 * update the privacy level for this gtkconv */
2770 purple_conversation_write(conv, NULL, buf, PURPLE_MESSAGE_SYSTEM,
2771 time(NULL));
2772
2773 g_free(buf);
2774
2775 }
2776
2777 /* If the conversation switches on us */
conversation_switched(PurpleConversation * conv,void * data)2778 static void conversation_switched ( PurpleConversation *conv, void * data )
2779 {
2780 if ( conv == NULL ) return;
2781
2782 otrg_gtk_dialog_new_purple_conv(conv);
2783
2784 }
2785
2786 /* If the conversation gets destroyed on us, clean up the data we stored
2787 * pointing to it. */
conversation_destroyed(PurpleConversation * conv,void * data)2788 static void conversation_destroyed(PurpleConversation *conv, void *data)
2789 {
2790 GtkWidget *menu = (GtkWidget *) purple_conversation_get_data(conv,
2791 "otr-menu");
2792 PidginConversation *gtkconv;
2793 PidginWindow *win;
2794 GHashTable * conv_or_ctx_map;
2795 GHashTable * conv_to_idx_map;
2796 gint * max_instance_idx;
2797 gboolean * is_conv_multi_instance;
2798 gboolean * have_warned_instances;
2799 otrl_instag_t * last_received_instance;
2800
2801 if (menu) gtk_object_destroy(GTK_OBJECT(menu));
2802
2803 conv_or_ctx_map = purple_conversation_get_data(conv, "otr-convorctx");
2804 g_hash_table_destroy(conv_or_ctx_map);
2805
2806 conv_to_idx_map = purple_conversation_get_data(conv, "otr-conv_to_idx");
2807 g_hash_table_destroy(conv_to_idx_map);
2808
2809 max_instance_idx = purple_conversation_get_data(conv, "otr-max_idx");
2810 if (max_instance_idx) {
2811 g_free(max_instance_idx);
2812 }
2813
2814 is_conv_multi_instance = purple_conversation_get_data(conv,
2815 "otr-conv_multi_instances");
2816 if (is_conv_multi_instance) {
2817 g_free(is_conv_multi_instance);
2818 }
2819
2820 have_warned_instances = purple_conversation_get_data(conv,
2821 "otr-warned_instances");
2822 if (have_warned_instances) {
2823 g_free(have_warned_instances);
2824 }
2825
2826 last_received_instance = purple_conversation_get_data(conv,
2827 "otr-last_received_ctx");
2828 if (last_received_instance) {
2829 g_free(last_received_instance);
2830 }
2831
2832 g_hash_table_remove(conv->data, "otr-label");
2833 g_hash_table_remove(conv->data, "otr-button");
2834 g_hash_table_remove(conv->data, "otr-icon");
2835 g_hash_table_remove(conv->data, "otr-menu");
2836 g_hash_table_remove(conv->data, "otr-private");
2837 g_hash_table_remove(conv->data, "otr-authenticated");
2838 g_hash_table_remove(conv->data, "otr-finished");
2839 g_hash_table_remove(conv->data, "otr-select_best");
2840 g_hash_table_remove(conv->data, "otr-select_recent");
2841 g_hash_table_remove(conv->data, "otr-convorctx");
2842 g_hash_table_remove(conv->data, "otr-conv_to_idx");
2843 g_hash_table_remove(conv->data, "otr-max_idx");
2844 g_hash_table_remove(conv->data, "otr-conv_multi_instances");
2845 g_hash_table_remove(conv->data, "otr-warned_instances");
2846 g_hash_table_remove(conv->data, "otr-last_received_ctx");
2847
2848 otrg_gtk_dialog_free_smp_data(conv);
2849
2850 gtkconv = PIDGIN_CONVERSATION ( conv );
2851
2852 /* Only delete the OTR menus if we're the active conversation */
2853 if (gtkconv != pidgin_conv_window_get_active_gtkconv(gtkconv->win)) {
2854 return;
2855 }
2856
2857 win = pidgin_conv_get_window ( gtkconv );
2858
2859 otr_clear_win_menu_list(win);
2860
2861 g_hash_table_remove(otr_win_menus, win);
2862
2863 }
2864
2865 /* Set up the per-conversation information display */
otrg_gtk_dialog_new_purple_conv(PurpleConversation * conv)2866 static void otrg_gtk_dialog_new_purple_conv(PurpleConversation *conv)
2867 {
2868 PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv);
2869 ConnContext *context;
2870 ConvOrContext *convctx;
2871 GtkWidget *bbox;
2872 GtkWidget *button;
2873 GtkWidget *label;
2874 GtkWidget *bwbox;
2875 GtkWidget *icon;
2876 GtkWidget *menu;
2877
2878 PurpleAccount *account;
2879 const char *name;
2880 OtrgUiPrefs prefs;
2881
2882 GHashTable * conv_or_ctx_map;
2883 GHashTable * ctx_to_idx_map;
2884
2885 gint * max_instance_idx;
2886 gboolean * is_conv_multi_instance;
2887 gboolean * have_warned_instances;
2888 otrl_instag_t * last_received_instance;
2889
2890 /* Do nothing if this isn't an IM conversation */
2891 if (purple_conversation_get_type(conv) != PURPLE_CONV_TYPE_IM) return;
2892
2893 /* Get the prefs */
2894 account = purple_conversation_get_account(conv);
2895 name = purple_conversation_get_name(conv);
2896 otrg_ui_get_prefs(&prefs, account, name);
2897
2898 /* OTR is disabled for this buddy */
2899 if (prefs.policy == OTRL_POLICY_NEVER) {
2900 otr_destroy_top_menu_objects(conv);
2901 return;
2902 }
2903
2904 bbox = gtkconv->toolbar;
2905
2906 context = otrg_plugin_conv_to_selected_context(conv, 0);
2907
2908 /* See if we're already set up */
2909 button = purple_conversation_get_data(conv, "otr-button");
2910 if (button) {
2911 if (prefs.show_otr_button) {
2912 /* Check if we've been removed from the bbox; purple does this
2913 * when the user changes her prefs for the style of buttons to
2914 * display. */
2915 GList *children = gtk_container_get_children(GTK_CONTAINER(bbox));
2916 if (!g_list_find(children, button)) {
2917 gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
2918 }
2919 g_list_free(children);
2920 gtk_widget_show_all(button);
2921 } else {
2922 gtk_container_remove(GTK_CONTAINER(bbox), button);
2923 gtk_widget_hide_all(button);
2924 }
2925 dialog_update_label_conv(conv, otrg_plugin_context_to_trust(context));
2926 return;
2927 }
2928
2929 conv_or_ctx_map = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL,
2930 free);
2931 purple_conversation_set_data(conv, "otr-convorctx", conv_or_ctx_map);
2932
2933 ctx_to_idx_map = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL,
2934 g_free);
2935 purple_conversation_set_data(conv, "otr-conv_to_idx", ctx_to_idx_map);
2936
2937 max_instance_idx = g_malloc(sizeof(gint));
2938 *max_instance_idx = 0;
2939 purple_conversation_set_data(conv, "otr-max_idx",
2940 (gpointer)max_instance_idx);
2941
2942 is_conv_multi_instance = g_malloc(sizeof(gboolean));
2943 *is_conv_multi_instance = FALSE;
2944 purple_conversation_set_data(conv, "otr-conv_multi_instances",
2945 (gpointer)is_conv_multi_instance);
2946
2947 have_warned_instances = g_malloc(sizeof(gboolean));
2948 *have_warned_instances = FALSE;
2949 purple_conversation_set_data(conv, "otr-warned_instances",
2950 (gpointer)have_warned_instances);
2951
2952 last_received_instance = g_malloc(sizeof(otrl_instag_t));
2953 *last_received_instance = OTRL_INSTAG_BEST; /* cannot be received */
2954 purple_conversation_set_data(conv, "otr-last_received_ctx",
2955 (gpointer)last_received_instance);
2956
2957 /* Make the button */
2958 button = gtk_button_new();
2959
2960 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
2961 if (prefs.show_otr_button) {
2962 gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
2963 }
2964
2965 bwbox = gtk_hbox_new(FALSE, 0);
2966 gtk_container_add(GTK_CONTAINER(button), bwbox);
2967 icon = otr_icon(NULL, TRUST_NOT_PRIVATE, 1);
2968 gtk_box_pack_start(GTK_BOX(bwbox), icon, TRUE, FALSE, 0);
2969 label = gtk_label_new(NULL);
2970 gtk_box_pack_start(GTK_BOX(bwbox), label, FALSE, FALSE, 0);
2971
2972 if (prefs.show_otr_button) {
2973 gtk_widget_show_all(button);
2974 }
2975
2976 /* Make the context menu */
2977
2978 menu = gtk_menu_new();
2979 gtk_menu_set_title(GTK_MENU(menu), _("OTR Messaging"));
2980
2981 convctx = malloc(sizeof(ConvOrContext));
2982 convctx->convctx_type = convctx_conv;
2983 convctx->conv = conv;
2984 g_hash_table_replace ( conv_or_ctx_map, conv, convctx );
2985 build_otr_menu(convctx, menu, TRUST_NOT_PRIVATE);
2986 otr_build_status_submenu(pidgin_conv_get_window(gtkconv), convctx, menu,
2987 TRUST_NOT_PRIVATE);
2988
2989 purple_conversation_set_data(conv, "otr-label", label);
2990 purple_conversation_set_data(conv, "otr-button", button);
2991 purple_conversation_set_data(conv, "otr-icon", icon);
2992 purple_conversation_set_data(conv, "otr-menu", menu);
2993 g_signal_connect(G_OBJECT(button), "button-press-event",
2994 G_CALLBACK(button_pressed), conv);
2995
2996 dialog_update_label_conv(conv, otrg_plugin_context_to_trust(context));
2997 dialog_resensitize(conv);
2998
2999 /* Finally, add the state for the socialist millionaires dialogs */
3000 otrg_gtk_dialog_add_smp_data(conv);
3001 }
3002
3003 /* Set up the per-conversation information display */
otrg_gtk_dialog_new_conv(PurpleConversation * conv)3004 static void otrg_gtk_dialog_new_conv(PurpleConversation *conv)
3005 {
3006 PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv);
3007 conversation_switched (gtkconv->active_conv, NULL);
3008 }
3009
3010 /* Remove the per-conversation information display */
otrg_gtk_dialog_remove_conv(PurpleConversation * conv)3011 static void otrg_gtk_dialog_remove_conv(PurpleConversation *conv)
3012 {
3013 GtkWidget *button;
3014
3015 /* Do nothing if this isn't an IM conversation */
3016 if (purple_conversation_get_type(conv) != PURPLE_CONV_TYPE_IM) return;
3017
3018 button = purple_conversation_get_data(conv, "otr-button");
3019 if (button) gtk_object_destroy(GTK_OBJECT(button));
3020
3021 conversation_destroyed(conv, NULL);
3022 }
3023
3024 /* Set the OTR button to "sensitive" or "insensitive" as appropriate. */
dialog_resensitize(PurpleConversation * conv)3025 static void dialog_resensitize(PurpleConversation *conv)
3026 {
3027 PurpleAccount *account;
3028 PurpleConnection *connection;
3029 GtkWidget *button;
3030 const char *name;
3031 OtrgUiPrefs prefs;
3032
3033 /* Do nothing if this isn't an IM conversation */
3034 if (purple_conversation_get_type(conv) != PURPLE_CONV_TYPE_IM) return;
3035
3036 account = purple_conversation_get_account(conv);
3037 name = purple_conversation_get_name(conv);
3038 otrg_ui_get_prefs(&prefs, account, name);
3039
3040 if (prefs.policy == OTRL_POLICY_NEVER) {
3041 otrg_gtk_dialog_remove_conv(conv);
3042 } else {
3043 otrg_gtk_dialog_new_conv(conv);
3044 }
3045 button = purple_conversation_get_data(conv, "otr-button");
3046 if (!button) return;
3047 if (account) {
3048 connection = purple_account_get_connection(account);
3049 if (connection) {
3050 /* Set the button to "sensitive" */
3051 gtk_widget_set_sensitive(button, 1);
3052 return;
3053 }
3054 }
3055 /* Set the button to "insensitive" */
3056 gtk_widget_set_sensitive(button, 0);
3057 }
3058
3059 /* Set all OTR buttons to "sensitive" or "insensitive" as appropriate.
3060 * Call this when accounts are logged in or out. */
otrg_gtk_dialog_resensitize_all(void)3061 static void otrg_gtk_dialog_resensitize_all(void)
3062 {
3063 purple_conversation_foreach(dialog_resensitize);
3064 }
3065
foreach_free_lists(void * key,void * value,void * data)3066 static void foreach_free_lists(void * key, void * value, void* data)
3067 {
3068 PidginWindow *win = (PidginWindow *) key;
3069
3070 otr_clear_win_menu_list(win);
3071 }
3072
3073
3074
conversation_timestamp(PurpleConversation * conv,time_t mtime,gboolean show_date)3075 static char* conversation_timestamp(PurpleConversation *conv, time_t mtime,
3076 gboolean show_date) {
3077
3078 PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv);
3079 TrustLevel current_level = TRUST_NOT_PRIVATE;
3080 ConnContext *context = (ConnContext *) otrg_plugin_conv_to_context(conv,
3081 OTRL_INSTAG_RECENT, 0);
3082 TrustLevel *previous_level;
3083 int id;
3084
3085
3086 if (context != NULL) {
3087 current_level = otrg_plugin_context_to_trust(context);
3088 }
3089
3090 previous_level = g_hash_table_lookup ( otr_win_status, gtkconv );
3091
3092
3093 if (previous_level && *previous_level == current_level) {
3094 return NULL;
3095 }
3096
3097 /* We want to update this gtkconv's privacy level only if the new privacy
3098 * level we received corresponds to the active conversation. */
3099 if (conv == gtkconv->active_conv) {
3100 /* 'free' is handled by the hashtable */
3101 TrustLevel * current_level_ptr = malloc(sizeof(TrustLevel));
3102 *current_level_ptr = current_level;
3103 g_hash_table_replace ( otr_win_status, gtkconv, current_level_ptr );
3104 }
3105
3106 if (!previous_level) {
3107 return NULL;
3108 }
3109
3110 id = -1;
3111
3112 switch(current_level) {
3113 case TRUST_NOT_PRIVATE:
3114 id = img_id_not_private;
3115 break;
3116 case TRUST_UNVERIFIED:
3117 id = img_id_unverified;
3118 break;
3119 case TRUST_PRIVATE:
3120 id = img_id_private;
3121 break;
3122 case TRUST_FINISHED:
3123 id = img_id_finished;
3124 break;
3125 }
3126
3127
3128 if (id > 0 ) {
3129 char * msg = g_strdup_printf("<IMG ID=\"%d\"> ", id);
3130 gtk_imhtml_append_text_with_images((GtkIMHtml*)gtkconv->imhtml, msg, 0,
3131 NULL);
3132 g_free(msg);
3133 }
3134
3135
3136 return NULL;
3137 }
3138
3139 /* If the user has selected a meta instance, an incoming message may trigger an
3140 * instance change... we need to update the GUI appropriately */
check_incoming_instance_change(PurpleAccount * account,char * sender,char * message,PurpleConversation * conv,PurpleMessageFlags flags)3141 static gboolean check_incoming_instance_change(PurpleAccount *account,
3142 char *sender, char *message, PurpleConversation *conv,
3143 PurpleMessageFlags flags) {
3144 otrl_instag_t * last_received_instance;
3145 otrl_instag_t selected_instance;
3146 gboolean have_received = FALSE;
3147 ConnContext *received_context = NULL;
3148 ConnContext *current_out = NULL;
3149
3150 if (!conv || !conv->data) {
3151 return 0;
3152 }
3153
3154 selected_instance = otrg_plugin_conv_to_selected_instag(conv, 0);
3155 current_out = otrg_plugin_conv_to_selected_context(conv, 0);
3156
3157 last_received_instance = g_hash_table_lookup(conv->data,
3158 "otr-last_received_ctx");
3159
3160 if (!last_received_instance) {
3161 return 0; /* OTR disabled for this buddy */
3162 }
3163
3164 if (*last_received_instance == OTRL_INSTAG_MASTER ||
3165 *last_received_instance >= OTRL_MIN_VALID_INSTAG) {
3166 have_received = TRUE;
3167 }
3168
3169 received_context = (ConnContext *) otrg_plugin_conv_to_context(conv,
3170 (otrl_instag_t)OTRL_INSTAG_RECENT_RECEIVED, 0);
3171
3172 if (!received_context) {
3173 return 0;
3174 }
3175
3176 if (have_received &&
3177 *last_received_instance != received_context->their_instance &&
3178 selected_instance != OTRL_INSTAG_MASTER &&
3179 selected_instance < OTRL_MIN_VALID_INSTAG) {
3180 dialog_update_label_conv(conv,
3181 otrg_plugin_context_to_trust(current_out));
3182 }
3183
3184 *last_received_instance = received_context->their_instance;
3185
3186 return 0;
3187 }
3188
unref_img_by_id(int * id)3189 static void unref_img_by_id(int *id)
3190 {
3191 if (id && *id > 0) {
3192 purple_imgstore_unref_by_id(*id);
3193 *id = -1;
3194 }
3195 }
3196
dialog_quitting(void)3197 static void dialog_quitting(void)
3198 {
3199 /* We need to do this by catching the quitting signal, because
3200 * purple (mistakenly?) frees up all data structures, including
3201 * the imgstore, *before* calling the unload() method of the
3202 * plugins. */
3203 unref_img_by_id(&img_id_not_private);
3204 unref_img_by_id(&img_id_unverified);
3205 unref_img_by_id(&img_id_private);
3206 unref_img_by_id(&img_id_finished);
3207 }
3208
3209 /* Initialize the OTR dialog subsystem */
otrg_gtk_dialog_init(void)3210 static void otrg_gtk_dialog_init(void)
3211 {
3212 otr_win_menus = g_hash_table_new(g_direct_hash, g_direct_equal);
3213 otr_win_status = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL,
3214 free);
3215
3216
3217 img_id_not_private = purple_imgstore_add_with_id(
3218 g_memdup(not_private_png, sizeof(not_private_png)),
3219 sizeof(not_private_png), "");
3220
3221 img_id_unverified = purple_imgstore_add_with_id(
3222 g_memdup(unverified_png, sizeof(unverified_png)),
3223 sizeof(unverified_png), "");
3224
3225 img_id_private = purple_imgstore_add_with_id(
3226 g_memdup(private_png, sizeof(private_png)),
3227 sizeof(private_png), "");
3228
3229 img_id_finished = purple_imgstore_add_with_id(
3230 g_memdup(finished_png, sizeof(finished_png)),
3231 sizeof(finished_png), "");
3232
3233
3234 purple_signal_connect(pidgin_conversations_get_handle(),
3235 "conversation-switched", otrg_plugin_handle,
3236 PURPLE_CALLBACK(conversation_switched), NULL);
3237
3238 purple_signal_connect(purple_conversations_get_handle(),
3239 "deleting-conversation", otrg_plugin_handle,
3240 PURPLE_CALLBACK(conversation_destroyed), NULL);
3241
3242 purple_signal_connect(pidgin_conversations_get_handle(),
3243 "conversation-timestamp", otrg_plugin_handle,
3244 PURPLE_CALLBACK(conversation_timestamp), NULL);
3245
3246 purple_signal_connect(purple_conversations_get_handle(),
3247 "received-im-msg", otrg_plugin_handle,
3248 PURPLE_CALLBACK(check_incoming_instance_change), NULL);
3249
3250 purple_signal_connect(purple_get_core(),
3251 "quitting", otrg_plugin_handle,
3252 PURPLE_CALLBACK(dialog_quitting), NULL);
3253 }
3254
3255 /* Deinitialize the OTR dialog subsystem */
otrg_gtk_dialog_cleanup(void)3256 static void otrg_gtk_dialog_cleanup(void)
3257 {
3258 purple_signal_disconnect(purple_get_core(), "quitting",
3259 otrg_plugin_handle, PURPLE_CALLBACK(dialog_quitting));
3260
3261 purple_signal_disconnect(pidgin_conversations_get_handle(),
3262 "conversation-switched", otrg_plugin_handle,
3263 PURPLE_CALLBACK(conversation_switched));
3264
3265 purple_signal_disconnect(pidgin_conversations_get_handle(),
3266 "conversation-timestamp", otrg_plugin_handle,
3267 PURPLE_CALLBACK(conversation_timestamp));
3268
3269 purple_signal_disconnect(purple_conversations_get_handle(),
3270 "deleting-conversation", otrg_plugin_handle,
3271 PURPLE_CALLBACK(conversation_destroyed));
3272
3273 purple_signal_disconnect(purple_conversations_get_handle(),
3274 "received-im-msg", otrg_plugin_handle,
3275 PURPLE_CALLBACK(check_incoming_instance_change));
3276
3277 /* If we're quitting, the imgstore will already have been destroyed
3278 * by purple, but we should have already called dialog_quitting(),
3279 * so the img_id_* should be -1, and all should be OK. */
3280 unref_img_by_id(&img_id_not_private);
3281 unref_img_by_id(&img_id_unverified);
3282 unref_img_by_id(&img_id_private);
3283 unref_img_by_id(&img_id_finished);
3284
3285 g_hash_table_foreach(otr_win_menus, foreach_free_lists, NULL);
3286
3287 g_hash_table_destroy(otr_win_menus);
3288
3289 g_hash_table_destroy(otr_win_status);
3290 }
3291
3292 static const OtrgDialogUiOps gtk_dialog_ui_ops = {
3293 otrg_gtk_dialog_init,
3294 otrg_gtk_dialog_cleanup,
3295 otrg_gtk_dialog_notify_message,
3296 otrg_gtk_dialog_display_otr_message,
3297 otrg_gtk_dialog_private_key_wait_start,
3298 otrg_gtk_dialog_private_key_wait_done,
3299 otrg_gtk_dialog_unknown_fingerprint,
3300 otrg_gtk_dialog_verify_fingerprint,
3301 otrg_gtk_dialog_socialist_millionaires,
3302 otrg_gtk_dialog_update_smp,
3303 otrg_gtk_dialog_connected,
3304 otrg_gtk_dialog_disconnected,
3305 otrg_gtk_dialog_stillconnected,
3306 otrg_gtk_dialog_finished,
3307 otrg_gtk_dialog_resensitize_all,
3308 otrg_gtk_dialog_new_conv,
3309 otrg_gtk_dialog_remove_conv
3310 };
3311
3312 /* Get the GTK dialog UI ops */
otrg_gtk_dialog_get_ui_ops(void)3313 const OtrgDialogUiOps *otrg_gtk_dialog_get_ui_ops(void)
3314 {
3315 return >k_dialog_ui_ops;
3316 }
3317