1 /*  Protocol-independent Key structures                                   */
2 /*             Copyright (C) 2001-2003 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 #include "internal.h"
19 
20 #include <glib.h>
21 #if GLIB_CHECK_VERSION(2,6,0)
22 #	include <glib/gstdio.h>
23 #else
24 #	define g_freopen freopen
25 #	define g_fopen fopen
26 #	define g_rmdir rmdir
27 #	define g_remove remove
28 #	define g_unlink unlink
29 #	define g_lstat lstat
30 #	define g_stat stat
31 #	define g_mkdir mkdir
32 #	define g_rename rename
33 #	define g_open open
34 #endif
35 
36 #include <gdk/gdk.h>
37 #include <gtk/gtk.h>
38 #include <gtk/gtkplug.h>
39 
40 #include <debug.h>
41 #include <util.h>
42 
43 #include <time.h>
44 #include <sys/types.h>
45 #ifndef _WIN32
46   #include <sys/time.h>
47 #endif
48 #include <string.h>
49 #include <unistd.h>
50 #include <math.h>
51 
52 #include <stdio.h>
53 #include <stdlib.h>
54 #include <sys/types.h>
55 #include <sys/stat.h>
56 #include <fcntl.h>
57 #include <errno.h>
58 
59 #include "keys.h"
60 #include "cryptutil.h"
61 #include "prefs.h"
62 #include "encrypt.h"
63 #include "keys_ui.h"
64 #include "pe_ui.h"
65 #include "nls.h"
66 
67 #ifdef _WIN32
68 #include "win32dep.h"
69 #endif
70 
71 /* List of all the keys we know about */
72 key_ring *PE_buddy_ring = 0, *PE_saved_buddy_ring = 0, *PE_my_priv_ring = 0, *PE_my_pub_ring = 0;
73 
74 typedef enum {KEY_MATCH, KEY_NOT_THERE, KEY_CONFLICT} KeyCheckVal;
75 
76 
77 static KeyCheckVal PE_check_known_key(const char *filename, key_ring_data* key);
78 
79 
PE_find_key_by_name(key_ring * ring,const char * name,PurpleAccount * acct)80 crypt_key * PE_find_key_by_name(key_ring *ring, const char *name, PurpleAccount *acct) {
81    key_ring *i = PE_find_key_node_by_name(ring, name, acct);
82    purple_debug(PURPLE_DEBUG_MISC, "pidgin-encryption", "find key by name: %s\n", name);
83    return (i == NULL) ? NULL : ((key_ring_data *)i->data)->key;
84 }
85 
PE_find_own_key_by_name(key_ring ** ring,char * name,PurpleAccount * acct,PurpleConversation * conv)86 crypt_key * PE_find_own_key_by_name(key_ring **ring, char *name, PurpleAccount *acct, PurpleConversation *conv) {
87    crypt_key *key = PE_find_key_by_name(*ring, name, acct);
88    if (key) return key;
89 
90    /* Can't find the key, but it's ours, so we'll make one */
91    purple_debug(PURPLE_DEBUG_ERROR, "pidgin-encryption", "Error!  Can't find own key for %s\n",
92               name);
93    purple_debug(PURPLE_DEBUG_ERROR, "pidgin-encryption", "Dumping public keyring:\n");
94    PE_debug_dump_keyring(PE_my_pub_ring);
95 
96    if (conv != 0) {
97       purple_conversation_write(conv, "Encryption Manager",
98                               _("Making new key pair..."),
99                               PURPLE_MESSAGE_SYSTEM, time((time_t)NULL));
100    }
101 
102    PE_make_private_pair((crypt_proto *)crypt_proto_list->data, name, conv->account, 1024);
103 
104    key = PE_find_key_by_name(*ring, name, conv->account);
105    if (key) return key;
106 
107    /* Still no key: something is seriously wrong.  Probably having trouble saving the */
108    /* key to the key file, or some such.                                              */
109 
110    purple_debug(PURPLE_DEBUG_ERROR, "pidgin-encryption", "Error!  Can't make new key for %s\n",
111               name);
112 
113    if (conv != 0) {
114       purple_conversation_write(conv, "Encryption Manager",
115                               _("Error trying to make key."),
116                               PURPLE_MESSAGE_SYSTEM, time((time_t)NULL));
117    }
118 
119    return 0;
120 }
121 
PE_find_key_node_by_name(key_ring * ring,const char * name,PurpleAccount * acct)122 key_ring * PE_find_key_node_by_name(key_ring *ring, const char *name, PurpleAccount* acct) {
123    key_ring *i = 0;
124 
125    for( i = ring; i != NULL; i = i->next ) {
126       if( (strncmp(name, ((key_ring_data *)i->data)->name, sizeof(((key_ring_data*)i->data)->name)) == 0 ) &&
127           (acct == ((key_ring_data*)i->data)->account))
128          break;
129    }
130 
131    return (i == NULL) ? NULL : i;
132 }
133 
PE_debug_dump_keyring(key_ring * ring)134 void PE_debug_dump_keyring(key_ring * ring) {
135    key_ring *i = 0;
136 
137    for( i = ring; i != NULL; i = i->next ) {
138       purple_debug(PURPLE_DEBUG_INFO, "pidgin-encryption", "Key ring::%*s::%p\n",
139                    (unsigned)sizeof(((key_ring_data *)i->data)->name),
140                    ((key_ring_data *)i->data)->name,
141                    ((key_ring_data *)i->data)->account);
142    }
143 }
144 
145 /* add_key_to_ring will ensure that there is only one key on a ring that matches
146    a given name.  So your buddy switches computers (and keys), we will discard
147    his old key when he sends us his new one.                                  */
148 
PE_add_key_to_ring(key_ring * ring,key_ring_data * key)149 key_ring* PE_add_key_to_ring(key_ring* ring, key_ring_data* key) {
150    key_ring* old_key = PE_find_key_node_by_name(ring, key->name, key->account);
151 
152    if (old_key != NULL) {
153       ring = g_slist_remove_link(ring, old_key);
154    }
155    ring = g_slist_prepend(ring, key);
156    return ring;
157 }
158 
PE_del_key_from_ring(key_ring * ring,const char * name,PurpleAccount * acct)159 key_ring* PE_del_key_from_ring(key_ring* ring, const char* name, PurpleAccount* acct) {
160    key_ring* old_key = PE_find_key_node_by_name(ring, name, acct);
161 
162    if (old_key != NULL) {
163       purple_debug(PURPLE_DEBUG_INFO, "pidgin-encryption", "Removing key for %s\n", name);
164       ring = g_slist_remove_link(ring, old_key);
165    }
166    return ring;
167 }
168 
PE_clear_ring(key_ring * ring)169 key_ring* PE_clear_ring(key_ring* ring) {
170    crypt_key* key;
171    key_ring *iter = ring;
172 
173    while (iter != NULL) {
174       key = ((key_ring_data *)(iter->data))->key;
175       PE_free_key(key);
176       g_free(iter->data);
177       iter = iter->next;
178    }
179    g_slist_free(ring);
180    return NULL;
181 }
182 
PE_received_key(char * key_msg,char * name,PurpleAccount * acct,PurpleConversation * conv,char ** orig_msg)183 void PE_received_key(char *key_msg, char *name, PurpleAccount* acct, PurpleConversation* conv, char** orig_msg) {
184    GSList *protoiter;
185    crypt_proto* proto=0;
186    char* key_len_msg=0;
187    unsigned int length;
188 	int realstart = 0;
189    gchar** after_key;
190    gchar* resend_msg_id = 0;
191 
192    key_ring_data *new_key;
193    KeyCheckVal keycheck_return;
194 
195    purple_debug(PURPLE_DEBUG_INFO, "pidgin-encryption", "received_key\n");
196 
197    if (strncmp(key_msg, ": Prot ", sizeof(": Prot ") - 1) != 0) {
198       purple_debug(PURPLE_DEBUG_ERROR, "pidgin-encryption", "Error in received key\n");
199       return;
200    }
201    key_msg += sizeof(": Prot ") - 1;
202 
203    protoiter = crypt_proto_list;
204    while (protoiter != 0 && proto == 0) {
205       if( (key_len_msg =
206            ((crypt_proto *)protoiter->data)->parseable(key_msg)) != 0 ) {
207          proto = ((crypt_proto *) protoiter->data);
208       }
209       protoiter = protoiter->next;
210    }
211 
212    if (proto == 0) {
213       purple_debug(PURPLE_DEBUG_ERROR, "pidgin-encryption", "Unknown protocol type: %10s\n", key_msg);
214       return;
215    }
216 
217    if ( (sscanf(key_len_msg, ": Len %u:%n", &length, &realstart) < 1) ||
218         (realstart == 0) ) {
219       purple_debug(PURPLE_DEBUG_ERROR, "pidgin-encryption", "Error in key header\n");
220       return;
221    }
222 
223    key_len_msg += realstart;
224    if (strlen(key_len_msg) < length) {
225       purple_debug(PURPLE_DEBUG_ERROR, "pidgin-encryption", "Length doesn't match in add_key\n");
226       return;
227    }
228 
229    purple_debug(PURPLE_DEBUG_MISC, "pidgin-encryption", "After key:%s\n", key_len_msg+length);
230    after_key = g_strsplit(key_len_msg+length, ":", 3);
231    if (after_key[0] && (strcmp(after_key[0], "Resend") == 0)) {
232       if (after_key[1]) {
233          resend_msg_id = g_strdup(after_key[1]);
234       }
235    }
236    g_strfreev(after_key);
237 
238    key_len_msg[length] = 0;
239 
240    /* Make a new node for the linked list */
241    new_key = g_malloc(sizeof(key_ring_data));
242    new_key->account = acct;
243    new_key->key = proto->parse_sent_key(key_len_msg);
244 
245    if (new_key->key == 0) {
246       g_free(new_key);
247       if (resend_msg_id) {
248          g_free(resend_msg_id);
249       }
250       purple_debug(PURPLE_DEBUG_ERROR, "pidgin-encryption", "Invalid key received\n");
251       return;
252    }
253 
254    strncpy(new_key->name, name, sizeof(new_key->name));
255 
256    keycheck_return = PE_check_known_key(Buddy_key_file, new_key);
257 
258    /* Now that we've pulled the key out of the original message, we can free it */
259    /* so that (maybe) a stored message can be returned in it                    */
260 
261    (*orig_msg)[0] = 0;
262    g_free(*orig_msg);
263    *orig_msg = 0;
264 
265    switch(keycheck_return) {
266    case KEY_NOT_THERE:
267       PE_choose_accept_unknown_key(new_key, resend_msg_id, conv);
268       break;
269    case KEY_MATCH:
270       PE_buddy_ring = PE_add_key_to_ring(PE_buddy_ring, new_key);
271       PE_send_stored_msgs(new_key->account, new_key->name);
272       PE_show_stored_msgs(new_key->account, new_key->name);
273       if (resend_msg_id) {
274          PE_resend_msg(new_key->account, new_key->name, resend_msg_id);
275       }
276 
277       break;
278    case KEY_CONFLICT:
279       if (conv) {
280          purple_conversation_write(conv, "Encryption Manager", _("Conflicting Key Received!"),
281                                  PURPLE_MESSAGE_SYSTEM, time((time_t)NULL));
282       }
283       PE_choose_accept_conflict_key(new_key, resend_msg_id, conv);
284       break;
285    }
286    if (resend_msg_id) {
287       g_free(resend_msg_id);
288       resend_msg_id = 0;
289    }
290 }
291 
get_base_key_path()292 static const char * get_base_key_path() {
293    const char * basepath = purple_prefs_get_string("/plugins/gtk/encrypt/key_path");
294    const char * displayedpath =  purple_prefs_get_string("/plugins/gtk/encrypt/key_path_displayed");
295 
296    // migration help: if the base key path is equal to purple_user_dir, blank the pref (ie default)
297    if (strcmp(basepath, purple_user_dir()) == 0) {
298       purple_prefs_set_string("/plugins/gtk/encrypt/key_path", "");
299       basepath = 0;
300    } else {
301       // see if the basepath matches purple_user_dir with .purple -> .gaim.  If so, migrate it
302       gchar ** splitPath = g_strsplit(purple_user_dir(), ".purple", 5);
303       gchar * legacyPath = g_strjoinv(".gaim", splitPath);
304 
305       if (strcmp(basepath, legacyPath) == 0) {
306          purple_prefs_set_string("/plugins/gtk/encrypt/key_path", "");
307          basepath = 0;
308       }
309 
310       g_strfreev(splitPath);
311       g_free(legacyPath);
312    }
313 
314 
315 
316    if (!basepath || *basepath == 0) {
317       basepath = purple_user_dir();
318       if (!displayedpath || strcmp(basepath, displayedpath) != 0) {
319          purple_prefs_set_string("/plugins/gtk/encrypt/key_path_displayed", basepath);
320       }
321    }
322    return basepath;
323 }
324 
PE_check_base_key_path()325 gboolean PE_check_base_key_path() {
326    char path[4096];
327    struct stat fs;
328 
329    g_snprintf(path, sizeof(path), "%s%s%s", get_base_key_path(), G_DIR_SEPARATOR_S, Private_key_file);
330 
331    if (!g_path_is_absolute(path)) {
332       return FALSE;
333    }
334 
335    if (stat(path, &fs) == -1) {
336       /* file does not exist */
337       return FALSE;
338    } else {
339       return TRUE;
340    }
341 }
342 
PE_check_known_key(const char * filename,key_ring_data * key)343 static KeyCheckVal PE_check_known_key(const char* filename, key_ring_data* key) {
344    char line[MAX_KEY_STORLEN];
345    GString *line_str, *key_str, *name_str;
346    char path[4096];
347 
348    struct stat fs;
349    FILE* fp;
350    int fd;
351    int found_name = 0;
352 
353    g_snprintf(path, sizeof(path), "%s%s%s", get_base_key_path(), G_DIR_SEPARATOR_S, filename);
354 
355    purple_debug(PURPLE_DEBUG_INFO, "pidgin-encryption", "Checking key file %s for name %s\n", path, key->name);
356 
357    /* check file permissions */
358    if (stat(path, &fs) == -1) {
359       /* file doesn't exist, so make it */
360       fd = g_open(path, O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
361       if (fd == -1) {
362          /* Ok, maybe something else strange is going on... */
363          purple_debug(PURPLE_DEBUG_ERROR, "pidgin-encryption", "Error trying to create a known key file\n");
364          return KEY_NOT_THERE;
365       }
366       fstat(fd, &fs);
367       fchmod(fd, fs.st_mode & S_IRWXU);  /* zero out non-owner permissions */
368       close(fd);
369    } else {
370 #ifdef S_IWGRP
371       /* WIN32 doesn't have user-based file permissions, so skips this */
372       if (fs.st_mode & (S_IWGRP | S_IWOTH)) {
373          fd = g_open(path, O_WRONLY, 0);
374          if (fd == -1) {
375             /* Ok, maybe something else strange is going on... */
376             purple_debug(PURPLE_DEBUG_ERROR, "pidgin-encryption", "Error trying to modify permissions on known key file\n");
377             return KEY_NOT_THERE;
378          }
379          fstat(fd, &fs);
380          fchmod(fd, fs.st_mode & S_IRWXU);  /* zero out non-owner permissions */
381          close(fd);
382          purple_debug(PURPLE_DEBUG_WARNING, "pidgin-encryption", "Changed file permissions on %s\n", path);
383       }
384 #endif
385    }
386 
387    /* build string from key */
388    name_str = g_string_new(key->name);
389    PE_escape_name(name_str);
390    if (key->account) {
391       g_string_append_printf(name_str, ",%s", purple_account_get_protocol_id(key->account));
392    } else {
393       g_string_append(name_str, ",");
394    }
395 
396    line_str = g_string_new(name_str->str);
397    g_string_append_printf(line_str, " %s ", key->key->proto->name);
398    key_str = PE_key_to_gstr(key->key);
399    g_string_append(line_str, key_str->str);
400 
401    /*   purple_debug(PURPLE_DEBUG_MISC, "pidgin-encryption", "built line '%s'\n", line_str->str); */
402 
403    /* look for key in file */
404    if( (fp = g_fopen(path, "r")) != NULL ) {
405       while (!feof(fp)) {
406          fgets(line, sizeof(line), fp);
407          /* purple_debug(PURPLE_DEBUG_MISC, "pidgin-encryption", "checking line '%s'\n", line); */
408          if ( (strchr(line, ' ') == line + name_str->len) &&
409               (strncmp(line_str->str, line, name_str->len) == 0) ) {
410             purple_debug(PURPLE_DEBUG_MISC, "pidgin-encryption", "Got Name\n");
411             found_name = 1;
412             if (strncmp(line_str->str, line, line_str->len) == 0) {
413                purple_debug(PURPLE_DEBUG_MISC, "pidgin-encryption", "Got Match\n");
414                fclose(fp);
415                g_string_free(line_str, TRUE);
416                g_string_free(key_str, TRUE);
417                g_string_free(name_str, TRUE);
418                return KEY_MATCH;
419             }
420          }
421       }
422       fclose(fp);
423    }
424 
425    g_string_free(line_str, TRUE);
426    g_string_free(key_str, TRUE);
427    g_string_free(name_str, TRUE);
428 
429    if (found_name) return KEY_CONFLICT;
430    return KEY_NOT_THERE;
431 }
432 
433 /* For now, we'll make all key files privately owned, even though the
434    id.pub and known_keys files could be public.                        */
435 
PE_add_key_to_file(const char * filename,key_ring_data * key)436 void PE_add_key_to_file(const char *filename, key_ring_data* key) {
437    GString *line_str, *key_str;
438 
439    char path[4096];
440    char errbuf[500];
441 
442    FILE* fp;
443    int fd;
444    char c;
445    struct stat fdstat;
446 
447    purple_debug(PURPLE_DEBUG_INFO, "pidgin-encryption", "Saving key to file:%s:%p\n", key->name, key->account);
448 
449    g_snprintf(path, sizeof(path), "%s%s%s", get_base_key_path(), G_DIR_SEPARATOR_S, filename);
450 
451    fd = g_open(path, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
452    if (fd == -1) {
453       purple_debug(PURPLE_DEBUG_ERROR, "pidgin-encryption", "Error opening key file %s for write\n", path);
454       /* WIN32 doesn't have user-based file permissions, so skips this */
455 #ifdef S_IRWXG
456       if (chmod(path, S_IRUSR | S_IWUSR) == -1) {
457 
458          purple_debug(PURPLE_DEBUG_ERROR, "pidgin-encryption", "Unable to change file mode, aborting\n");
459          g_snprintf(errbuf, sizeof(errbuf),
460                     _("Error changing access mode for file: %s\nCannot save key."), filename);
461          PE_ui_error(errbuf);
462          return;
463       }
464 #endif
465       fd = g_open(path, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
466       if (fd == -1) {
467          purple_debug(PURPLE_DEBUG_ERROR, "pidgin-encryption", "Changed mode, but still wonky.  Aborting.\n");
468          g_snprintf(errbuf, sizeof(errbuf),
469                     _("Error (2) changing access mode for file: %s\nCannot save key."), filename);
470          PE_ui_error(errbuf);
471          return;
472       } else {
473          purple_debug(PURPLE_DEBUG_ERROR, "pidgin-encryption", "Key file '%s' no longer read-only.\n", path);
474       }
475    }
476    fstat(fd, &fdstat);
477 #ifdef S_IRWXG
478    /* WIN32 doesn't have user-based file permissions, so skips this */
479    if (fdstat.st_mode & (S_IRWXG | S_IRWXO)) {
480       fchmod(fd, fdstat.st_mode & S_IRWXU);  /* zero out non-owner permissions */
481 
482       purple_debug(PURPLE_DEBUG_ERROR, "pidgin-encryption", "Bad permissions on key file.  Changed: %s\n", path);
483       g_snprintf(errbuf, sizeof(errbuf),
484                  _("Permissions on key file changed for: %s\n"
485                    "Pidgin-Encryption will not save keys to a world- or group-accessible file."),
486                    filename);
487       PE_ui_error(errbuf);
488    }
489 #endif
490 
491    line_str = g_string_new(key->name);
492    PE_escape_name(line_str);
493    if (key->account) {
494       g_string_append_printf(line_str, ",%s", purple_account_get_protocol_id(key->account));
495    } else {
496       g_string_append(line_str, ",");
497    }
498    g_string_append_printf(line_str, " %s ", key->key->proto->name);
499    key_str = PE_key_to_gstr(key->key);
500    g_string_append(line_str, key_str->str);
501 
502    purple_debug(PURPLE_DEBUG_INFO, "pidgin-encryption", "here\n");
503    /* To be nice to users... we'll allow the last key in the file to not    */
504    /* have a trailing \n, so they can cut-n-paste with abandon.             */
505    fp = fdopen(fd, "r");
506    fseek(fp, -1, SEEK_END);
507    c = fgetc(fp);
508    if (feof(fp)) c = '\n';  /*if file is empty, we don't need to write a \n */
509    fclose(fp);
510 
511    fd = g_open(path, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
512    fp = fdopen(fd, "a+");
513    fseek(fp, 0, SEEK_END);   /* should be unnecessary, but needed for WIN32 */
514 
515    if (c != '\n') fputc('\n', fp);
516    fputs(line_str->str, fp);
517    fclose(fp);
518 
519    g_string_free(key_str, TRUE);
520    g_string_free(line_str, TRUE);
521 }
522 
PE_del_one_key_from_file(const char * filename,int key_num,const char * name)523 void PE_del_one_key_from_file(const char *filename, int key_num, const char *name) {
524    char line[MAX_KEY_STORLEN];
525    char path[4096], tmp_path[4096];
526 
527    int foundit = 0;
528    FILE *fp, *tmp_fp;
529    int fd;
530    int line_num;
531 
532    GString *line_start, *old_style_start, *normalized_start;
533 
534    line_start = g_string_new(name);
535    PE_escape_name(line_start);
536    g_string_append_printf(line_start, ",");
537 
538    old_style_start = g_string_new(name);
539    PE_escape_name(old_style_start);
540    g_string_append_printf(old_style_start, " ");
541 
542    normalized_start = g_string_new(name);
543    PE_escape_name(normalized_start);
544    g_string_append_printf(normalized_start, " ");
545 
546    g_snprintf(path, sizeof(path), "%s%s%s", get_base_key_path(), G_DIR_SEPARATOR_S, filename);
547 
548    /* Look for name in the file.  If it's not there, we're done */
549    fp = g_fopen(path, "r");
550 
551    if (fp == NULL) {
552       g_string_free(line_start, TRUE);
553       g_string_free(old_style_start, TRUE);
554       g_string_free(normalized_start, TRUE);
555       return;
556    }
557 
558    for (line_num = 0; line_num <= key_num; ++line_num) {
559       fgets(line, sizeof(line), fp);
560    }
561 
562    if ( (strncmp(line, line_start->str, line_start->len) == 0) ||
563         (strncmp(line, old_style_start->str, old_style_start->len) == 0) ||
564         (strncmp(line, normalized_start->str, normalized_start->len) == 0) ) {
565       foundit = 1;
566    }
567 
568    fclose(fp);
569 
570    purple_debug(PURPLE_DEBUG_INFO, "pidgin-encryption", "Delete one key: found(%d)\n", foundit);
571 
572    if (!foundit) {
573       g_string_free(line_start, TRUE);
574       g_string_free(old_style_start, TRUE);
575       g_string_free(normalized_start, TRUE);
576       return;
577    }
578 
579    /* It's there.  Move file to a temporary, and copy the other lines */
580 
581    g_snprintf(tmp_path, sizeof(tmp_path), "%s.tmp", path);
582    rename(path, tmp_path);
583 
584    fd = g_open(path, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
585    if (fd == -1) {
586       purple_debug(PURPLE_DEBUG_ERROR, "pidgin-encryption", "Error opening key file %s\n", path);
587       perror("Error opening key file");
588       g_string_free(line_start, TRUE);
589       g_string_free(old_style_start, TRUE);
590       g_string_free(normalized_start, TRUE);
591 
592       return;
593    }
594    fp = fdopen(fd, "a+");
595 
596    tmp_fp = g_fopen(tmp_path, "r");
597    if (tmp_fp == NULL) {
598       purple_debug(PURPLE_DEBUG_ERROR, "pidgin-encryption", "Wah!  I moved a file and now it is gone\n");
599       fclose(fp);
600       g_string_free(line_start, TRUE);
601       g_string_free(old_style_start, TRUE);
602       g_string_free(normalized_start, TRUE);
603 
604       return;
605    }
606 
607    line_num = 0;
608    while (fgets(line, sizeof(line), tmp_fp)) {
609       if (line_num != key_num) {
610          fputs(line, fp);
611       } else {
612          purple_debug(PURPLE_DEBUG_INFO, "pidgin-encryption", "Skipping line %d\n", line_num);
613       }
614       ++line_num;
615    }
616 
617    fclose(fp);
618    fclose(tmp_fp);
619    unlink(tmp_path);
620    g_string_free(line_start, TRUE);
621 }
622 
623 
PE_del_key_from_file(const char * filename,const char * name,PurpleAccount * acct)624 void PE_del_key_from_file(const char *filename, const char *name, PurpleAccount *acct) {
625    char line[MAX_KEY_STORLEN];
626    char path[4096], tmp_path[4096];
627 
628    int foundit = 0;
629    FILE *fp, *tmp_fp;
630    int fd;
631    GString *line_start, *old_style_start, *normalized_start;
632 
633    line_start = g_string_new(name);
634    PE_escape_name(line_start);
635    if (acct != 0) {
636       g_string_append_printf(line_start, ",%s", purple_account_get_protocol_id(acct));
637    } else {
638       g_string_append_printf(line_start, ",");
639    }
640 
641    old_style_start = g_string_new(name);
642    PE_escape_name(old_style_start);
643    g_string_append_printf(old_style_start, " ");
644 
645    normalized_start = g_string_new(name);
646    PE_escape_name(normalized_start);
647    g_string_append_printf(normalized_start, " ");
648 
649    g_snprintf(path, sizeof(path), "%s%s%s", get_base_key_path(), G_DIR_SEPARATOR_S, filename);
650 
651    /* Look for name in the file.  If it's not there, we're done */
652    fp = g_fopen(path, "r");
653 
654    if (fp == NULL) {
655       g_string_free(line_start, TRUE);
656       g_string_free(old_style_start, TRUE);
657       g_string_free(normalized_start, TRUE);
658       return;
659    }
660 
661    while (fgets(line, sizeof(line), fp)) {
662       if ( (strncmp(line, line_start->str, line_start->len) == 0) ||
663            (strncmp(line, old_style_start->str, old_style_start->len) == 0) ||
664            (strncmp(line, normalized_start->str, normalized_start->len) == 0) ) {
665          foundit = 1;
666       }
667    }
668    fclose(fp);
669    if (!foundit) {
670       g_string_free(line_start, TRUE);
671       g_string_free(old_style_start, TRUE);
672       g_string_free(normalized_start, TRUE);
673       return;
674    }
675 
676    /* It's there.  Move file to a temporary, and copy the lines */
677    /* that don't match.                                         */
678    g_snprintf(tmp_path, sizeof(tmp_path), "%s.tmp", path);
679    rename(path, tmp_path);
680 
681    fd = g_open(path, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
682    if (fd == -1) {
683       purple_debug(PURPLE_DEBUG_ERROR, "pidgin-encryption", "Error opening key file %s\n", path);
684       perror("Error opening key file");
685       g_string_free(line_start, TRUE);
686       g_string_free(old_style_start, TRUE);
687       g_string_free(normalized_start, TRUE);
688 
689       return;
690    }
691    fp = fdopen(fd, "a+");
692 
693    tmp_fp = g_fopen(tmp_path, "r");
694    if (tmp_fp == NULL) {
695       purple_debug(PURPLE_DEBUG_ERROR, "pidgin-encryption", "Wah!  I moved a file and now it is gone\n");
696       fclose(fp);
697       g_string_free(line_start, TRUE);
698       g_string_free(old_style_start, TRUE);
699       g_string_free(normalized_start, TRUE);
700 
701       return;
702    }
703    while (fgets(line, sizeof(line), tmp_fp)) {
704       if ( (strncmp(line, line_start->str, line_start->len) != 0) &&
705            (strncmp(line, old_style_start->str, old_style_start->len) != 0) &&
706            (strncmp(line, normalized_start->str, normalized_start->len) != 0) ) {
707          fputs(line, fp);
708       }
709    }
710 
711    fclose(fp);
712    fclose(tmp_fp);
713    unlink(tmp_path);
714    g_string_free(line_start, TRUE);
715 }
716 
717 
718 
PE_load_keys(const char * filename)719 key_ring * PE_load_keys(const char *filename) {
720    FILE* fp;
721    char name[64], nameacct[164], proto[20], proto_name[10], proto_ver[10], key_str_buf[MAX_KEY_STORLEN];
722    char path[4096];
723    int rv;
724    key_ring *new_ring = 0;
725    key_ring_data *new_key;
726    GSList* proto_node;
727    gchar **nameaccount_split;
728    PurpleAccount* account;
729 
730    g_snprintf(path, sizeof(path), "%s%s%s", get_base_key_path(), G_DIR_SEPARATOR_S, filename);
731 
732    /* Check permissions on file before use */
733 #ifdef S_IRWXG
734    /* WIN32 doesn't have user-based file permissions, so skips this */
735    {
736       char errbuf[500];
737       struct stat fdstat;
738       int fd = g_open(path, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
739       if (fd != -1) {
740          fstat(fd, &fdstat);
741          if (fdstat.st_mode & (S_IRWXG | S_IRWXO)) {
742             fchmod(fd, fdstat.st_mode & S_IRWXU);  /* zero out non-owner permissions */
743 
744             purple_debug(PURPLE_DEBUG_ERROR, "pidgin-encryption", "Bad permissions on key file.  Changed: %s\n", path);
745             g_snprintf(errbuf, sizeof(errbuf),
746                        _("Permissions on key file changed for: %s\n"
747                          "Pidgin-Encryption will not use keys in a world- or group-accessible file."),
748                        filename);
749             PE_ui_error(errbuf);
750          }
751          close(fd);
752       }
753    }
754 #endif
755 
756 if( (fp = g_fopen(path, "r")) != NULL ) {
757       do {
758       	 /* 7999 = MAX_KEY_STORLEN - 1 */
759          rv = fscanf(fp, "%163s %9s %9s %7999s\n", nameacct, proto_name,
760                      proto_ver, key_str_buf);
761 
762          if( rv == 4 ) {
763             if (strlen(key_str_buf) > MAX_KEY_STORLEN - 2) {
764                purple_debug(PURPLE_DEBUG_ERROR, "pidgin-encryption", "Problem in key file.  Increase key buffer size.\n");
765                continue;
766             }
767             nameaccount_split = g_strsplit(nameacct, ",", 2);
768             strncpy(name, nameaccount_split[0], sizeof(name));
769             name[sizeof(name)-1] = 0;
770             PE_unescape_name(name);
771 
772             /* This will do the right thing: if no account, it will match any */
773             account = purple_accounts_find(name, nameaccount_split[1]);
774 
775             purple_debug(PURPLE_DEBUG_INFO, "pidgin-encryption", "load_keys: name(%s), protocol (%s): %p\n",
776                        nameaccount_split[0],
777                        ((nameaccount_split[1]) ? nameaccount_split[1] : "none"),
778                         account);
779 
780             g_strfreev(nameaccount_split);
781 
782             /* purple_debug(PURPLE_DEBUG_MISC, "pidgin-encryption", "load_keys() %i: Read: %s:%s %s %s\n",
783                __LINE__, filename, name, proto_name, proto_ver); */
784 
785             /* find the make_key_from_str for this protocol */
786             g_snprintf(proto, sizeof(proto), "%s %s", proto_name, proto_ver);
787             proto_node = crypt_proto_list;
788             while (proto_node != NULL) {
789                if (strcmp(((crypt_proto *)proto_node->data)->name, proto) == 0)
790                   break;
791                proto_node = proto_node->next;
792             }
793 
794             if (proto_node == NULL) {
795                purple_debug(PURPLE_DEBUG_ERROR, "pidgin-encryption", "load_keys() %i: invalid protocol: %s\n",
796                             __LINE__, proto);
797                continue;
798             }
799 
800             new_key = g_malloc(sizeof(key_ring_data));
801             new_key->key =
802                ((crypt_proto *)proto_node->data)->make_key_from_str(key_str_buf);
803 
804             new_key->account = account;
805             strncpy(new_key->name, name, sizeof(new_key->name));
806             purple_debug(PURPLE_DEBUG_MISC, "pidgin-encryption", "load_keys() %i: Added: %*s %s %s\n", __LINE__,
807                          (unsigned)sizeof(new_key->name), new_key->name, proto_name, proto_ver);
808 
809             new_ring = g_slist_append(new_ring, new_key);
810          } else if (rv > 0) {
811             purple_debug(PURPLE_DEBUG_ERROR, "pidgin-encryption", "Bad key (%s) in file: %s\n", name, path);
812          }
813       } while( rv != EOF );
814 
815       fclose(fp);
816    } else {
817       if (errno != ENOENT) {
818          purple_debug(PURPLE_DEBUG_WARNING, "pidgin-encryption", "Couldn't open file:%s\n", path);
819          perror("Error opening file");
820       } else {
821          purple_debug(PURPLE_DEBUG_WARNING, "pidgin-encryption",
822                     "File %s doesn't exist (yet).  A new one will be created.\n", path);
823       }
824    }
825 
826    return new_ring;
827 }
828 
PE_key_rings_init()829 void PE_key_rings_init() {
830    GSList *proto_node;
831    GList *cur_sn;
832    crypt_key *pub_key = 0, *priv_key = 0;
833    key_ring_data *new_key;
834    char *name;
835    PurpleAccount *acct;
836 
837    /* Clear any rings that have data from a previous init */
838    if (PE_my_pub_ring) PE_clear_ring(PE_my_pub_ring);
839    if (PE_my_priv_ring) PE_clear_ring(PE_my_priv_ring);
840    if (PE_saved_buddy_ring) PE_clear_ring(PE_saved_buddy_ring);
841 
842    /* Load the keys */
843    PE_my_pub_ring = PE_load_keys(Public_key_file);
844    PE_my_priv_ring = PE_load_keys(Private_key_file);
845    PE_saved_buddy_ring = PE_load_keys(Buddy_key_file);
846 
847    /* Create a key for each screen name if we don't already have one */
848 
849    for (cur_sn = purple_accounts_get_all(); cur_sn != NULL; cur_sn = cur_sn->next) {
850       acct = (PurpleAccount *)cur_sn->data;
851       name = acct->username;
852       priv_key = PE_find_key_by_name(PE_my_priv_ring, name, acct);
853       pub_key = PE_find_key_by_name(PE_my_pub_ring, name, acct);
854 
855       if (priv_key == NULL) { /* No key for this username.  Make one.  */
856          proto_node = crypt_proto_list;
857          /* make a pair using the first protocol that comes to mind. */
858          /* user can override using the config tool */
859          PE_make_private_pair((crypt_proto *)proto_node->data, name, (PurpleAccount*)(cur_sn->data), 1024);
860       } else {  /* There is a private key  */
861          if (pub_key == NULL) { /* but no public key */
862             purple_debug(PURPLE_DEBUG_ERROR, "pidgin-encryption", "No public key found for %s\n", name);
863             purple_debug(PURPLE_DEBUG_ERROR, "pidgin-encryption", "  Making one from private key and saving...\n");
864             pub_key = priv_key->proto->make_pub_from_priv(priv_key);
865             new_key = g_malloc(sizeof(key_ring_data));
866             new_key->key = pub_key;
867             new_key->account = acct;
868             strncpy(new_key->name, name, sizeof(new_key->name));
869             PE_my_pub_ring = g_slist_append(PE_my_pub_ring, new_key);
870             PE_add_key_to_file(Public_key_file, new_key);
871          }
872       }
873    }
874 }
875 
PE_make_private_pair(crypt_proto * proto,const char * name,PurpleAccount * acct,int keylength)876 void PE_make_private_pair(crypt_proto* proto, const char* name, PurpleAccount* acct, int keylength) {
877    crypt_key *pub_key, *priv_key;
878    key_ring_data *new_key;
879 
880    proto->gen_key_pair(&pub_key, &priv_key, name, keylength);
881 
882    new_key = g_malloc(sizeof(key_ring_data));
883    new_key->key = pub_key;
884    new_key->account = acct;
885    strncpy(new_key->name, name, sizeof(new_key->name));
886    PE_my_pub_ring = PE_add_key_to_ring(PE_my_pub_ring, new_key);
887 
888    PE_del_key_from_file(Public_key_file, name, acct);
889    PE_add_key_to_file(Public_key_file, new_key);
890 
891    new_key = g_malloc(sizeof(key_ring_data));
892    new_key->key = priv_key;
893    new_key->account = acct;
894    strncpy(new_key->name, name,
895            sizeof(new_key->name));
896    PE_my_priv_ring = PE_add_key_to_ring(PE_my_priv_ring, new_key);
897 
898    PE_del_key_from_file(Private_key_file, name, acct);
899    PE_add_key_to_file(Private_key_file, new_key);
900 }
901 
902