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 &gtk_dialog_ui_ops;
3316 }
3317