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