1 /* Pidgin encryption plugin */
2 /* Copyright (C) 2001-2007 William Tompkins */
3
4 /* This plugin is free software, distributed under the GNU General Public */
5 /* License. */
6 /* Please see the file "COPYING" distributed with this source code */
7 /* for more details */
8 /* */
9 /* */
10 /* This software is distributed in the hope that it will be useful, */
11 /* but WITHOUT ANY WARRANTY; without even the implied warranty of */
12 /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU */
13 /* General Public License for more details. */
14
15 /* To compile and use: */
16 /* See INSTALL file. */
17
18 #define PURPLE_PLUGINS
19
20 #include "internal.h"
21
22 #include "pidgin-encryption-config.h"
23
24 #include <gdk/gdk.h>
25 #include <gtk/gtkplug.h>
26
27 #include <debug.h>
28 #include <core.h>
29 #include <gtkutils.h>
30 #include <gtkplugin.h>
31 #include <gtkconv.h>
32 #include <gtkdialogs.h>
33 #include <gtkprefs.h>
34 #include <blist.h>
35 #include <gtkblist.h>
36 #include <gtkimhtml.h>
pop_back_iteratorboost::fusion::pop_back_iterator37 #include <gtklog.h>
38 #include <signals.h>
39 #include <util.h>
40 #include <version.h>
41
42 #include "cryptproto.h"
43 #include "cryptutil.h"
44 #include "state.h"
45 #include "state_ui.h"
46 #include "keys.h"
47 #include "nonce.h"
48 #include "prefs.h"
49 #include "config_ui.h"
50 #include "pe_blist.h"
51
52 #include "encrypt.h"
53 #include "nls.h"
54
55 #include <time.h>
56 #include <sys/types.h>
57 #ifndef _WIN32
58 #include <sys/time.h>
59 #endif
60 #include <string.h>
61 #include <unistd.h>
62 #include <math.h>
63 #include <ctype.h>
64
65 #include <stdio.h>
66 #include <stdlib.h>
67 #include <sys/types.h>
68 #include <sys/stat.h>
69 #include <fcntl.h>
70 #include <errno.h>
71
72 #ifdef HAVE_ALLOCA_H
73 #include <alloca.h>
74 #endif
75
76 #ifdef _WIN32
77 #include "win32dep.h"
78 #endif
79
80
81 /* from Purple's internal.h, but it isn't critical that it is in sync: */
82 #define PE_BUF_LONG 4096
83
84 G_MODULE_IMPORT GSList *purple_accounts;
85 G_MODULE_IMPORT guint im_options;
86
87
88 #define ENCRYPT_PLUGIN_ID "gtk-obobo-pidgin-encryption"
89
90 /* Types */
91 struct msg_node {
92 char who[64];
93 time_t time;
94 PurpleConnection* gc;
95 struct msg_node* next;
96 char msg[1];
97 };
98 typedef struct msg_node msg_node;
callboost::fusion::pop_back_iterator::prior_impl99
100
101 static PurplePlugin *PE_plugin_handle;
102 static guint PE_pref_callback_id;
103
104 /* Outgoing message queue (waiting on a public key to encrypt) */
105 static msg_node* first_out_msg = 0;
106 static msg_node* last_out_msg = 0;
107
108 /* Incoming message queue (waiting on a public key to verify) */
109 static msg_node* first_inc_msg = 0;
110 static msg_node* last_inc_msg = 0;
111
112 static int PE_get_msg_size_limit(PurpleAccount*);
113 static void PE_send_key(PurpleAccount *, const char *name, int, char*);
114 static crypt_key * PE_get_key(PurpleConnection *, const char *name);
115 static int decrypt_msg(char **decrypted, char *msg,
116 const char *name, crypt_key *, crypt_key *);
117 static void PE_store_msg(const char *name, PurpleConnection*, char *,
118 msg_node**, msg_node**);
119 static void got_encrypted_msg(PurpleConnection *, const char *name, char **);
120
callboost::fusion::pop_back_iterator::prior_impl121 static void reap_all_sent_messages(PurpleConversation*);
122 static void reap_old_sent_messages(PurpleConversation*);
123
124 /* Function pointers exported to Purple */
125 static gboolean PE_got_msg_cb(PurpleAccount *, char **, char **, PurpleConversation *conv, int* flags);
126 static void PE_send_msg_cb(PurpleAccount *, char *, char **, void *);
127 static void PE_new_conv_cb(PurpleConversation *, void *);
128 static void PE_del_conv_cb(PurpleConversation *, void *);
129 static void PE_updated_conv_cb(PurpleConversation *, void *);
130
131 /* legacy... we try to use HTML in some of our headers- format is protocol dependent */
132 static GHashTable *header_table, *footer_table, *notify_table;
133 static gchar* header_default; /* the non-HTML default header */
134
135 static gchar* header_broken; /* if a server is stripping HTML and we're using it */
136 /* in our header, we'll see this */
137 static GHashTable *broken_users; /* keeps track of who is seeing broken HTML */
138
139 static char * unrequited_capable_who = 0; /* if we learn that someone is capable, but don't have */
140 /* a conv for them yet, we set this to be them */
141
142 /* A field for the LibPurple conversation "data" hashmap to indicate that we want to use non-html */
143 #define BROKEN_HTML "Encrypt-HTMLBroken"
144
145 static void strip_crypto_smiley(char* s) {
146 char * pos;
147
148 while ( (pos = strstr(s, CRYPTO_SMILEY)) != 0 ) {
149 memmove(pos, pos + CRYPTO_SMILEY_LEN, strlen(pos + CRYPTO_SMILEY_LEN)+1);
150 }
151 }
152
153 /* Send key to other side. If msg_id is non-null, we include a request to re-send */
154 /* a certain message, as well. */
155
156 static void PE_send_key(PurpleAccount *acct, const char *name, int asError, gchar *msg_id) {
pop_back(Sequence const & seq)157 /* load key somehow */
158 char *msg;
159 GString *key_str;
160 crypt_key *pub_key;
161 PurpleConversation *conv;
162 int conv_breaks_html = 0;
163
164 int header_size, footer_size;
165 const gchar* header = g_hash_table_lookup(header_table, purple_account_get_protocol_id(acct));
166 const gchar* footer = g_hash_table_lookup(footer_table, purple_account_get_protocol_id(acct));
167
168 purple_debug(PURPLE_DEBUG_INFO, "pidgin-encryption", "send_key: %s\n", acct->username);
169
170 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, name, acct);
171 purple_debug(PURPLE_DEBUG_INFO, "pidgin-encryption", "send_key: %s, %p, %s\n", name, conv, acct->username);
172
173 if (g_hash_table_lookup(broken_users, name)) {
174 conv_breaks_html = 1;
175 }
176
177 if (!header || conv_breaks_html) header = header_default;
178 if (!footer || conv_breaks_html) footer = "";
179
180 header_size = strlen(header);
181 footer_size = strlen(footer);
182
183 pub_key = PE_find_own_key_by_name(&PE_my_pub_ring, acct->username, acct, conv);
184 purple_debug(PURPLE_DEBUG_INFO, "pidgin-encryption", "send_key2: %s\n", acct->username);
185 if (!pub_key) return;
186
187 key_str = PE_make_sendable_key(pub_key, name);
188 purple_debug(PURPLE_DEBUG_INFO, "pidgin-encryption", "send_key3: %s\n", acct->username);
189
190 msg = alloca(header_size + footer_size + key_str->len + 100);
191 if (msg == 0) return;
192 if (asError) {
193 if (msg_id) {
194 sprintf(msg, "%s: ErrKey: Prot %s: Len %d:%sResend:%s:%s", header,
195 pub_key->proto->name, (int)key_str->len, key_str->str, msg_id, footer);
196 } else {
197 sprintf(msg, "%s: ErrKey: Prot %s: Len %d:%s%s", header,
198 pub_key->proto->name, (int)key_str->len, key_str->str, footer);
199 }
200 } else {
201 sprintf(msg, "%s: Key: Prot %s: Len %d:%s%s", header,
202 pub_key->proto->name, (int)key_str->len, key_str->str, footer);
203 }
204
205 if (strlen(msg) > PE_get_msg_size_limit(acct)) {
206 purple_debug(PURPLE_DEBUG_MISC, "pidgin-encryption", "Key too big to send in message (%u > %d)\n",
207 (unsigned)strlen(msg), PE_get_msg_size_limit(acct));
208 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, name, acct);
209 if (conv == NULL) {
210 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, name);
211 }
212 purple_conversation_write(conv, 0,
213 _("This account key is too large for this protocol. "
214 "Unable to send."),
215 PURPLE_MESSAGE_SYSTEM, time((time_t)NULL));
216 return;
217 }
218
219 serv_send_im(acct->gc, name, msg, 0);
220 g_string_free(key_str, TRUE);
221 }
222
223 static crypt_key *PE_get_key(PurpleConnection *gc, const char *name) {
224 crypt_key *bkey;
225
226 purple_debug(PURPLE_DEBUG_INFO, "pidgin-encryption", "get_key: %s\n", name);
227 bkey = PE_find_key_by_name(PE_buddy_ring, name, gc->account);
228 purple_debug(PURPLE_DEBUG_INFO, "pidgin-encryption", "got key: %p\n", bkey);
229
230 if( bkey == 0 ) {
231 char* tmpmsg;
232
233 int header_size, footer_size;
234 const gchar* header = g_hash_table_lookup(header_table, purple_account_get_protocol_id(gc->account));
235 const gchar* footer = g_hash_table_lookup(footer_table, purple_account_get_protocol_id(gc->account));
236 int conv_breaks_html = 0;
237
238 if (g_hash_table_lookup(broken_users, name)) {
239 conv_breaks_html = 1;
240 }
241 if (g_hash_table_lookup(broken_users, name)) {
242 conv_breaks_html = 1;
243 }
244
245
246 if (!header || conv_breaks_html) header = header_default;
247 if (!footer || conv_breaks_html) footer = "";
248
249 header_size = strlen(header);
250 footer_size = strlen(footer);
251
252 tmpmsg = alloca(header_size + footer_size +
253 sizeof (": Send Key")); // sizeof() gets the trailing null too
254
255 sprintf(tmpmsg, "%s%s%s", header, ": Send Key", footer);
256
257 purple_debug(PURPLE_DEBUG_MISC, "pidgin-encryption", "Sending: %s\n", tmpmsg);
258 serv_send_im(gc, name, tmpmsg, 0);
259 return 0;
260 }
261
262 return bkey;
263 }
264
265
266 static int decrypt_msg(char **decrypted, char *msg, const char *name,
267 crypt_key *priv_key, crypt_key *pub_key) {
268 int realstart = 0;
269 unsigned int length;
270 int len;
271 char* decrypted_no_header = 0;
272 purple_debug(PURPLE_DEBUG_INFO, "pidgin-encryption", "decrypt_msg\n");
273
274 *decrypted = 0;
275 if ( (sscanf(msg, ": Len %u:%n", &length, &realstart) < 1) || (realstart == 0)) {
276 purple_debug(PURPLE_DEBUG_ERROR, "pidgin-encryption", "Garbled length in decrypt\n");
277 return -1;
278 }
279
280 msg += realstart;
281
282 if (strlen(msg) < length) {
283 purple_debug(PURPLE_DEBUG_ERROR, "pidgin-encryption", "Length doesn't match in decrypt\n");
284 return -1;
285 }
286 msg[length] = 0;
287
288 len = PE_decrypt_signed(&decrypted_no_header, msg, priv_key, pub_key, name);
289
290 if (len <= 0 || decrypted_no_header == 0) {
291 return -1;
292 }
293 strip_crypto_smiley(decrypted_no_header);
294
295 if (purple_prefs_get_bool("/plugins/gtk/encrypt/show_inline_icons")) {
296 if (decrypted_no_header[0] == '/') {
297 gchar** slashsplit = g_strsplit(decrypted_no_header, " ", 2);
298 *decrypted = g_strconcat(slashsplit[0], " ", CRYPTO_SMILEY, " ", slashsplit[1], NULL);
299 g_strfreev(slashsplit);
300 g_free(decrypted_no_header);
301 } else {
302 *decrypted = g_strconcat(CRYPTO_SMILEY, " ", decrypted_no_header, NULL);
303 g_free(decrypted_no_header);
304 }
305
306 return len + CRYPTO_SMILEY_LEN + 1; /* plus 1 from space after smiley */
307
308 } else {
309 /* not showing inline icons */
310 *decrypted = decrypted_no_header;
311 return len;
312 }
313 }
314
315
316 static void PE_store_msg(const char *who, PurpleConnection *gc, char *msg, msg_node** first_node,
317 msg_node** last_node) {
318 msg_node* newnode;
319
320
321 newnode = g_malloc(sizeof(msg_node) + strlen(msg));
322
323 purple_debug(PURPLE_DEBUG_INFO, "pidgin-encryption", "store_msg: %p : %s\n", newnode, who);
324
325 strncpy(newnode->who, purple_normalize(gc->account, who), sizeof(newnode->who));
326 newnode->who[sizeof(newnode->who)-1] = 0;
327
328 newnode->gc = gc;
329 newnode->time = time((time_t)NULL);
330 strcpy(newnode->msg, msg);
331 newnode->next = 0;
332
333
334 if (*first_node == 0) {
335 *last_node = newnode;
336 *first_node = newnode;
337 } else {
338 (*last_node)->next = newnode;
339 *last_node = newnode;
340 }
341
342 for (newnode = *first_node; newnode != *last_node; newnode = newnode->next) {
343 purple_debug(PURPLE_DEBUG_INFO, "pidgin-encryption", " In store stack: %p, %s\n",
344 newnode, newnode->who);
345 }
346 purple_debug(PURPLE_DEBUG_INFO, "pidgin-encryption", " In store stack: %p, %s\n",
347 *last_node, (*last_node)->who);
348 }
349
350 void PE_send_stored_msgs(PurpleAccount* acct, const char* who) {
351 msg_node* node = first_out_msg;
352 msg_node* prev = 0;
353 char *tmp_msg;
354
355 purple_debug(PURPLE_DEBUG_INFO, "pidgin-encryption", "send_stored_msgs\n");
356
357 while (node != 0) {
358 purple_debug(PURPLE_DEBUG_INFO, "pidgin-encryption",
359 "Looking for stored msg:%s:%s\n",node->who, who);
360 if ((strcmp(node->who, who) == 0) && (node->gc->account == acct)) {
361 tmp_msg = g_strdup(node->msg);
362 PE_send_msg_cb(node->gc->account, (char*)who, &tmp_msg, 0);
363 PE_clear_string(node->msg);
364 if (tmp_msg != 0) {
365 g_free(tmp_msg);
366 }
367 if (node == last_out_msg) {
368 last_out_msg = prev;
369 }
370 if (prev != 0) { /* a random one matched */
371 prev->next = node->next;
372 g_free(node);
373 node = prev->next;
374 } else { /* the first one matched */
375 first_out_msg = node->next;
376 g_free(node);
377 node = first_out_msg;
378 }
379 } else { /* didn't match */
380 prev = node;
381 node = node->next;
382 }
383 }
384 }
385
386 void PE_delete_stored_msgs(PurpleAccount* acct, const char* who) {
387 msg_node* node = first_out_msg;
388 msg_node* prev = 0;
389
390 purple_debug(PURPLE_DEBUG_INFO, "pidgin-encryption", "delete_stored_msgs\n");
391
392 while (node != 0) {
393 purple_debug(PURPLE_DEBUG_INFO, "pidgin-encryption",
394 "Looking for stored msg:%s:%s\n",node->who, who);
395 if ((strcmp(node->who, who) == 0) && (node->gc->account == acct)) {
396 PE_clear_string(node->msg);
397 if (node == last_out_msg) {
398 last_out_msg = prev;
399 }
400 if (prev != 0) { /* a random one matched */
401 prev->next = node->next;
402 g_free(node);
403 node = prev->next;
404 } else { /* the first one matched */
405 first_out_msg = node->next;
406 g_free(node);
407 node = first_out_msg;
408 }
409 } else { /* didn't match */
410 prev = node;
411 node = node->next;
412 }
413 }
414 }
415
416 void PE_show_stored_msgs(PurpleAccount*acct, const char* who) {
417 msg_node* node = first_inc_msg;
418 msg_node* prev = 0;
419 char *tmp_msg;
420
421 PurpleConversation *conv;
422
423 while (node != 0) {
424 purple_debug(PURPLE_DEBUG_INFO, "pidgin-encryption", "show_stored_msgs:%p:%s:%s:\n", node, node->who, who);
425 if (strcmp(node->who, who) == 0) {
426 tmp_msg = g_strdup(node->msg);
427 got_encrypted_msg(node->gc, who, &tmp_msg);
428 if (tmp_msg != 0) {
429 purple_debug(PURPLE_DEBUG_INFO, "pidgin-encryption", "showing msg:%s\n", tmp_msg);
430
431 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, who, acct);
432
433 // let gtkconv (and others) know that we're about to display a message, so
434 // this is their chance to thwart it, or change the window, or...
435 purple_signal_emit(purple_conversations_get_handle(), "received-im-msg", acct,
436 who, tmp_msg, conv, PURPLE_MESSAGE_RECV);
437
438 // the conv may have been updated with that signal, so fetch it again
439 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, who, acct);
440
441 if (!conv) {
442 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, node->gc->account, who);
443 }
444
445 purple_conv_im_write(PURPLE_CONV_IM(conv), NULL, tmp_msg,
446 PURPLE_MESSAGE_RECV, time((time_t)NULL));
447
448 g_free(tmp_msg);
449
450 /* we might have just created the conversation, and now we're displaying an */
451 /* encrypted message. So... make sure we've got all the trappings, and then */
452 /* set the various indicators */
453
454 PE_updated_conv_cb(conv, 0);
455 PE_set_capable(conv, TRUE);
456 if (purple_prefs_get_bool("/plugins/gtk/encrypt/encrypt_response")) {
457 PE_set_tx_encryption(conv, TRUE);
458 }
459 PE_set_rx_encryption(conv, TRUE);
460
461 }
462 if (node == last_inc_msg) {
463 last_inc_msg = prev;
464 }
465 if (prev != 0) { /* a random one matched */
466 prev->next = node->next;
467 g_free(node);
468 node = prev->next;
469 } else { /* the first one matched */
470 first_inc_msg = node->next;
471 g_free(node);
472 node = first_inc_msg;
473 }
474 } else { /* didn't match */
475 prev = node;
476 node = node->next;
477 }
478 }
479 }
480
481 static void reap_all_sent_messages(PurpleConversation* conv){
482
483 GQueue *sent_msg_queue = g_hash_table_lookup(conv->data, "sent messages");
484
485 PE_SentMessage *sent_msg_item;
486
487 /* purple_debug(PURPLE_DEBUG_MISC, "pidgin-encryption", "ZZZ Reaping all messages: %p\n", conv); */
488
489 while (!g_queue_is_empty(sent_msg_queue)) {
490 sent_msg_item = g_queue_pop_tail(sent_msg_queue);
491 /* purple_debug(PURPLE_DEBUG_INFO, "pidgin-encryption", "ZZZ Message: %s\n", sent_msg_item->id); */
492 g_free(sent_msg_item->id);
493 g_free(sent_msg_item->msg);
494 g_free(sent_msg_item);
495 }
496 }
497
498 static void reap_old_sent_messages(PurpleConversation* conv){
499 GQueue *sent_msg_queue = g_hash_table_lookup(conv->data, "sent messages");
500
501 PE_SentMessage *sent_msg_item;
502 time_t curtime = time(0);
503
504 /* purple_debug(PURPLE_DEBUG_MISC, "pidgin-encryption", "ZZZ Reaping old messages: %p\n", conv); */
505
506 while (!g_queue_is_empty(sent_msg_queue)) {
507 sent_msg_item = g_queue_peek_tail(sent_msg_queue);
508 /* purple_debug(PURPLE_DEBUG_INFO, "pidgin-encryption", "ZZZ Message: %s\n", sent_msg_item->id); */
509 if (curtime - sent_msg_item->time > 60) { /* message is over 1 minute old */
510 sent_msg_item = g_queue_pop_tail(sent_msg_queue);
511 /* purple_debug(PURPLE_DEBUG_INFO, "pidgin-encryption", "ZZZ Deleted\n"); */
512 g_free(sent_msg_item->id);
513 g_free(sent_msg_item->msg);
514 g_free(sent_msg_item);
515 } else {
516 /* These were pushed on in order, so if this one is not old, we're done */
517 break;
518 }
519 }
520 }
521
522 static gboolean PE_got_msg_cb(PurpleAccount *acct, char **who, char **message,
523 PurpleConversation *conv, int *flags) {
524 char *name;
525
526 gchar *headerpos; /* Header is allowed to be anywhere in message now */
527 gchar *notifypos = 0;
528 gchar *caps_header, *caps_message, /* temps for ascii_strup() versions of each */
529 *caps_notify; /* since Jabber mucks with case */
530
531 gchar *boldAsterixPos; /* position of <b>*</b>-substituted header */
532 gchar *debianHeaderPos; /* position of Debian-specific header */
533 gchar *unescaped_message; /* temps for html_unescaped */
534 /* since ICQ will now escape HTML */
535
536 int header_size, footer_size;
537 const gchar* header = g_hash_table_lookup(header_table, purple_account_get_protocol_id(acct));
538 const gchar* footer = g_hash_table_lookup(footer_table, purple_account_get_protocol_id(acct));
539 const gchar* notify = g_hash_table_lookup(notify_table, purple_account_get_protocol_id(acct));
540
541 if (!header) header = header_default;
542 if (!footer) footer = "";
543
544 header_size = strlen(header);
545 footer_size = strlen(footer);
546
547 /* Since we don't have a periodic callback, we do some housekeeping here */
548 purple_conversation_foreach(reap_old_sent_messages);
549
550 name = g_strdup(purple_normalize(acct, *who));
551
552 if (*message != NULL) {
553 /* More header madness: Debian patched the headers to start with
554 "--- Encrypted with the ...", to fix the issue that sometimes
555 "***" is being replaced with <b>*</b>. So... replace either that
556 we see, to canonicalize the message */
557
558 /* also make message all caps... */
559 caps_message = g_ascii_strup(*message, -1);
560 caps_header = g_ascii_strup(header, -1);
561
562 boldAsterixPos = strstr(caps_message, "<B>*</B> ENCRYPTED WITH THE GAIM-ENCRYPTION PLUGIN");
563 if (boldAsterixPos) {
564 memcpy(boldAsterixPos, " ***", 8);
565 }
566
567 debianHeaderPos = strstr(caps_message, "--- ENCRYPTED WITH THE GAIM-ENCRYPTION PLUGIN");
568
569 if (debianHeaderPos) {
570 memcpy(debianHeaderPos, "***", 3);
571 }
572
573 headerpos = strstr(caps_message, caps_header);
574 g_free(caps_header);
575
576 if (headerpos == 0 && notify) {
577 caps_notify = g_ascii_strup(notify, -1);
578 notifypos = strstr(caps_message, caps_notify);
579 g_free(caps_notify);
580 } else {
581 notifypos = 0;
582 }
583 if (headerpos != 0) {
584 /* adjust to where the header is in the _real_ message, if */
585 /* we found it in the caps_message */
586 headerpos += (*message) - caps_message;
587 }
588
589 if (notifypos != 0) {
590 /* ditto for the notification header */
591 notifypos += (*message) - caps_message;
592 }
593
594 g_free(caps_message);
595
596 if (headerpos == 0 && notifypos == 0) {
597 unescaped_message = purple_unescape_html(*message);
598 /* Check for ICQ-escaped header*/
599 headerpos = strstr(unescaped_message, header);
600 if (headerpos == 0 && notify) {
601 notifypos = strstr(unescaped_message, notify);
602 }
603 if (headerpos != 0 || notifypos != 0) {
604 /* ICQ PRPL escaped our HTML header, but we outsmarted it */
605 /* replace message with unescaped message. */
606 purple_debug(PURPLE_DEBUG_MISC, "pidgin-encryption",
607 "Escaped header: replacing %s with %s\n",
608 *message, unescaped_message);
609 g_free(*message);
610 *message = unescaped_message;
611 } else {
612 g_free(unescaped_message);
613 }
614 }
615
616 if (headerpos == 0 && notifypos == 0) {
617 /* check for a header that has had the HTML ripped out. */
618 if (strstr(*message, header_broken)) {
619 /* mark this name as having broken HTML, so we send appropriately */
620 g_hash_table_insert(broken_users, g_strdup(name), (gpointer)TRUE);
621 /* send key to other side as an error condition */
622 PE_send_key(acct, name, 1, 0);
623 (*message)[0] = 0;
624 g_free(*message);
625 *message = NULL;
626 purple_debug(PURPLE_DEBUG_MISC, "pidgin-encryption",
627 "Broken HTML header found, asking for key\n");
628
629 // Bail early, since we short-circuited the logic below
630 g_free(name);
631 return FALSE;
632 }
633 }
634
635 if (headerpos == 0 && header != header_default){
636 /* look for a default header, in case other side has decided that html is broken */
637 headerpos = strstr(*message, header_default);
638 if (headerpos) {
639 purple_debug(PURPLE_DEBUG_MISC, "pidgin-encryption",
640 "Found default header when expecting proto-specific one\n");
641 header_size = strlen(header_default);
642 footer_size = 0;
643
644 /* mark this conv as having broken HTML, so we send appropriately */
645 g_hash_table_insert(broken_users, g_strdup(name), (gpointer)TRUE);
646 }
647 }
648
649 /* Whew. Enough of this header-finding. */
650
651 if (headerpos != 0) {
652 PE_set_capable(conv, TRUE);
653 if (purple_prefs_get_bool("/plugins/gtk/encrypt/encrypt_response")) {
654 PE_set_tx_encryption(conv, TRUE);
655 }
656 if (strncmp(headerpos + header_size, ": Send Key",
657 sizeof(": Send Key")-1) == 0) {
658 PE_send_key(acct, name, 0, 0);
659 (*message)[0] = 0;
660 g_free(*message);
661 *message = NULL;
662 purple_debug(PURPLE_DEBUG_MISC, "pidgin-encryption", "Sent key per request\n");
663 } else if (strncmp(headerpos + header_size, ": Key",
664 sizeof(": Key") - 1) == 0) {
665 purple_debug(PURPLE_DEBUG_MISC, "pidgin-encryption", "Got key\n");
666 PE_received_key(headerpos + header_size + sizeof(": Key") - 1, name, acct,
667 conv, message);
668 } else if (strncmp(headerpos + header_size, ": ErrKey",
669 sizeof(": ErrKey") - 1) == 0) {
670 purple_debug(PURPLE_DEBUG_MISC, "pidgin-encryption", "Got key in response to error\n");
671 purple_conversation_write(conv, 0,
672 _("Last outgoing message not received properly- resetting"),
673 PURPLE_MESSAGE_SYSTEM, time((time_t)NULL));
674
675 PE_received_key(headerpos + header_size + sizeof(": ErrKey") - 1, name, acct,
676 conv, message);
677 } else if (strncmp(headerpos + header_size, ": Msg",
678 sizeof(": Msg") - 1) == 0){
679 purple_debug(PURPLE_DEBUG_MISC, "pidgin-encryption",
680 "Got encrypted message: %u\n", (unsigned)strlen(*message));
681 purple_debug(PURPLE_DEBUG_INFO, "pidgin-encryption",
682 "Message is:%s:\n", *message);
683 memmove(*message, headerpos + header_size + sizeof(": Msg") - 1,
684 strlen(headerpos + header_size + sizeof(": Msg") -1)+1);
685 got_encrypted_msg(acct->gc, name, message);
686 PE_set_rx_encryption(conv, TRUE);
687 } else {
688 purple_debug(PURPLE_DEBUG_ERROR, "pidgin-encryption",
689 "Invalid Pidgin-Encryption packet type\n");
690 }
691 } else if (notifypos != 0) {
692 PE_set_rx_encryption(conv, FALSE);
693 if (conv) {
694 PE_set_capable(conv, TRUE);
695 if (purple_prefs_get_bool("/plugins/gtk/encrypt/encrypt_if_notified")) {
696 PE_set_tx_encryption(conv, TRUE);
697 }
698 } else {
699 /* remember who this was. If the next new conversation event is for */
700 /* the same guy, we'll set as capable then */
701 if (unrequited_capable_who) {
702 g_free(unrequited_capable_who);
703 }
704 unrequited_capable_who = g_strdup(*who);
705 }
706 /* remove the notification HTML so it doesn't pollute the logs */
707 memmove(notifypos, notifypos+strlen(notify),
708 strlen(notifypos+strlen(notify))+1); /* +1 to include null */
709 strip_crypto_smiley(*message);
710 } else { /* No encrypt-o-header */
711 PE_set_rx_encryption(conv, FALSE);
712 purple_debug(PURPLE_DEBUG_MISC, "pidgin-encryption", "No header: %s\n", *message);
713 purple_debug(PURPLE_DEBUG_MISC, "pidgin-encryption",
714 "Proto '%s', header should be: %s\n", purple_account_get_protocol_id(acct), header);
715 strip_crypto_smiley(*message);
716 }
717 }
718
719 g_free(name);
720
721 if (*message) {
722 return FALSE;
723 }
724 else {
725 return TRUE;
726 }
727 }
728
729 static void got_encrypted_msg(PurpleConnection *gc, const char* name, char **message){
730 unsigned char send_key_sum[KEY_DIGEST_LENGTH], recv_key_sum[KEY_DIGEST_LENGTH];
731 char *tmp_msg=0;
732 crypt_key *priv_key, *pub_key;
733 int msg_pos = 0;
734 PurpleConversation* conv;
735
736 purple_debug(PURPLE_DEBUG_MISC, "pidgin-encryption", "got_encrypted_msg\n");
737
738 if ( (sscanf(*message, ": S%10c: R%10c%n", send_key_sum, recv_key_sum, &msg_pos) < 2) ||
739 (msg_pos == 0) ) {
740 purple_debug(PURPLE_DEBUG_WARNING, "pidgin-encryption", "Garbled msg header\n");
741 return;
742 }
743
744 priv_key = PE_find_key_by_name(PE_my_priv_ring, gc->account->username, gc->account);
745 pub_key = PE_get_key(gc, name);
746
747 if (strncmp((char*)priv_key->digest, (char*)recv_key_sum, KEY_DIGEST_LENGTH) != 0) {
748 /* Someone sent us a message, but didn't use our correct public key */
749 PE_send_key(gc->account, name, 1, 0);
750 purple_debug(PURPLE_DEBUG_WARNING, "pidgin-encryption",
751 "Digests aren't same: {%*s} and {%*s}\n",
752 KEY_DIGEST_LENGTH, priv_key->digest,
753 KEY_DIGEST_LENGTH, recv_key_sum);
754 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, name, gc->account);
755 if (conv != 0) {
756 purple_conversation_write(conv, 0,
757 _("Received message encrypted with wrong key"),
758 PURPLE_MESSAGE_SYSTEM, time((time_t)NULL));
759
760 } else {
761 purple_debug(PURPLE_DEBUG_WARNING, "pidgin-encryption",
762 "Received msg with wrong key, "
763 "but can't write err msg to conv: %s\n", name);
764 }
765 g_free(*message);
766 *message = NULL;
767 return;
768 }
769
770 if (pub_key && (strncmp((char*)pub_key->digest, (char*)send_key_sum, KEY_DIGEST_LENGTH) != 0)) {
771 /* We have a key for this guy, but the digest didn't match. Store the message */
772 /* and ask for a new key */
773 PE_del_key_from_ring(PE_buddy_ring, name, gc->account);
774 pub_key = PE_get_key(gc, name); /* will be 0 now */
775 }
776
777 if (pub_key == 0) {
778 purple_debug(PURPLE_DEBUG_INFO, "pidgin-encryption", "g_e_m: Storing message on Show stack\n");
779 PE_store_msg(name, gc, *message, &first_inc_msg, &last_inc_msg);
780 g_free(*message);
781 *message = NULL;
782 return;
783 }
784
785 memmove(*message, *message + msg_pos, strlen(*message + msg_pos) + 1);
786
787 purple_debug(PURPLE_DEBUG_INFO, "pidgin-encryption", "attempting decrypt on '%s'\n", *message);
788
789 if (decrypt_msg(&tmp_msg, *message, name, priv_key, pub_key) < 0) {
790 purple_debug(PURPLE_DEBUG_ERROR, "pidgin-encryption", "Error in decrypt\n");
791 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, name, gc->account);
792 if (conv != 0) {
793 purple_conversation_write(conv, 0,
794 _("Error in decryption- asking for resend..."),
795 PURPLE_MESSAGE_SYSTEM, time((time_t)NULL));
796
797 } else {
798 purple_debug(PURPLE_DEBUG_WARNING, "pidgin-encryption",
799 "Asking for resend, but can't write err msg to conv: %s\n", name);
800 }
801 PE_send_key(gc->account, name, 1, tmp_msg);
802 g_free(*message);
803 if (tmp_msg) g_free(tmp_msg);
804 *message = NULL;
805 return;
806 }
807
808 /* Successful Decryption */
809
810 /* Note- we're feeding purple an arbitrarily formed message, which could
811 potentially have lots of nasty control characters and stuff. But, that
812 has been tested, and at present, at least, Purple won't barf on any
813 characters that we give it.
814
815 As an aside- Purple does now use g_convert() to convert to UTF-8 from
816 other character streams. If we wanted to be all i18n, we could
817 do the same, and even include the encoding type with the message.
818 We're not all that, at least not yet.
819 */
820
821 /* Why the extra space (and the extra buffered copy)? Well, the *
822 * purple server.c code does this, and having the extra space seems *
823 * to prevent at least one possible type of crash. Pretty scary. */
824
825 g_free(*message);
826 *message = g_malloc(MAX(strlen(tmp_msg) + 1, PE_BUF_LONG));
827 strcpy(*message, tmp_msg);
828 g_free(tmp_msg);
829
830 purple_debug(PURPLE_DEBUG_INFO, "pidgin-encryption", "Msg rcv:'%s'\n", *message);
831 }
832
833 /* Get account-specific message size limit*/
834
835 static int PE_get_msg_size_limit(PurpleAccount *acct) {
836 const char* protocol_id = purple_account_get_protocol_id(acct);
837
838 if (strcmp(protocol_id, "prpl-yahoo") == 0) {
839 return 945;
840 } else if (strcmp(protocol_id, "prpl-msn") == 0) {
841 return 1500; /* This may be too small... somewhere in the 1500-1600 (+ html on front/back) */
842 } else {
843 /* Well, ok, this isn't too exciting. Someday we can actually check */
844 /* to see what the real limits are. For now, 2500 works for everyone */
845 /* but Yahoo. */
846 return 2500;
847 }
848 }
849
850 static void PE_send_msg_cb(PurpleAccount *acct, char *who, char **message, void* data) {
851 char *out_msg, *crypt_msg = 0;
852 char *dupname = g_strdup(purple_normalize(acct, who));
853
854 int msgsize;
855 const char msg_format[] = "%s: Msg:S%.10s:R%.10s: Len %d:%s%s";
856 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, who, acct);
857 crypt_key *our_key, *his_key;
858 GSList *cur_msg;
859 GQueue *sent_msg_queue;
860 PE_SentMessage *sent_msg_item;
861
862 int unencrypted_size_limit, msg_size_limit;
863 int baggage_size;
864 char baggage[PE_BUF_LONG];
865
866 const gchar* header = g_hash_table_lookup(header_table, purple_account_get_protocol_id(acct));
867 const gchar* footer = g_hash_table_lookup(footer_table, purple_account_get_protocol_id(acct));
868 const gchar* notify = g_hash_table_lookup(notify_table, purple_account_get_protocol_id(acct));
869
870 int conv_breaks_html = 0;
871
872 if (g_hash_table_lookup(broken_users, dupname)) {
873 conv_breaks_html = 1;
874 }
875
876 if (!header || conv_breaks_html) header = header_default;
877 if (!footer || conv_breaks_html) footer = "";
878
879 msg_size_limit = PE_get_msg_size_limit(acct);
880
881 /* who: name that you are sending to */
882 /* gc->username: your name */
883
884 purple_debug(PURPLE_DEBUG_MISC, "pidgin-encryption", "send_msg: %s\n", who);
885
886 /* Since we don't have a periodic callback, we do some housekeeping here */
887 purple_conversation_foreach(reap_old_sent_messages);
888
889 /* Message might have been eaten by another plugin: */
890 if ((message == NULL) || (*message == NULL)) {
891 g_free(dupname);
892 return;
893 }
894
895 if (conv == NULL) {
896 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, who);
897 }
898
899 if (PE_get_tx_encryption(conv) == FALSE) {
900 if (notify && purple_prefs_get_bool("/plugins/gtk/encrypt/broadcast_notify")
901 && !PE_has_been_notified(conv)) {
902 PE_set_notified(conv, TRUE);
903 if (PE_msg_starts_with_link(*message) == TRUE) {
904 /* This is a hack- AOL's client has a bug in the html parsing
905 so that adjacent links (like <a href="a"></a><a href="b"></a>)
906 get concatenated (into <a href="ab"></a>). So we insert a
907 space if the first thing in the message is a link.
908 */
909 out_msg = g_strconcat(notify, " ", *message, NULL);
910 } else {
911 out_msg = g_strconcat(notify, *message, NULL);
912 }
913 g_free(*message);
914 *message = out_msg;
915 }
916 purple_debug(PURPLE_DEBUG_MISC, "pidgin-encryption", "Outgoing Msg::%s::\n", *message);
917 g_free(dupname);
918 return;
919 }
920
921 purple_debug(PURPLE_DEBUG_MISC, "pidgin-encryption", "send_msg B: %s, %p, %p, %p\n",
922 who, &PE_my_priv_ring, acct, conv);
923
924 our_key = PE_find_own_key_by_name(&PE_my_priv_ring, acct->username, acct, conv);
925
926 if (!our_key) {
927 *message[0] = 0; /* Nuke message so it doesn't look like it was sent. */
928 /* find_own_key (above) will have displayed error messages */
929 purple_debug(PURPLE_DEBUG_MISC, "pidgin-encryption", "leaving\n");
930
931 g_free(dupname);
932 return;
933 }
934
935 his_key = PE_get_key(acct->gc, dupname);
936
937 if (his_key == 0) { /* Don't have key for this guy yet */
938 /* PE_get_key will have sent the key request, just let user know */
939
940 purple_debug(PURPLE_DEBUG_INFO, "pidgin-encryption", "requesting key\n");
941 purple_conversation_write(conv, 0, _("Requesting key..."),
942 PURPLE_MESSAGE_SYSTEM, time((time_t)NULL));
943
944 PE_store_msg(who, acct->gc, *message, &first_out_msg, &last_out_msg);
945
946 } else { /* We have a key. Encrypt and send. */
947 purple_debug(PURPLE_DEBUG_INFO, "pidgin-encryption", "has key (%s)\n", dupname);
948 baggage_size = snprintf(baggage, sizeof(baggage), msg_format, header, our_key->digest,
949 his_key->digest, 10000, "", footer);
950 baggage_size = MIN(baggage_size, sizeof(baggage) - 1);
951
952 /* Warning: message_split keeps static copies, so if our */
953 /* caller uses it, we're hosed. Looks like nobody else */
954 /* uses it now, though. */
955 unencrypted_size_limit =
956 PE_calc_unencrypted_size(our_key, his_key, msg_size_limit - baggage_size);
957
958 cur_msg = PE_message_split(*message, unencrypted_size_limit);
959 while (cur_msg) {
960 gchar* disp_msg;
961 if (purple_prefs_get_bool("/plugins/gtk/encrypt/show_inline_icons")) {
962 /* add our smiley to front of message */
963 if (((gchar*)cur_msg->data)[0] == '/') {
964 /* doh, starting with a /command, so put the smiley after the /command */
965 gchar** slashsplit = g_strsplit(cur_msg->data, " ", 2);
966 disp_msg = g_strconcat(slashsplit[0], " ", CRYPTO_SMILEY, " ", slashsplit[1], NULL);
967 g_strfreev(slashsplit);
968 } else {
969 disp_msg = g_strconcat(CRYPTO_SMILEY, " ", cur_msg->data, NULL);
970 }
971 } else {
972 /* no smiley at front of message */
973 disp_msg = g_strdup(cur_msg->data);
974 }
975
976 purple_debug(PURPLE_DEBUG_INFO, "pidgin-encryption", "im_write: %s\n", dupname);
977
978 purple_conv_im_write(PURPLE_CONV_IM(conv), NULL, disp_msg,
979 PURPLE_MESSAGE_SEND, time((time_t)NULL));
980 g_free(disp_msg);
981
982 /* Add message to stash of sent messages: in case a key or nonce is wrong, we */
983 /* can then re-send the message when asked. */
984 sent_msg_queue = g_hash_table_lookup(conv->data, "sent messages");
985 sent_msg_item = g_malloc(sizeof(PE_SentMessage));
986 sent_msg_item->time = time(0);
987 sent_msg_item->id = PE_make_key_id(his_key); /* current nonce value */
988 sent_msg_item->msg = g_strdup(cur_msg->data);
989
990 g_queue_push_head(sent_msg_queue, sent_msg_item);
991
992 purple_debug(PURPLE_DEBUG_INFO, "pidgin-encryption", "Enc for send: '%s'\n", (char*)cur_msg->data);
993
994 PE_encrypt_signed(&crypt_msg, cur_msg->data, our_key, his_key);
995 msgsize = strlen(crypt_msg);
996
997 out_msg = g_malloc(msgsize + baggage_size + 1);
998
999 sprintf(out_msg, msg_format, header,
1000 our_key->digest, his_key->digest, msgsize, crypt_msg,
1001 footer);
1002
1003 serv_send_im(acct->gc, who, out_msg, 0);
1004
1005 /* emit the "sent-im-msg" event, which will cause sounds to get played, etc*/
1006 purple_signal_emit(purple_conversations_get_handle(), "sent-im-msg",
1007 acct, purple_conversation_get_name(conv), out_msg);
1008
1009 purple_debug(PURPLE_DEBUG_INFO, "pidgin-encryption",
1010 "send_im: %s: %u\n", who, (unsigned)strlen(out_msg));
1011 purple_debug(PURPLE_DEBUG_INFO, "pidgin-encryption",
1012 "outgoing:%s:\n", out_msg);
1013 g_free(out_msg);
1014 g_free(crypt_msg);
1015 cur_msg = cur_msg->next;
1016 /* if (purple_prefs_get_bool("/pidgin/conversations/im/hide_on_send")) {
1017 purple_window_hide(purple_conversation_get_window(conv));
1018 } */
1019 }
1020 }
1021
1022 *message[0] = 0;
1023 g_free(dupname);
1024
1025 return;
1026 }
1027
1028
1029 void PE_resend_msg(PurpleAccount* acct, const char* name, gchar *msg_id) {
1030 char *out_msg, *crypt_msg = 0, *msg = 0;
1031 PurpleConversation* conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, name, acct);
1032 int msgsize;
1033 const char msg_format[] = "%s: Msg:S%.10s:R%.10s: Len %d:%s%s";
1034 crypt_key *our_key, *his_key;
1035
1036 GQueue *sent_msg_queue;
1037 PE_SentMessage *sent_msg_item;
1038
1039 int baggage_size;
1040 char baggage[PE_BUF_LONG];
1041 const gchar *header, *footer;
1042 int conv_breaks_html = 0;
1043
1044 if (msg_id == 0) {
1045 purple_debug(PURPLE_DEBUG_ERROR, "pidgin-encryption", "Bad call to resend_msg: %p %p\n", conv, msg_id);
1046 return;
1047 }
1048
1049 if (conv == 0) {
1050 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, name);
1051 }
1052
1053 header = g_hash_table_lookup(header_table, purple_account_get_protocol_id(acct));
1054 footer = g_hash_table_lookup(footer_table, purple_account_get_protocol_id(acct));
1055
1056 if (g_hash_table_lookup(broken_users, name)) {
1057 conv_breaks_html = 1;
1058 }
1059
1060 if (!header || conv_breaks_html) header = header_default;
1061 if (!footer || conv_breaks_html) footer = "";
1062
1063 /*Sometimes callers don't know whether there's a msg to send... */
1064 if (msg_id == 0 || conv == 0) return;
1065
1066 purple_debug(PURPLE_DEBUG_MISC, "pidgin-encryption",
1067 "resend_encrypted_msg: %s:%s\n", conv->name, msg_id);
1068
1069 our_key = PE_find_key_by_name(PE_my_priv_ring, conv->account->username, conv->account);
1070
1071 his_key = PE_find_key_by_name(PE_buddy_ring, name, conv->account);
1072
1073 if (his_key == 0) { /* Don't have key for this guy */
1074 purple_conversation_write(conv, 0,
1075 _("No key to resend message. Message lost."),
1076 PURPLE_MESSAGE_SYSTEM, time((time_t)NULL));
1077
1078 } else { /* We have a key. Encrypt and send. */
1079
1080 sent_msg_queue = g_hash_table_lookup(conv->data, "sent messages");
1081
1082 /* Root through the queue looking for the right message. Any that are older than this */
1083 /* one we will throw out, since they would have already been asked for. */
1084
1085 while (!g_queue_is_empty(sent_msg_queue)) {
1086 sent_msg_item = g_queue_pop_tail(sent_msg_queue);
1087 purple_debug(PURPLE_DEBUG_INFO, "pidgin-encryption", "Examining Message: %s\n",
1088 sent_msg_item->id);
1089
1090 if (strcmp(sent_msg_item->id, msg_id) == 0) { /* This is the one to resend */
1091 msg = sent_msg_item->msg;
1092 g_free(sent_msg_item->id);
1093 g_free(sent_msg_item);
1094 break;
1095 }
1096 /* Not the one to resend: pitch it */
1097 purple_debug(PURPLE_DEBUG_INFO, "pidgin-encryption", " Deleted\n");
1098 g_free(sent_msg_item->id);
1099 g_free(sent_msg_item->msg);
1100 g_free(sent_msg_item);
1101 }
1102
1103 if (msg) {
1104 baggage_size = snprintf(baggage, sizeof(baggage), msg_format, header, our_key->digest,
1105 his_key->digest, 10000, "", footer);
1106 baggage_size = MIN(baggage_size, sizeof(baggage) - 1);
1107
1108 PE_encrypt_signed(&crypt_msg, msg, our_key, his_key);
1109 msgsize = strlen(crypt_msg);
1110 out_msg = g_malloc(msgsize + baggage_size + 1);
1111
1112 sprintf(out_msg, msg_format, header,
1113 our_key->digest, his_key->digest, msgsize, crypt_msg,
1114 footer);
1115 purple_conversation_write(conv, 0,
1116 "Resending...",
1117 PURPLE_MESSAGE_SYSTEM, time((time_t)NULL));
1118 serv_send_im(conv->account->gc, name, out_msg, 0);
1119
1120 purple_debug(PURPLE_DEBUG_INFO, "pidgin-encryption",
1121 "resend_im: %s: %u\n", name, (unsigned)strlen(out_msg));
1122 purple_debug(PURPLE_DEBUG_INFO, "pidgin-encryption",
1123 "resend outgoing:%s:\n", out_msg);
1124 g_free(msg);
1125 g_free(out_msg);
1126 g_free(crypt_msg);
1127 } else {
1128 purple_conversation_write(conv, 0, _("Outgoing message lost."),
1129 PURPLE_MESSAGE_SYSTEM, time((time_t)NULL));
1130 }
1131 }
1132 }
1133
1134
1135
1136 static void PE_new_conv(PurpleConversation *conv) {
1137 purple_debug(PURPLE_DEBUG_MISC, "pidgin-encryption", "New conversation\n");
1138
1139 if ((conv != NULL) && (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM)) {
1140 g_hash_table_insert(conv->data, g_strdup("sent messages"), g_queue_new());
1141 g_hash_table_insert(conv->data, g_strdup("sent_capable"), FALSE);
1142 PE_add_smiley(conv);
1143 PE_sync_state(conv);
1144 if (unrequited_capable_who) {
1145 if (strcmp(unrequited_capable_who, purple_conversation_get_name(conv)) == 0) {
1146 PE_set_capable(conv, TRUE);
1147 if (purple_prefs_get_bool("/plugins/gtk/encrypt/encrypt_if_notified")) {
1148 PE_set_tx_encryption(conv, TRUE);
1149 }
1150 }
1151 g_free(unrequited_capable_who);
1152 unrequited_capable_who = 0;
1153 }
1154 } else {
1155 purple_debug(PURPLE_DEBUG_ERROR, "pidgin-encryption", "New conversation IS NULL\n");
1156 }
1157 }
1158
1159 static void PE_new_conv_cb(PurpleConversation *conv, void* data) {
1160 PE_new_conv(conv);
1161 }
1162
1163 static void PE_updated_conv_cb(PurpleConversation *conv, void* data) {
1164 PE_add_smiley(conv);
1165 PE_sync_state(conv);
1166 }
1167
1168 static void PE_del_conv_cb(PurpleConversation *conv, void* data)
1169 {
1170 GQueue *sent_msg_queue;
1171
1172 if ((conv != NULL) && (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM)) {
1173 purple_debug(PURPLE_DEBUG_MISC, "pidgin-encryption",
1174 "Got conversation delete event for %s\n", conv->name);
1175
1176 /* Remove cached copies of sent messages */
1177 reap_all_sent_messages(conv);
1178 sent_msg_queue = g_hash_table_lookup(conv->data, "sent messages");
1179 g_queue_free(sent_msg_queue);
1180 g_hash_table_remove(conv->data, "sent messages");
1181
1182 /* Remove to-be-sent-on-receipt-of-key messages: */
1183 PE_delete_stored_msgs(conv->account, purple_normalize(conv->account, conv->name));
1184
1185 PE_buddy_ring = PE_del_key_from_ring(PE_buddy_ring,
1186 purple_normalize(conv->account, conv->name), conv->account);
1187
1188 /* Would be good to add prefs for these, but for now, just reset: */
1189 PE_free_state(conv);
1190
1191 purple_debug(PURPLE_DEBUG_MISC, "pidgin-encryption",
1192 "Finished conversation delete event for %s\n", conv->name);
1193 /* button widgets (hopefully) destroyed on window close */
1194 /* hash table entries destroyed on hash table deletion, except */
1195 /* for any dynamically allocated values (keys are ok). */
1196 }
1197 }
1198
1199 static void PE_headers_init() {
1200 header_table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
1201 footer_table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
1202 notify_table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
1203
1204 g_hash_table_insert(header_table, g_strdup("prpl-toc"),
1205 g_strdup("*** Encrypted with the Gaim-Encryption plugin <A HREF=\""));
1206 g_hash_table_insert(footer_table, g_strdup("prpl-toc"),
1207 g_strdup("\"></A>"));
1208 g_hash_table_insert(notify_table, g_strdup("prpl-toc"),
1209 g_strdup("<A HREF=\"Gaim-Encryption Capable\"></A>"));
1210
1211 g_hash_table_insert(header_table, g_strdup("prpl-oscar"),
1212 g_strdup("*** Encrypted with the Gaim-Encryption plugin <A HREF=\""));
1213 g_hash_table_insert(footer_table, g_strdup("prpl-oscar"),
1214 g_strdup("\"></A>"));
1215 g_hash_table_insert(notify_table, g_strdup("prpl-oscar"),
1216 g_strdup("<A HREF=\"Gaim-Encryption Capable\"></A>"));
1217
1218 g_hash_table_insert(header_table, g_strdup("prpl-aim"),
1219 g_strdup("*** Encrypted with the Gaim-Encryption plugin <A HREF=\""));
1220 g_hash_table_insert(footer_table, g_strdup("prpl-aim"),
1221 g_strdup("\"></A>"));
1222 g_hash_table_insert(notify_table, g_strdup("prpl-aim"),
1223 g_strdup("<A HREF=\"Gaim-Encryption Capable\"></A>"));
1224
1225 /* If jabber stops stripping HTML, we can go back to these headers */
1226 /* g_hash_table_insert(header_table, g_strdup("prpl-jabber"), */
1227 /* g_strdup("*** Encrypted with the Gaim-Encryption plugin <A HREF='")); */
1228 /* g_hash_table_insert(footer_table, g_strdup("prpl-jabber"), */
1229 /* g_strdup("'></A>")); */
1230 /* g_hash_table_insert(notify_table, g_strdup("prpl-jabber"), */
1231 /* g_strdup("<A HREF='Gaim-Encryption Capable'> </A>")); */
1232
1233
1234 g_hash_table_insert(header_table, g_strdup("prpl-jabber"),
1235 g_strdup("*** Encrypted with the Gaim-Encryption plugin "));
1236 g_hash_table_insert(footer_table, g_strdup("prpl-jabber"),
1237 g_strdup(" "));
1238 g_hash_table_insert(notify_table, g_strdup("prpl-jabber"),
1239 g_strdup("<A HREF='Gaim-Encryption Capable'> </A>"));
1240
1241 header_default = g_strdup("*** Encrypted :");
1242
1243 header_broken = g_strdup("*** Encrypted with the Gaim-Encryption plugin");
1244
1245 broken_users = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
1246 }
1247
1248 /* #define CRYPT_HEADER "*** Encrypted with the Gaim-Encryption plugin <A HREF=\"" */
1249 /* #define CRYPT_FOOTER "\"></A>" */
1250 /* #define CRYPT_NOTIFY_HEADER "<A HREF=\"Gaim-Encryption Capable\"></A>" */
1251
1252 // Jabber seems to turn our double quotes into single quotes at times, so define
1253 // the same headers, only with single quotes. Lengths MUST be the same as above
1254 /* #define CRYPT_HEADER_MANGLED "*** Encrypted with the Gaim-Encryption plugin <A HREF='" */
1255 /* #define CRYPT_NOTIFY_HEADER_MANGLED "<A HREF='Gaim-Encryption Capable'></A>" */
1256
1257
1258 static void init_prefs() {
1259 /* These only add/set a pref if it doesn't currently exist: */
1260
1261 int default_width;
1262
1263 if (purple_prefs_get_type("/plugins/gtk/encrypt/accept_unknown_key") == PURPLE_PREF_NONE) {
1264 /* First time loading the plugin, since we don't have our prefs set yet */
1265
1266 /* so up the default window width to accomodate new buttons */
1267 default_width = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/im/default_width");
1268
1269 if (default_width == 410) { /* the stock pidgin default width */
1270 purple_prefs_set_int(PIDGIN_PREFS_ROOT "/conversations/im/default_width", 490);
1271 }
1272 }
1273
1274 purple_prefs_add_none("/plugins/gtk");
1275 purple_prefs_add_none("/plugins/gtk/encrypt");
1276
1277 purple_prefs_add_bool("/plugins/gtk/encrypt/accept_unknown_key", FALSE);
1278 purple_prefs_add_bool("/plugins/gtk/encrypt/accept_conflicting_key", FALSE);
1279 purple_prefs_add_bool("/plugins/gtk/encrypt/encrypt_response", TRUE);
1280 purple_prefs_add_bool("/plugins/gtk/encrypt/broadcast_notify", FALSE);
1281 purple_prefs_add_bool("/plugins/gtk/encrypt/encrypt_if_notified", TRUE);
1282
1283 purple_prefs_add_string("/plugins/gtk/encrypt/key_path", "");
1284 purple_prefs_add_string("/plugins/gtk/encrypt/key_path_displayed", purple_user_dir());
1285
1286 PE_pref_callback_id =
1287 purple_prefs_connect_callback(PE_plugin_handle, "/plugins/gtk/encrypt/key_path_displayed",
1288 PE_prefs_changed_cb, 0);
1289
1290 PE_convert_legacy_prefs();
1291 }
1292
1293 /* Called by Purple when plugin is first loaded */
1294 static gboolean PE_plugin_load(PurplePlugin *h) {
1295
1296 void *conv_handle;
1297
1298 #ifdef ENABLE_NLS
1299 bindtextdomain (ENC_PACKAGE, LOCALEDIR);
1300 bind_textdomain_codeset (ENC_PACKAGE, "UTF-8");
1301 setlocale(LC_ALL, "");
1302 #endif
1303
1304 purple_debug(PURPLE_DEBUG_INFO, "pidgin-encryption",
1305 "Compiled with Purple '%d.%d.%d', running with Purple '%s'.\n",
1306 PURPLE_MAJOR_VERSION, PURPLE_MINOR_VERSION, PURPLE_MICRO_VERSION, purple_core_get_version());
1307
1308 init_prefs();
1309
1310 conv_handle = purple_conversations_get_handle();
1311
1312 purple_debug(PURPLE_DEBUG_INFO, "pidgin-encryption", "plugin_load called\n");
1313 PE_plugin_handle = h;
1314
1315 PE_state_init();
1316 PE_pixmap_init();
1317
1318 if (!rsa_nss_init()) {
1319 return FALSE;
1320 }
1321
1322 PE_key_rings_init();
1323 PE_nonce_map_init();
1324 PE_state_ui_init();
1325 PE_headers_init();
1326
1327 purple_signal_connect(conv_handle, "receiving-im-msg", h,
1328 PURPLE_CALLBACK(PE_got_msg_cb), NULL);
1329 purple_signal_connect(conv_handle, "sending-im-msg", h,
1330 PURPLE_CALLBACK(PE_send_msg_cb), NULL);
1331 purple_signal_connect(conv_handle, "conversation-created", h,
1332 PURPLE_CALLBACK(PE_new_conv_cb), NULL);
1333 purple_signal_connect(conv_handle, "conversation-updated", h,
1334 PURPLE_CALLBACK(PE_updated_conv_cb), NULL);
1335 purple_signal_connect(conv_handle, "deleting-conversation", h,
1336 PURPLE_CALLBACK(PE_del_conv_cb), NULL);
1337 purple_signal_connect(pidgin_log_get_handle(), "log-displaying", h,
1338 PURPLE_CALLBACK(PE_log_displaying_cb), NULL);
1339
1340 purple_signal_connect(purple_blist_get_handle(), "blist-node-extended-menu", h,
1341 PURPLE_CALLBACK(PE_buddy_menu_cb), NULL);
1342
1343
1344 purple_conversation_foreach(PE_sync_state);
1345
1346 purple_debug(PURPLE_DEBUG_MISC, "pidgin-encryption", "done loading\n");
1347
1348
1349 return TRUE;
1350 }
1351
1352 /* Called by Purple when plugin is removed */
1353 static gboolean PE_plugin_unload(PurplePlugin *h) {
1354
1355 purple_signals_disconnect_by_handle(h);
1356
1357 purple_prefs_disconnect_callback(PE_pref_callback_id);
1358
1359 PE_config_unload();
1360
1361 purple_conversation_foreach(PE_remove_decorations);
1362
1363 PE_my_priv_ring = PE_clear_ring(PE_my_priv_ring);
1364 PE_my_pub_ring = PE_clear_ring(PE_my_pub_ring);
1365 PE_buddy_ring = PE_clear_ring(PE_buddy_ring);
1366
1367 PE_state_delete();
1368 PE_state_ui_delete();
1369 return TRUE;
1370 }
1371
1372 static PidginPluginUiInfo ui_info =
1373 {
1374 PE_get_config_frame,
1375 0, /* page_num (Reserved) */
1376
1377 /* padding */
1378 NULL,
1379 NULL,
1380 NULL,
1381 NULL
1382 };
1383
1384 static PurplePluginInfo info =
1385 {
1386 PURPLE_PLUGIN_MAGIC, /**< I'm a plugin! */
1387 PURPLE_MAJOR_VERSION,
1388 PURPLE_MINOR_VERSION,
1389 PURPLE_PLUGIN_STANDARD, /**< type */
1390 PIDGIN_PLUGIN_TYPE, /**< ui_requirement */
1391 0, /**< flags */
1392 NULL, /**< dependencies */
1393 PURPLE_PRIORITY_DEFAULT, /**< priority */
1394
1395 ENCRYPT_PLUGIN_ID, /**< id */
1396 0, /**< name */
1397 ENC_VERSION, /**< version */
1398 0, /** summary */
1399 0, /** description */
1400 0, /**< author */
1401 ENC_WEBSITE, /**< homepage */
1402
1403 PE_plugin_load, /**< load */
1404 PE_plugin_unload, /**< unload */
1405 NULL, /**< destroy */
1406
1407 &ui_info, /**< ui_info */
1408 NULL, /**< extra_info */
1409 NULL, /**< prefs_info */
1410 NULL, /**< actions */
1411
1412 /* padding */
1413 NULL,
1414 NULL,
1415 NULL,
1416 NULL
1417 };
1418
1419 static void
1420 init_plugin(PurplePlugin *plugin)
1421 {
1422
1423 #ifdef ENABLE_NLS
1424 bindtextdomain (ENC_PACKAGE, LOCALEDIR);
1425 bind_textdomain_codeset (ENC_PACKAGE, "UTF-8");
1426 setlocale(LC_ALL, "");
1427 #endif
1428
1429 info.name = _("Pidgin-Encryption");
1430 info.summary = _("Encrypts conversations with RSA encryption.");
1431 info.description = _("RSA encryption with keys up to 4096 bits,"
1432 " using the Mozilla NSS crypto library.\n");
1433 /* Translators: Feel free to add your name to the author field, with text like */
1434 /* "Bill Tompkins, translation by Phil McGee" */
1435 info.author = _("Bill Tompkins");
1436
1437 }
1438
1439
1440 PURPLE_INIT_PLUGIN(pidgin_encryption, init_plugin, info);
1441