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