1 #include "FiSH.h"
2
3 /**
4 * Default key for FiSH ini file.
5 */
6 static const char default_iniKey[] = "blowinikey";
7
8 /*
9 * Load a base64 blowfish key for contact
10 * If theKey is NULL, only a test is made (= IsKeySetForContact)
11 * @param contactPtr
12 * @param theKey
13 * @return 1 if everything ok 0 if not
14 */
getContactKey(const char * contactPtr,char * theKey)15 int getContactKey(const char *contactPtr, char *theKey)
16 {
17 struct IniValue iniValue;
18 int bRet = FALSE;
19
20 iniValue = allocateIni(contactPtr, "key", iniPath);
21 getIniValue(contactPtr, "key", "", iniValue.key, iniValue.keySize, iniPath);
22
23 // don't process, encrypted key not found in ini
24 if (strlen(iniValue.key) < 16) {
25 freeIni(iniValue);
26 return bRet;
27 }
28
29 // encrypted key found
30 if (strncmp(iniValue.key, "+OK ", 4) == 0) {
31 if (theKey) {
32 decrypt_string((char *)iniKey, iniValue.key + 4, theKey,
33 strlen(iniValue.key + 4));
34 }
35 bRet = TRUE;
36 }
37
38 freeIni(iniValue);
39 return bRet;
40 }
41
42 /*
43 * Construct a ini section key for contact
44 * @param server irssi server record
45 * @param contactPtr contact
46 * @param iniSectionKey buffer to there the section key is generated
47 * @return TRUE if everything ok FALSE if not
48 */
getIniSectionForContact(const SERVER_REC * serverRec,const char * contactPtr,char * iniSectionKey)49 int getIniSectionForContact(const SERVER_REC * serverRec,
50 const char *contactPtr, char *iniSectionKey)
51 {
52 char *target;
53 size_t i, c;
54
55 ZeroMemory(iniSectionKey, CONTACT_SIZE);
56
57 if (contactPtr == NULL)
58 return FALSE;
59
60 if (iniSectionKey == NULL)
61 return FALSE;
62
63 target = g_ascii_strdown((gchar*)contactPtr, (gssize)strlen(contactPtr));
64
65 // Allows nicks with [ ], replaces them with ( )
66 for(i = 0, c = strlen(target); i < c; i++) {
67 if (target[i] == '[') target[i] = '(';
68 if (target[i] == ']') target[i] = ')';
69 }
70
71 if (serverRec != NULL) {
72 snprintf(iniSectionKey, CONTACT_SIZE, "%s:%s", serverRec->tag,
73 target);
74 } else {
75 snprintf(iniSectionKey, CONTACT_SIZE, "%s", target);
76 }
77
78 return TRUE;
79 }
80
81 /*
82 * Encrypt a message and store in bf_dest (using key for target)
83 * @param server
84 * @param msg_ptr
85 * @param target
86 * @param bf_dest
87 * @return 1 if everything ok 0 if not
88 */
FiSH_encrypt(const SERVER_REC * serverRec,const char * msgPtr,const char * target,char * bf_dest)89 int FiSH_encrypt(const SERVER_REC * serverRec, const char *msgPtr,
90 const char *target, char *bf_dest)
91 {
92 struct IniValue iniValue;
93 char contactName[CONTACT_SIZE] = "";
94
95 if (IsNULLorEmpty(msgPtr) || bf_dest == NULL || IsNULLorEmpty(target))
96 return 0;
97
98 if (settings_get_bool("process_outgoing") == 0)
99 return 0;
100
101 if (getIniSectionForContact(serverRec, target, contactName) == FALSE)
102 return 0;
103
104 iniValue = allocateIni(contactName, "key", iniPath);
105
106 if (getContactKey(contactName, iniValue.key) == FALSE) {
107 freeIni(iniValue);
108 return 0;
109 }
110
111 if (iniValue.cbc == 1) {
112 strcpy(bf_dest, "+OK *");
113 encrypt_string_cbc(iniValue.key, msgPtr, bf_dest + 5, strlen(msgPtr));
114 } else {
115 strcpy(bf_dest, "+OK ");
116 encrypt_string(iniValue.key, msgPtr, bf_dest + 4, strlen(msgPtr));
117 }
118
119 freeIni(iniValue);
120 return 1;
121 }
122
123 /*
124 * Decrypt a base64 cipher text (using key for target)
125 */
FiSH_decrypt(const SERVER_REC * serverRec,char * msg_ptr,const char * target,GString * decrypted_msg)126 int FiSH_decrypt(const SERVER_REC * serverRec, char *msg_ptr,
127 const char *target, GString* decrypted_msg)
128 {
129 char contactName[CONTACT_SIZE] = "";
130 struct IniValue iniValue;
131 char bf_dest[1000] = "";
132 char myMark[20] = "";
133 int msg_len, i, mark_broken_block = 0, action_found = 0;
134 int mode = 0;
135 int cbc_ret = 0;
136
137 if (IsNULLorEmpty(msg_ptr) || decrypted_msg == NULL || IsNULLorEmpty(target))
138 return 0;
139
140 if (settings_get_bool("process_incoming") == 0)
141 return 0;
142
143 if (strncmp(msg_ptr, "+OK ", 4) == 0)
144 msg_ptr += 4;
145 else if (strncmp(msg_ptr, "mcps ", 5) == 0)
146 msg_ptr += 5;
147 else
148 return 0; // don't process, blowcrypt-prefix not found
149
150 // Strip the * from the CBC mode
151 if (strncmp(msg_ptr, "*", 1) == 0) {
152 mode = 1;
153 msg_ptr++;
154 }
155
156 msg_len = strlen(msg_ptr);
157
158 // Verify base64 string - only for ECB
159 if (mode == 0 && (strspn(msg_ptr, B64) != (size_t) msg_len))
160 return 0;
161
162 if (msg_len < 12)
163 return 0;
164
165 if (getIniSectionForContact(serverRec, target, contactName) == FALSE)
166 return 0;
167
168 iniValue = allocateIni(contactName, "key", iniPath);
169
170 if (getContactKey(contactName, iniValue.key) == FALSE) {
171 freeIni(iniValue);
172 return 0;
173 }
174
175 // usually a received message does not exceed 512 chars, but we want to prevent evil buffer overflow
176 if (msg_len >= (int)(sizeof(bf_dest) * 1.5))
177 msg_ptr[(int)(sizeof(bf_dest) * 1.5) - 20] = '\0';
178
179 // block-align blowcrypt strings if truncated by IRC server (each block is 12 chars long)
180 // such a truncated block is destroyed and not needed anymore
181 if ((mode == 0) && (msg_len != (msg_len / 12) * 12)) {
182 msg_len = (msg_len / 12) * 12;
183 msg_ptr[msg_len] = '\0';
184 strncpy(myMark, settings_get_str("mark_broken_block"),
185 sizeof(myMark));
186 if (*myMark == '\0' || isNoChar(*myMark))
187 mark_broken_block = 0;
188 else
189 mark_broken_block = 1;
190 }
191
192 if (iniValue.cbc == 1) {
193 cbc_ret = decrypt_string_cbc(iniValue.key, msg_ptr, bf_dest, msg_len);
194 } else {
195 decrypt_string(iniValue.key, msg_ptr, bf_dest, msg_len);
196 }
197
198 if (cbc_ret == -1) {
199 strncpy(myMark, settings_get_str("mark_broken_block"),
200 sizeof(myMark));
201 if (*myMark == '\0' || isNoChar(*myMark))
202 mark_broken_block = 0;
203 else
204 mark_broken_block = 1;
205 }
206
207 freeIni(iniValue);
208
209 if (*bf_dest == '\0')
210 return 0; // don't process, decrypted msg is bad
211
212 // recode message again, last time it was the encrypted message...
213 if (settings_get_bool("recode") && serverRec != NULL) {
214 char *recoded = recode_in(serverRec, bf_dest, target);
215 if (recoded) {
216 strncpy(bf_dest, recoded, sizeof(bf_dest));
217 ZeroMemory(recoded, strlen(recoded));
218 g_free(recoded);
219 }
220 }
221
222 i = 0;
223 while (bf_dest[i] != 0x0A && bf_dest[i] != 0x0D && bf_dest[i] != '\0')
224 i++;
225 bf_dest[i] = '\0'; // in case of wrong key, decrypted message might have control characters -> cut message
226
227 if (strncmp(bf_dest, "\001ACTION ", 8) == 0) {
228 // ACTION message found
229 if (bf_dest[i - 1] == '\001')
230 bf_dest[i - 1] = '\0'; // remove 0x01 control character
231 action_found = 1;
232 }
233 // append broken-block-mark?
234 if (mark_broken_block)
235 strcat(bf_dest, myMark);
236
237 // append crypt-mark?
238 strncpy(myMark, settings_get_str("mark_encrypted"), sizeof(myMark));
239 if (*myMark != '\0') {
240 int markPos = settings_get_int("mark_position");
241 if (markPos == 0 || action_found)
242 strcat(bf_dest, myMark); // append mark at the end (default for ACTION messages)
243 else { // prefix mark
244 i = strlen(myMark);
245 memmove(bf_dest + i, bf_dest, strlen(bf_dest) + 1);
246 strncpy(bf_dest, myMark, i);
247 }
248 }
249
250 g_string_assign(decrypted_msg, bf_dest);
251 ZeroMemory(bf_dest, sizeof(bf_dest));
252
253 return 1;
254 }
255
decrypt_msg(SERVER_REC * server,char * msg,const char * nick,const char * address,const char * target)256 void decrypt_msg(SERVER_REC * server, char *msg, const char *nick,
257 const char *address, const char *target)
258 {
259 GString *decrypted;
260 const char *contactPtr;
261
262 if (msg == NULL || target == NULL || nick == NULL)
263 return;
264
265 //channel?
266 if (server_ischannel(server, target)) {
267 contactPtr = target;
268 } else {
269 contactPtr = nick;
270 }
271
272 decrypted = g_string_new("");
273 if (FiSH_decrypt(server, msg, contactPtr, decrypted)) {
274 if (strncmp(decrypted->str, "\001ACTION ", 8) == 0) {
275 // ACTION message found
276 signal_stop();
277 signal_emit("message irc action", 5, server,
278 decrypted->str + 8, nick, address, target);
279 }
280 else {
281 signal_continue(5, server, decrypted->str, nick, address, target);
282 }
283 ZeroMemory(decrypted->str, decrypted->len);
284 }
285 g_string_free(decrypted, TRUE);
286 }
287
encrypt_msg(SERVER_REC * server,char * target,char * msg,char * orig_target)288 void encrypt_msg(SERVER_REC * server, char *target, char *msg,
289 char *orig_target)
290 {
291 char bf_dest[800] = "", *plainMsg;
292 char contactName[CONTACT_SIZE] = "";
293
294 if (IsNULLorEmpty(msg) || IsNULLorEmpty(target))
295 return;
296
297 if (getIniSectionForContact(server, target, contactName) == FALSE)
298 return;
299
300 if (getContactKey(contactName, NULL) == FALSE)
301 return;
302
303 plainMsg = isPlainPrefix(msg);
304 if (plainMsg) {
305 signal_continue(4, server, target, plainMsg, orig_target);
306 return;
307 }
308 // generally cut a message to a size of 512 byte, as everything above will never arrive complete anyway
309 if (strlen(msg) > 512)
310 msg[512] = '\0';
311
312 if (FiSH_encrypt(server, msg, target, bf_dest) == 1) { // message was encrypted
313 bf_dest[512] = '\0';
314 signal_continue(4, server, target, bf_dest, orig_target);
315 }
316 }
317
318 /*
319 * format outgoing (encrypted) messages (add crypt-mark or remove plain-prefix)
320 */
format_msg(SERVER_REC * server,char * msg,char * target,char * orig_target)321 void format_msg(SERVER_REC * server, char *msg, char *target, char *orig_target)
322 {
323 char contactName[CONTACT_SIZE] = "", myMark[20] = "";
324 char *plainMsg;
325
326 if (IsNULLorEmpty(msg) || IsNULLorEmpty(target))
327 return;
328
329 if (settings_get_bool("process_outgoing") == 0)
330 return;
331
332 if (getIniSectionForContact(server, target, contactName) == FALSE)
333 return;
334
335 if (getContactKey(contactName, NULL) == FALSE)
336 return;
337
338 plainMsg = isPlainPrefix(msg);
339 if (plainMsg) {
340 signal_continue(4, server, plainMsg, target, orig_target);
341 return;
342 }
343
344 // generally cut a message to a size of 512 byte, as everything above will never arrive complete anyway
345 if (strlen(msg) > 512)
346 msg[512] = '\0';
347
348 // append crypt-mark?
349 strncpy(myMark, settings_get_str("mark_encrypted"), sizeof(myMark));
350 if (*myMark != '\0') {
351 char formattedMsg[800];
352 strcpy(formattedMsg, msg);
353 int markPos = settings_get_int("mark_position");
354 if (markPos == 0)
355 strcat(formattedMsg, myMark); //append mark at the end
356 else { // prefix mark
357 int i = strlen(myMark);
358 memmove(formattedMsg + i, formattedMsg,
359 strlen(formattedMsg) + 1);
360 strncpy(formattedMsg, myMark, i);
361 }
362
363 signal_continue(4, server, formattedMsg, target, orig_target);
364
365 ZeroMemory(formattedMsg, sizeof(formattedMsg));
366 }
367
368 return;
369 }
370
371 /*
372 * Decrypt NOTICE messages (and forward DH1080 key-exchange)
373 */
decrypt_notice(SERVER_REC * server,char * msg,char * nick,char * address,char * target)374 void decrypt_notice(SERVER_REC * server, char *msg, char *nick, char *address,
375 char *target)
376 {
377 GString *decrypted;
378
379 if (strncmp(msg, "DH1080_", 7) == 0) {
380 DH1080_received(server, msg, nick, address, target);
381 return;
382 }
383
384 decrypted = g_string_new("");
385 if (FiSH_decrypt(server, msg, server_ischannel(server, target) ? target : nick, decrypted)) {
386 signal_continue(5, server, decrypted->str, nick, address, target);
387 ZeroMemory(decrypted->str, decrypted->len);
388 }
389 g_string_free(decrypted, TRUE);
390 }
391
decrypt_action(SERVER_REC * server,char * msg,char * nick,char * address,char * target)392 void decrypt_action(SERVER_REC * server, char *msg, char *nick, char *address,
393 char *target)
394 {
395 GString *decrypted;
396 if (target == NULL)
397 return;
398
399 decrypted = g_string_new("");
400 if (FiSH_decrypt(server, msg, server_ischannel(server, target) ? target : nick, decrypted)) {
401 signal_continue(5, server, decrypted->str, nick, address, target);
402 ZeroMemory(decrypted->str, decrypted->len);
403 }
404 g_string_free(decrypted, TRUE);
405 }
406
decrypt_topic(SERVER_REC * server,char * channel,char * topic,char * nick,char * address)407 void decrypt_topic(SERVER_REC * server, char *channel, char *topic, char *nick,
408 char *address)
409 {
410 GString *decrypted;
411
412 decrypted = g_string_new("");
413 if (FiSH_decrypt(server, topic, channel, decrypted)) {
414 signal_continue(5, server, channel, decrypted->str, nick, address);
415 ZeroMemory(decrypted->str, decrypted->len);
416 }
417 g_string_free(decrypted, TRUE);
418 }
419
decrypt_changed_topic(CHANNEL_REC * chan_rec)420 void decrypt_changed_topic(CHANNEL_REC * chan_rec)
421 {
422 GString *decrypted;
423
424 decrypted = g_string_new("");
425 if (FiSH_decrypt(chan_rec->server, chan_rec->topic,
426 chan_rec->name, decrypted)) {
427 g_free_not_null(chan_rec->topic);
428 chan_rec->topic = g_strdup(decrypted->str);
429 signal_continue(1, chan_rec);
430 ZeroMemory(decrypted->str, decrypted->len);
431 }
432 g_string_free(decrypted, TRUE);
433 }
434
raw_handler(SERVER_REC * server,char * data)435 void raw_handler(SERVER_REC * server, char *data)
436 {
437 GString *decrypted;
438 char channel[CONTACT_SIZE], *ptr, *ptr_bak;
439 int len;
440
441 if (IsNULLorEmpty(data))
442 return;
443
444 ptr = strchr(data, ' '); // point to command
445 if (ptr == NULL)
446 return;
447 ptr++;
448
449 if (strncmp(ptr, "332 ", 4) != 0)
450 return; // 332 = TOPIC
451
452 ptr_bak = ptr;
453 ptr = strchr(ptr, '#'); // point to #channel
454 if (ptr == NULL) {
455 ptr = strchr(ptr_bak, '&'); // point to &channel
456 if (ptr == NULL) {
457 ptr = strchr(ptr_bak, '!'); // point to !channel
458 if (ptr == NULL)
459 return;
460 }
461 }
462
463 len = strchr(ptr, ' ') - ptr;
464 if (len >= CONTACT_SIZE - 2)
465 return; // channel string too long, something went wrong
466 strncpy(channel, ptr, len);
467 channel[len] = '\0';
468
469 ptr = strchr(ptr, ':'); // point to topic msg start
470 if (ptr == NULL)
471 return;
472 ptr++;
473
474 decrypted = g_string_new("");
475 if (FiSH_decrypt(server, ptr, channel, decrypted)) {
476 g_string_prepend_len(decrypted, data, strlen(data) - strlen(ptr));
477 signal_continue(2, server, decrypted->str);
478 ZeroMemory(decrypted->str, decrypted->len);
479 }
480 g_string_free(decrypted, TRUE);
481 }
482
mark_crypted(const char * original)483 static char *mark_crypted(const char *original) {
484 const char * mark = settings_get_str("mark_encrypted");
485 if(mark == NULL || *mark == '\0') {
486 return strdup(original);
487 }
488
489 // 0 for suffix, anything else for prefix
490 const int mark_position = settings_get_int("mark_position");
491
492 const size_t new_len = strlen(original) + strlen(mark) + 1;
493 char * new = (char *) calloc(new_len, sizeof(char));
494
495 snprintf(new, new_len, "%s%s", mark_position == 0 ? original : mark,
496 mark_position == 0 ? mark : original);
497
498 return new;
499 }
500
501 /*
502 * /notice+ <nick/#channel> <notice message>
503 */
cmd_crypt_notice(const char * data,SERVER_REC * server,WI_ITEM_REC * item)504 void cmd_crypt_notice(const char *data, SERVER_REC * server, WI_ITEM_REC * item)
505 {
506 char bf_dest[1000] = "", *msg;
507 const char *target;
508 void *free_arg = NULL;
509 char *marked;
510
511 if (data == NULL || (strlen(data) < 3))
512 goto notice_error;
513 if (!cmd_get_params(data, &free_arg, 1, &target))
514 goto notice_error;
515
516 msg = strchr(data, ' ');
517 if (IsNULLorEmpty(target) || IsNULLorEmpty(msg))
518 goto notice_error;
519
520 msg++; // point to the notice message
521
522 // generally refuse a notice size of more than 512 byte, as everything above will never arrive complete anyway
523 if (strlen(msg) >= 512) {
524 printtext(server, target, MSGLEVEL_CRAP,
525 "\002FiSH:\002 /notice+ \002error\002: message argument exceeds buffer size!");
526 return;
527 }
528
529 if (FiSH_encrypt(server, msg, target, bf_dest) == 0) {
530 printtext(server, target, MSGLEVEL_CRAP,
531 "\002FiSH:\002 /notice+ \002error\002: Encryption disabled or no key found for %s.",
532 target);
533 return;
534 }
535
536 bf_dest[512] = '\0';
537 irc_send_cmdv((IRC_SERVER_REC *) server, "NOTICE %s :%s\n", target,
538 bf_dest);
539
540 marked = mark_crypted(msg);
541
542 signal_emit("message irc own_notice", 3, server, marked, target);
543 cmd_params_free(free_arg);
544 free(marked);
545 return;
546
547 notice_error:
548 if (free_arg)
549 cmd_params_free(free_arg);
550 printtext(server, item != NULL ? window_item_get_target(item) : NULL,
551 MSGLEVEL_CRAP,
552 "\002FiSH:\002 Usage: /notice+ <nick/#channel> <notice message>");
553 }
554
555 /*
556 * /me+ <action message>
557 */
cmd_crypt_action(const char * data,SERVER_REC * server,WI_ITEM_REC * item)558 void cmd_crypt_action(const char *data, SERVER_REC * server, WI_ITEM_REC * item)
559 {
560 char bf_dest[1000] = "";
561 const char *target;
562 char *marked;
563
564 if (data == NULL || (strlen(data) < 2))
565 goto action_error;
566
567 if (item != NULL)
568 target = window_item_get_target(item);
569 else
570 goto action_error;
571
572 // generally refuse an action size of more than 512 byte, as everything above will never arrive complete anyway
573 if (strlen(data) >= 512) {
574 printtext(server, target, MSGLEVEL_CRAP,
575 "\002FiSH:\002 /me+ \002error\002: message argument exceeds buffer size!");
576 return;
577 }
578
579 if (FiSH_encrypt(server, (char *)data, target, bf_dest) == 0) {
580 printtext(server, target, MSGLEVEL_CRAP,
581 "\002FiSH:\002 /me+ \002error\002: Encryption disabled or no key found for %s.",
582 target);
583 return;
584 }
585
586 bf_dest[512] = '\0';
587 irc_send_cmdv((IRC_SERVER_REC *) server,
588 "PRIVMSG %s :\001ACTION %s\001\n", target, bf_dest);
589
590 marked = mark_crypted(data);
591
592 signal_emit("message irc own_action", 3, server, marked, target);
593 free(marked);
594 return;
595
596 action_error:
597 printtext(server, item != NULL ? window_item_get_target(item) : NULL,
598 MSGLEVEL_CRAP, "\002FiSH:\002 Usage: /me+ <action message>");
599 }
600
601 /*
602 * Set encrypted topic for current channel, irssi syntax: /topic+ <your topic>
603 */
cmd_crypt_topic(const char * data,SERVER_REC * server,WI_ITEM_REC * item)604 void cmd_crypt_topic(const char *data, SERVER_REC * server, WI_ITEM_REC * item)
605 {
606 char bf_dest[1000] = "";
607 const char *target;
608
609 if (data == 0 || *data == '\0')
610 goto topic_error;
611 if (item != NULL)
612 target = window_item_get_target(item);
613 else
614 goto topic_error;
615
616 if (!server_ischannel(server, target)) {
617 printtext(server, target, MSGLEVEL_CRAP,
618 "\002FiSH:\002 Please change to the channel window where you want to set the topic!");
619 goto topic_error;
620 }
621 // generally refuse a topic size of more than 512 byte, as everything above will never arrive complete anyway
622 if (strlen(data) >= 512) {
623 printtext(server, target, MSGLEVEL_CRAP,
624 "\002FiSH:\002 /topic+ error: topic length exceeds buffer size!");
625 goto topic_error;
626 }
627 // encrypt a message (using key for target)
628 if (FiSH_encrypt(server, (char *)data, target, bf_dest) == 0) {
629 printtext(server, target, MSGLEVEL_CRAP,
630 "\002FiSH:\002 /topic+ error: Encryption disabled or no key found for %s.",
631 target);
632 goto topic_error;
633 }
634
635 bf_dest[512] = '\0';
636 irc_send_cmdv((IRC_SERVER_REC *) server, "TOPIC %s :%s\n", target,
637 bf_dest);
638 return;
639
640 topic_error:
641 printtext(server, item != NULL ? window_item_get_target(item) : NULL,
642 MSGLEVEL_CRAP,
643 "\002FiSH:\002 Usage: /topic+ <your new topic>");
644 }
645
sig_complete_topic_plus(GList ** list,WINDOW_REC * window,const char * word,const char * line,int * want_space)646 static void sig_complete_topic_plus(GList **list, WINDOW_REC *window,
647 const char *word, const char *line,
648 int *want_space)
649 {
650 char *topic;
651
652 g_return_if_fail(list != NULL);
653 g_return_if_fail(word != NULL);
654
655 if (*word == '\0' && IS_CHANNEL(window->active)) {
656 topic = g_strdup(CHANNEL(window->active)->topic);
657 if (topic != NULL) {
658 const char *mark = settings_get_str("mark_encrypted");
659 if (!IsNULLorEmpty(mark)) {
660 int topic_len = strlen(topic);
661 int mark_len = strlen(mark);
662 if (settings_get_int("mark_position") == 0) { // suffix
663 char *p = topic + (topic_len - mark_len);
664 if (strncmp(p, mark, mark_len) == 0) {
665 *p = '\0'; // Remove mark
666 }
667 }
668 else { // prefix
669 if (strncmp(topic, mark, mark_len) == 0) {
670 memmove(topic, topic + mark_len, topic_len - mark_len + 1);
671 }
672 }
673 }
674
675 *list = g_list_append(NULL, topic);
676 signal_stop();
677 }
678 }
679 }
680
681
cmd_helpfish(const char * arg,SERVER_REC * server,WI_ITEM_REC * item)682 void cmd_helpfish(const char *arg, SERVER_REC * server, WI_ITEM_REC * item)
683 {
684 printtext(NULL, NULL, MSGLEVEL_CRAP,
685 "\n\002FiSH HELP:\002 For more information read FiSH-irssi.txt :)\n\n"
686 " /topic+ <your new topic>\n"
687 " /notice+ <nick/#channel> <notice message>\n"
688 " /me+ <your action message>\n"
689 " /setkey [-<server tag>] [<nick | #channel>] <key>\n"
690 " /delkey [-<server tag>] [<nick | #channel>]\n"
691 " /key|showkey [-<server tag>] [<nick | #channel>]\n"
692 " /keyx [-ecb|-cbc] [<nick>]\n"
693 " /setinipw <blow.ini_password>\n"
694 " /unsetinipw\n"
695 " /fishlogin\n");
696 }
697
recrypt_ini_file(const char * iniPath,const char * iniPath_new,const char * old_iniKey)698 int recrypt_ini_file(const char *iniPath, const char *iniPath_new,
699 const char *old_iniKey)
700 {
701 GKeyFile *config = g_key_file_new();
702 GError *error = NULL;
703 gsize groups_count = 0;
704 int i;
705 int re_enc = 0;
706 int newbfKeySize;
707 char *newbfKey;
708 int plusOkSize;
709 char *plusOk;
710 int bfKeySize;
711 char *bfKey;
712
713 g_key_file_load_from_file(config, iniPath, G_KEY_FILE_NONE, &error);
714 if (error != NULL) {
715 g_error_free(error);
716 error = NULL;
717 g_key_file_free(config);
718 return -1;
719 }
720
721 gchar **groups = g_key_file_get_groups(config, &groups_count);
722
723 for (i = 0; i < groups_count; i++) {
724
725 gsize keys_count = 0;
726 gchar **keys = g_key_file_get_keys(config, groups[i], &keys_count, &error);
727
728 if (error != NULL) {
729 g_error_free(error);
730 error = NULL;
731 continue;
732 }
733
734 int j;
735
736 for (j = 0; j < keys_count; j++) {
737 gchar *value = g_key_file_get_value(config, groups[i], keys[j], &error);
738
739 if (error != NULL) {
740 g_error_free(error);
741 error = NULL;
742 continue;
743 }
744
745 if (strncmp(value, "+OK ", 4) == 0) {
746 re_enc = 1;
747
748 bfKeySize = (strlen(value) * 2) * sizeof(char);
749 bfKey = (char *) calloc(bfKeySize, sizeof(char));
750 decrypt_string(old_iniKey, value + 4, bfKey, strlen(value + 4));
751
752 newbfKeySize = (strlen(bfKey) * 2)* sizeof(char);
753 newbfKey = (char *) calloc(newbfKeySize, sizeof(char));
754 encrypt_string(iniKey, bfKey, newbfKey, strlen(bfKey));
755
756 plusOkSize = (strlen(newbfKey) * 2) * sizeof(char);
757 plusOk = (char *) calloc(plusOkSize, sizeof(char));
758 snprintf(plusOk, plusOkSize, "+OK %s", newbfKey);
759
760 setIniValue(groups[i], keys[j], plusOk, iniPath_new);
761
762 bzero(bfKey, bfKeySize);
763 free(bfKey);
764 bzero(newbfKey, newbfKeySize);
765 free(newbfKey);
766 bzero(plusOk, plusOkSize);
767 free(plusOk);
768 }
769
770 g_free(value);
771 }
772
773 g_strfreev(keys);
774 }
775
776 g_strfreev(groups);
777 g_key_file_free(config);
778
779 remove(iniPath);
780 rename(iniPath_new, iniPath);
781
782 return re_enc;
783 }
784
cmd_setinipw(const char * iniPW,SERVER_REC * server,WI_ITEM_REC * item)785 void cmd_setinipw(const char *iniPW, SERVER_REC * server, WI_ITEM_REC * item)
786 {
787 int re_enc = 0;
788 char B64digest[50] = { '\0' };
789 char key[32] = { '\0' };
790 char hash[32] = { '\0' };
791 char iniPath_new[300];
792 int old_iniKeySize;
793 char *old_iniKey;
794 int new_iniKeySize;
795 char *new_iniKey;
796
797 old_iniKeySize = strlen(iniKey) * sizeof(char);
798 old_iniKey = (char *) calloc(old_iniKeySize + 1, sizeof(char));
799 strcpy(old_iniKey, iniKey);
800
801 if (iniPW != NULL) {
802 size_t pw_len = strlen(iniPW);
803
804 new_iniKeySize = pw_len * 2 + 1;
805 new_iniKey = (char *) calloc(new_iniKeySize, sizeof(char));
806
807 if (pw_len < 1 || (size_t) pw_len > new_iniKeySize) {
808 printtext(server,
809 item !=
810 NULL ? window_item_get_target(item) : NULL,
811 MSGLEVEL_CRAP,
812 "\002FiSH:\002 No parameters. Usage: /setinipw <sekure_blow.ini_password>");
813 bzero(old_iniKey, old_iniKeySize);
814 free(old_iniKey);
815 bzero(new_iniKey, new_iniKeySize);
816 free(new_iniKey);
817 return;
818 }
819
820 if (strfcpy(new_iniKey, (char *)iniPW, new_iniKeySize) == NULL) {
821 bzero(old_iniKey, old_iniKeySize);
822 free(old_iniKey);
823 bzero(new_iniKey, new_iniKeySize);
824 free(new_iniKey);
825 return;
826 }
827
828 ZeroMemory(iniPW, pw_len);
829 pw_len = strlen(new_iniKey);
830
831 if (pw_len < 8) {
832 printtext(server,
833 item !=
834 NULL ? window_item_get_target(item) : NULL,
835 MSGLEVEL_CRAP,
836 "\002FiSH:\002 Password too short, at least 8 characters needed! Usage: /setinipw <sekure_blow.ini_password>");
837 bzero(old_iniKey, old_iniKeySize);
838 free(old_iniKey);
839 bzero(new_iniKey, new_iniKeySize);
840 free(new_iniKey);
841 return;
842 }
843
844 key_from_password(new_iniKey, key);
845 htob64(key, B64digest, 32);
846
847 free(iniKey);
848 iniKey = (char *) calloc(strlen(B64digest) * 2, sizeof(char));
849
850 strcpy(iniKey, B64digest); // this is used for encrypting blow.ini
851
852 bzero(new_iniKey, new_iniKeySize);
853 free(new_iniKey);
854 } else {
855 strcpy(iniKey, default_iniKey); // use default blow.ini key
856 }
857
858 key_hash(key, hash);
859 htob64(hash, B64digest, 32); // this is used to verify the entered password
860 ZeroMemory(hash, sizeof(hash));
861 ZeroMemory(key, sizeof(key));
862
863 // Try to create blow.ini if it doesnt exist
864 open(iniPath, O_CREAT | O_WRONLY | O_EXCL, S_IRUSR | S_IWUSR);
865
866 // re-encrypt blow.ini with new password
867 strcpy(iniPath_new, iniPath);
868 strcat(iniPath_new, "_new");
869
870 re_enc = recrypt_ini_file(iniPath, iniPath_new, old_iniKey);
871 if (re_enc < 0) {
872 printtext(server,
873 item != NULL ? window_item_get_target(item) : NULL,
874 MSGLEVEL_CRAP,
875 "\002FiSH ERROR:\002 Unable to write new blow.ini, probably out of disc space.");
876 ZeroMemory(B64digest, sizeof(B64digest));
877 bzero(old_iniKey, old_iniKeySize);
878 free(old_iniKey);
879 return;
880 } else {
881 bzero(old_iniKey, old_iniKeySize);
882 free(old_iniKey);
883 }
884
885 if (setIniValue("FiSH", "ini_password_Hash", B64digest, iniPath) == -1) {
886 ZeroMemory(B64digest, sizeof(B64digest));
887 printtext(server,
888 item != NULL ? window_item_get_target(item) : NULL,
889 MSGLEVEL_CRAP,
890 "\002FiSH ERROR:\002 Unable to write to blow.ini, probably out of space or permission denied.");
891 return;
892 }
893
894 ZeroMemory(B64digest, sizeof(B64digest));
895
896 if (re_enc) {
897 printtext(server,
898 item != NULL ? window_item_get_target(item) : NULL,
899 MSGLEVEL_CRAP,
900 "\002FiSH: Re-encrypted blow.ini\002 with new password.");
901 }
902
903 if (iniPW != NULL) {
904 printtext(server,
905 item != NULL ? window_item_get_target(item) : NULL,
906 MSGLEVEL_CRAP,
907 "\002FiSH:\002 blow.ini password hash saved.");
908 }
909 }
910
911 /*
912 * Change back to default blow.ini password, irssi syntax: /unsetinipw
913 */
cmd_unsetinipw(const char * arg,SERVER_REC * server,WI_ITEM_REC * item)914 static void cmd_unsetinipw(const char *arg, SERVER_REC * server,
915 WI_ITEM_REC * item)
916 {
917 cmd_setinipw(NULL, server, item);
918
919 if (setIniValue("FiSH", "ini_password_Hash", "\0", iniPath) == -1) {
920 printtext(server,
921 item != NULL ? window_item_get_target(item) : NULL,
922 MSGLEVEL_CRAP,
923 "\002FiSH ERROR:\002 Unable to write to blow.ini, probably out of space or permission denied.");
924 return;
925 }
926
927 printtext(server, item != NULL ? window_item_get_target(item) : NULL,
928 MSGLEVEL_CRAP,
929 "\002FiSH:\002 Changed back to default blow.ini password, you won't have to enter it on start-up anymore!");
930 }
931
detect_mode(const char * key)932 int detect_mode(const char *key)
933 {
934 char mode[4];
935 int BLOWFISH_ECB = 0;
936 int BLOWFISH_CBC = 1;
937
938 if (strlen(key) > 4) {
939 strncpy(mode, key, 3);
940 mode[3] = '\0';
941
942 if (strcmp(mode, "cbc") == 0) {
943 return BLOWFISH_CBC;
944 }
945 }
946
947 return BLOWFISH_ECB;
948 }
949
950 /**
951 * Sets the key for a nick / channel in a server
952 * @param data command
953 * @param server irssi server record
954 * @param item irssi window/item
955 */
cmd_setkey(const char * data,SERVER_REC * server,WI_ITEM_REC * item)956 void cmd_setkey(const char *data, SERVER_REC * server, WI_ITEM_REC * item)
957 {
958 GHashTable *optlist;
959 char contactName[CONTACT_SIZE] = "";
960 char *encryptedKey;
961 int keySize;
962 int mode = 0;
963
964 const char *target, *key;
965 void *free_arg;
966
967 if (IsNULLorEmpty(data)) {
968 printtext(server,
969 item != NULL ? window_item_get_target(item) : NULL,
970 MSGLEVEL_CRAP,
971 "\002FiSH:\002 No parameters. Usage: /setkey [-<server tag>] [<nick | #channel>] <key>");
972 return;
973 }
974
975 if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_OPTIONS |
976 PARAM_FLAG_UNKNOWN_OPTIONS | PARAM_FLAG_GETREST,
977 "setkey", &optlist, &target, &key))
978 return;
979
980 if (*target == '\0') {
981 printtext(server,
982 item != NULL ? window_item_get_target(item) : NULL,
983 MSGLEVEL_CRAP,
984 "\002FiSH:\002 No parameters. Usage: /setkey [-<server tag>] [<nick | #channel>] <key>");
985 cmd_params_free(free_arg);
986 return;
987 }
988
989 server = cmd_options_get_server("setkey", optlist, server);
990 if (server == NULL || !server->connected)
991 cmd_param_error(CMDERR_NOT_CONNECTED);
992
993 if (*key == '\0') {
994 // one paramter given - it's the key
995 key = target;
996 if (item != NULL)
997 target = window_item_get_target(item);
998 else {
999 printtext(NULL, NULL, MSGLEVEL_CRAP,
1000 "\002FiSH:\002 Please define nick/#channel. Usage: /setkey [-<server tag>] [<nick | #channel>] <key>");
1001 cmd_params_free(free_arg);
1002 return;
1003 }
1004 }
1005
1006 keySize = (strlen(key) * 3) * sizeof(char);
1007 encryptedKey = (char *) calloc(keySize, sizeof(char));
1008 mode = detect_mode(key);
1009
1010 if (mode == 1) {
1011 encrypt_key((char *)key + 4, encryptedKey);
1012 } else {
1013 encrypt_key((char *)key, encryptedKey);
1014 }
1015
1016 if (getIniSectionForContact(server, target, contactName) == FALSE) {
1017 bzero(encryptedKey, keySize);
1018 free(encryptedKey);
1019 return;
1020 }
1021
1022 if (setIniValue(contactName, "key", encryptedKey, iniPath) == -1) {
1023 printtext(server,
1024 item != NULL ? window_item_get_target(item) : NULL,
1025 MSGLEVEL_CRAP,
1026 "\002FiSH ERROR:\002 Unable to write to blow.ini, probably out of space or permission denied.");
1027 cmd_params_free(free_arg);
1028 bzero(encryptedKey, keySize);
1029 free(encryptedKey);
1030 return;
1031 }
1032
1033 if (mode == 1) {
1034 setIniValue(contactName, "cbc", "1", iniPath);
1035 } else {
1036 setIniValue(contactName, "cbc", "0", iniPath);
1037 }
1038
1039 bzero(encryptedKey, keySize);
1040 free(encryptedKey);
1041
1042 printtext(server, item != NULL ? window_item_get_target(item) : NULL,
1043 MSGLEVEL_CRAP,
1044 "\002FiSH:\002 Key for %s@%s (%s) successfully set!", target,
1045 server->tag, mode == 1 ? "CBC" : "ECB");
1046
1047 cmd_params_free(free_arg);
1048 }
1049
cmd_delkey(const char * data,SERVER_REC * server,WI_ITEM_REC * item)1050 void cmd_delkey(const char *data, SERVER_REC * server, WI_ITEM_REC * item)
1051 {
1052 GHashTable *optlist;
1053 char *target;
1054 char contactName[CONTACT_SIZE] = "";
1055 void *free_arg;
1056
1057 if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS |
1058 PARAM_FLAG_UNKNOWN_OPTIONS | PARAM_FLAG_GETREST,
1059 "delkey", &optlist, &target))
1060 return;
1061
1062 if (item != NULL && IsNULLorEmpty(target))
1063 target = (char *)window_item_get_target(item);
1064
1065 if (IsNULLorEmpty(target)) {
1066 printtext(server,
1067 item != NULL ? window_item_get_target(item) : NULL,
1068 MSGLEVEL_CRAP,
1069 "\002FiSH:\002 No parameters. Usage: /delkey [-<server tag>] [<nick | #channel>]");
1070 return;
1071 }
1072
1073 server = cmd_options_get_server("delkey", optlist, server);
1074 if (server == NULL || !server->connected)
1075 cmd_param_error(CMDERR_NOT_CONNECTED);
1076
1077 target = (char *)g_strchomp(target);
1078
1079 if (getIniSectionForContact(server, target, contactName) == FALSE)
1080 return;
1081
1082 if (deleteIniValue(contactName, "key", iniPath) == 1) {
1083 printtext(server, item != NULL ? window_item_get_target(item) : NULL,
1084 MSGLEVEL_CRAP,
1085 "\002FiSH:\002 Key for %s@%s successfully removed!", target,
1086 server->tag);
1087 } else {
1088 printtext(server, item != NULL ? window_item_get_target(item) : NULL,
1089 MSGLEVEL_CRAP,
1090 "\002FiSH:\002 No key found for %s@%s", target,
1091 server->tag);
1092 }
1093 }
1094
cmd_key(const char * data,SERVER_REC * server,WI_ITEM_REC * item)1095 void cmd_key(const char *data, SERVER_REC * server, WI_ITEM_REC * item)
1096 {
1097 GHashTable *optlist;
1098 char *target;
1099 char contactName[CONTACT_SIZE] = "";
1100 struct IniValue iniValue;
1101 void *free_arg;
1102
1103 if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS |
1104 PARAM_FLAG_UNKNOWN_OPTIONS | PARAM_FLAG_GETREST,
1105 "key", &optlist, &target))
1106 return;
1107
1108 if (item != NULL && IsNULLorEmpty(target))
1109 target = (char *)window_item_get_target(item);
1110
1111 if (IsNULLorEmpty(target)) {
1112 printtext(server,
1113 item != NULL ? window_item_get_target(item) : NULL,
1114 MSGLEVEL_CRAP,
1115 "\002FiSH:\002 Please define nick/#channel. Usage: /key [-<server tag>] [<nick | #channel>]");
1116 return;
1117 }
1118
1119 server = cmd_options_get_server("key", optlist, server);
1120 if (server == NULL || !server->connected)
1121 cmd_param_error(CMDERR_NOT_CONNECTED);
1122
1123 target = (char *)g_strchomp(target);
1124
1125 if (getIniSectionForContact(server, target, contactName) == FALSE)
1126 return;
1127
1128 iniValue = allocateIni(contactName, "key", iniPath);
1129
1130 if (getContactKey(contactName, iniValue.key) == FALSE) {
1131 freeIni(iniValue);
1132
1133 printtext(server,
1134 item != NULL ? window_item_get_target(item) : NULL,
1135 MSGLEVEL_CRAP,
1136 "\002FiSH:\002 Key for %s@%s not found or invalid!",
1137 target, server->tag);
1138 return;
1139 }
1140
1141 printtext(server, target, MSGLEVEL_CRAP,
1142 "\002FiSH:\002 Key for %s@%s: %s (%s)", target, server->tag,
1143 iniValue.key, iniValue.cbc == 1 ? "CBC" : "ECB");
1144
1145 freeIni(iniValue);
1146 }
1147
cmd_keyx(const char * data,SERVER_REC * server,WI_ITEM_REC * item)1148 void cmd_keyx(const char *data, SERVER_REC * server, WI_ITEM_REC * item)
1149 {
1150 GHashTable *optlist = NULL;
1151 char *target = NULL;
1152 void *free_arg = NULL;
1153 int mode = -1;
1154 char contactName[CONTACT_SIZE] = "";
1155 struct IniValue iniValue;
1156
1157 if (server == NULL) {
1158 printtext(NULL, NULL, MSGLEVEL_CRAP,
1159 "\002FiSH:\002 No connection to server.");
1160 goto fail;
1161 }
1162
1163 if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_GETREST | PARAM_FLAG_OPTIONS,
1164 "keyx", &optlist, &target))
1165 goto fail;
1166
1167 if (g_hash_table_lookup(optlist, "ecb") != NULL) {
1168 mode = 0;
1169 }
1170
1171 if (g_hash_table_lookup(optlist, "cbc") != NULL) {
1172 mode = 1;
1173 }
1174
1175 if (item != NULL && IsNULLorEmpty(target))
1176 target = (char *)window_item_get_target(item);
1177
1178 if (IsNULLorEmpty(target)) {
1179 printtext(NULL, NULL, MSGLEVEL_CRAP,
1180 "\002FiSH:\002 Please define nick/#channel. Usage: /keyx [-ecb|-cbc] <nick>");
1181 goto fail;
1182 }
1183
1184 if (server_ischannel(server, target)) {
1185 printtext(server, target, MSGLEVEL_CRAP,
1186 "\002FiSH:\002 KeyXchange does not work for channels!");
1187 goto fail;
1188 }
1189
1190 target = (char *)g_strchomp(target);
1191
1192 if ((mode == -1) && (getIniSectionForContact(server, target, contactName))) {
1193 iniValue = allocateIni(contactName, "key", iniPath);
1194 if (iniValue.iniKeySize == 1) {
1195 mode = 1;
1196 } else {
1197 mode = iniValue.cbc;
1198 }
1199 freeIni(iniValue);
1200 }
1201
1202 DH1080_gen(g_myPrivKey, g_myPubKey);
1203
1204 irc_send_cmdv((IRC_SERVER_REC *) server, "NOTICE %s :%s%s%s", target,
1205 DH1080_INIT, g_myPubKey, mode == 1 ? CBC_SUFFIX : "");
1206
1207 printtext(server, item != NULL ? window_item_get_target(item) : NULL,
1208 MSGLEVEL_CRAP,
1209 "\002FiSH:\002 Sent my DH1080 public key to %s@%s (%s), waiting for reply ...",
1210 target, server->tag, mode == 1 ? "CBC" : "ECB");
1211 fail:
1212 if(free_arg) {
1213 cmd_params_free(free_arg);
1214 }
1215 }
1216
DH1080_received(SERVER_REC * server,char * msg,char * nick,char * address,char * target)1217 void DH1080_received(SERVER_REC * server, char *msg, char *nick, char *address,
1218 char *target)
1219 {
1220 int msg_len;
1221 char hisPubKey[300];
1222 char contactName[CONTACT_SIZE] = "";
1223 char encryptedKey[KEYBUF_SIZE] = "";
1224 // 0 for ECB, 1 for CBC
1225 int mode = 0;
1226 const unsigned int DH1080_INIT_LEN = strlen(DH1080_INIT);
1227 const unsigned int DH1080_FINISH_LEN = strlen(DH1080_FINISH);
1228 const unsigned int CBC_SUFFIX_LEN = strlen(CBC_SUFFIX);
1229
1230 if (server_ischannel(server, target) || server_ischannel(server, nick))
1231 return; // no KeyXchange for channels...
1232
1233 msg_len = strlen(msg);
1234
1235 if (msg_len < 191 || msg_len > 199)
1236 return;
1237
1238 if (strncmp(msg, DH1080_INIT, DH1080_INIT_LEN) == 0) {
1239
1240 // Check for CBC at the end
1241 if (strcmp(msg + msg_len - CBC_SUFFIX_LEN, " CBC") == 0) {
1242 mode = 1;
1243 }
1244
1245 if (mode == 0) {
1246 strcpy(hisPubKey, msg + DH1080_INIT_LEN);
1247 } else {
1248 // Strip the " CBC" at the end
1249 strncpy(hisPubKey, msg + DH1080_INIT_LEN, msg_len - DH1080_INIT_LEN - CBC_SUFFIX_LEN);
1250 hisPubKey[msg_len - DH1080_INIT_LEN - CBC_SUFFIX_LEN] = '\0';
1251 }
1252
1253 // This check only applies to ECB
1254 if ((mode == 0) && (strspn(hisPubKey, B64ABC) != strlen(hisPubKey)))
1255 return;
1256
1257 if (query_find(server, nick) == NULL) { // query window not found, lets create one
1258 keyx_query_created = 1;
1259 irc_query_create(server->tag, nick, TRUE);
1260 keyx_query_created = 0;
1261 }
1262
1263 printtext(server, nick, MSGLEVEL_CRAP,
1264 "\002FiSH:\002 Received DH1080 public key from %s@%s (%s), sending mine...",
1265 nick, server->tag, mode == 0 ? "ECB" : "CBC");
1266
1267 DH1080_gen(g_myPrivKey, g_myPubKey);
1268
1269 irc_send_cmdv((IRC_SERVER_REC *) server, "NOTICE %s :%s%s%s",
1270 nick, DH1080_FINISH, g_myPubKey, mode == 1 ? CBC_SUFFIX : "");
1271
1272 } else if (strncmp(msg, DH1080_FINISH, DH1080_FINISH_LEN) == 0) {
1273
1274 msg_len = strlen(msg);
1275
1276 // Check for CBC at the end
1277 if (strcmp(msg + msg_len - CBC_SUFFIX_LEN, CBC_SUFFIX) == 0) {
1278 mode = 1;
1279 }
1280
1281 if (mode == 0) {
1282 strcpy(hisPubKey, msg + DH1080_FINISH_LEN);
1283 } else {
1284 // Strip the " CBC" at the end
1285 strncpy(hisPubKey, msg + DH1080_FINISH_LEN, msg_len - DH1080_FINISH_LEN - CBC_SUFFIX_LEN);
1286 hisPubKey[msg_len - DH1080_FINISH_LEN - CBC_SUFFIX_LEN] = '\0';
1287 }
1288 } else {
1289 return;
1290 }
1291
1292 if (DH1080_comp(g_myPrivKey, hisPubKey) == 0)
1293 return;
1294 signal_stop();
1295
1296 encrypt_key(hisPubKey, encryptedKey);
1297 ZeroMemory(hisPubKey, sizeof(hisPubKey));
1298
1299 if (getIniSectionForContact(server, nick, contactName) == FALSE)
1300 return;
1301
1302 if (setIniValue(contactName, "key", encryptedKey, iniPath) == -1) {
1303 ZeroMemory(encryptedKey, KEYBUF_SIZE);
1304 printtext(server, nick, MSGLEVEL_CRAP,
1305 "\002FiSH ERROR:\002 Unable to write to blow.ini, probably out of space or permission denied.");
1306 return;
1307 }
1308
1309 // Remember mode
1310 if (setIniValue(contactName, "cbc", mode == 0 ? "0" : "1", iniPath) == -1) {
1311 printtext(server, nick, MSGLEVEL_CRAP,
1312 "\002FiSH ERROR:\002 Unable to write to blow.ini, probably out of space or permission denied.");
1313 return;
1314 }
1315
1316 ZeroMemory(encryptedKey, KEYBUF_SIZE);
1317
1318 printtext(server, nick, MSGLEVEL_CRAP,
1319 "\002FiSH:\002 Key for %s@%s (%s) successfully set!", nick, server->tag, mode == 0 ? "ECB" : "CBC");
1320 }
1321
1322 /*
1323 * Perform auto-keyXchange only for known people
1324 */
do_auto_keyx(QUERY_REC * query,int automatic)1325 void do_auto_keyx(QUERY_REC * query, int automatic)
1326 {
1327 char contactName[CONTACT_SIZE] = "";
1328
1329 if (keyx_query_created)
1330 return; // query was created by FiSH
1331
1332 if (settings_get_bool("auto_keyxchange") == 0)
1333 return;
1334
1335 if (getIniSectionForContact(query->server, query->name, contactName) ==
1336 FALSE)
1337 return;
1338
1339 if (getContactKey(contactName, NULL))
1340 cmd_keyx(query->name, query->server, NULL);
1341 }
1342
1343 /*
1344 * Copy key for old nick to use with the new one
1345 */
query_nick_changed(QUERY_REC * query,char * orignick)1346 void query_nick_changed(QUERY_REC * query, char *orignick)
1347 {
1348 struct IniValue iniValue;
1349 char contactName[CONTACT_SIZE] = "";
1350
1351 if (settings_get_bool("nicktracker") == 0)
1352 return;
1353
1354 if (orignick == NULL || strcasecmp(orignick, query->name) == 0)
1355 return; // same nick, different case?
1356
1357 if (getIniSectionForContact(query->server, orignick, contactName) ==
1358 FALSE)
1359 return;
1360
1361 iniValue = allocateIni(contactName, "key", iniPath);
1362
1363 if (getContactKey(contactName, iniValue.key) == FALSE) {
1364 freeIni(iniValue);
1365 return; // see if there is a key for the old nick
1366 }
1367
1368 if (getIniSectionForContact(query->server, query->name, contactName) ==
1369 FALSE) {
1370 freeIni(iniValue);
1371 return;
1372 }
1373
1374 if (setIniValue(contactName, "key", iniValue.key, iniPath) == -1)
1375 printtext(NULL, NULL, MSGLEVEL_CRAP,
1376 "\002FiSH ERROR:\002 Unable to write to blow.ini, probably out of space or permission denied.");
1377
1378 freeIni(iniValue);
1379 }
1380
calculate_password_key_and_hash(const char * a_password,char * a_key,char * a_hash)1381 void calculate_password_key_and_hash(const char *a_password,
1382 char *a_key, char *a_hash)
1383 {
1384 char key[256 / 8];
1385 char hash[256 / 8];
1386
1387 key_from_password(a_password, key);
1388 htob64(key, a_key, 32);
1389
1390 key_hash(key, hash);
1391 htob64(hash, a_hash, 32);
1392 }
1393
1394 /**
1395 * Iterate over all channels and send the "channel topic changed" signal to it
1396 * If any of them is FiSHed and you have the key, the topicbar will be updated
1397 */
refresh_topics()1398 void refresh_topics()
1399 {
1400 GSList *list;
1401
1402 for (list = channels; list != NULL; list = g_slist_next(list)) {
1403 CHANNEL_REC *rec = list->data;
1404 signal_emit("channel topic changed", 1, rec);
1405 }
1406 }
1407
setup_fish()1408 void setup_fish()
1409 {
1410 signal_add_first("server sendmsg", (SIGNAL_FUNC) encrypt_msg);
1411 signal_add_first("message private", (SIGNAL_FUNC) decrypt_msg);
1412 signal_add_first("message public", (SIGNAL_FUNC) decrypt_msg);
1413 signal_add_first("message irc notice", (SIGNAL_FUNC) decrypt_notice);
1414 signal_add_first("message irc action", (SIGNAL_FUNC) decrypt_action);
1415
1416 signal_add_first("message own_private", (SIGNAL_FUNC) format_msg);
1417 signal_add_first("message own_public", (SIGNAL_FUNC) format_msg);
1418
1419 signal_add_first("channel topic changed",
1420 (SIGNAL_FUNC) decrypt_changed_topic);
1421 signal_add_first("message topic", (SIGNAL_FUNC) decrypt_topic);
1422 signal_add_first("server incoming", (SIGNAL_FUNC) raw_handler);
1423
1424 signal_add("query created", (SIGNAL_FUNC) do_auto_keyx);
1425 signal_add("query nick changed", (SIGNAL_FUNC) query_nick_changed);
1426
1427 signal_add("complete command topic+", (SIGNAL_FUNC) sig_complete_topic_plus);
1428
1429 command_bind("topic+", NULL, (SIGNAL_FUNC) cmd_crypt_topic);
1430 command_bind("notice+", NULL, (SIGNAL_FUNC) cmd_crypt_notice);
1431 command_bind("me+", NULL, (SIGNAL_FUNC) cmd_crypt_action);
1432 command_bind("setkey", NULL, (SIGNAL_FUNC) cmd_setkey);
1433 command_bind("delkey", NULL, (SIGNAL_FUNC) cmd_delkey);
1434 command_bind("key", NULL, (SIGNAL_FUNC) cmd_key);
1435 command_bind("showkey", NULL, (SIGNAL_FUNC) cmd_key);
1436 command_bind("keyx", NULL, (SIGNAL_FUNC) cmd_keyx);
1437 command_set_options("keyx", "-ecb -cbc");
1438 command_bind("setinipw", NULL, (SIGNAL_FUNC) cmd_setinipw);
1439 command_bind("unsetinipw", NULL, (SIGNAL_FUNC) cmd_unsetinipw);
1440
1441 refresh_topics();
1442 }
1443
get_ini_password_hash(int password_size,char * password)1444 void get_ini_password_hash(int password_size, char* password) {
1445 getIniValue("FiSH", "ini_password_Hash", "0", password,
1446 password_size, iniPath);
1447 }
1448
1449
authenticated_fish_setup(const char * password,void * rec)1450 void authenticated_fish_setup(const char *password, void *rec) {
1451 char *B64digest;
1452 struct IniValue iniValue;
1453
1454 if (strlen(password) == 0) {
1455 return;
1456 }
1457
1458 if (iniUsed == 1) {
1459 free(iniKey);
1460 iniUsed = 0;
1461 }
1462
1463 iniKey = (char *) calloc((strlen(password) * 10), sizeof(char));
1464 iniUsed = 1;
1465
1466 iniValue = allocateIni("FiSH", "ini_password_Hash", iniPath);
1467
1468 // Verify if the key really exists
1469 if (iniValue.iniKeySize == 1) {
1470 printtext(NULL, NULL, MSGLEVEL_CRAP, "\002FiSH:\002 No password set for blow.ini");
1471 return;
1472 }
1473
1474 get_ini_password_hash(iniValue.keySize, iniValue.key);
1475
1476 B64digest = (char *) calloc((iniValue.keySize * 2), sizeof(char));
1477
1478 calculate_password_key_and_hash(password, iniKey, B64digest);
1479
1480 if (strcmp(B64digest, iniValue.key) != 0) {
1481 bzero(B64digest, (iniValue.keySize * 2) * sizeof(char));
1482 free(B64digest);
1483 freeIni(iniValue);
1484
1485 printtext(NULL, NULL, MSGLEVEL_CRAP,
1486 "\002FiSH:\002 Wrong blow.ini password entered... Please \002/fishlogin\002 to try again");
1487 return;
1488 }
1489
1490 printtext(NULL, NULL, MSGLEVEL_CRAP,
1491 "\002FiSH:\002 Correct blow.ini password entered, lets go!");
1492
1493 bzero(B64digest, (iniValue.keySize * 2) * sizeof(char));
1494 free(B64digest);
1495 freeIni(iniValue);
1496
1497 setup_fish();
1498 }
1499
cmd_fishlogin(const char * data,SERVER_REC * server,WI_ITEM_REC * item)1500 void cmd_fishlogin(const char *data, SERVER_REC * server, WI_ITEM_REC * item) {
1501 keyboard_entry_redirect((SIGNAL_FUNC) authenticated_fish_setup,
1502 " --> Please enter your blow.ini password: ",
1503 ENTRY_REDIRECT_FLAG_HIDDEN, NULL);
1504
1505 }
1506
1507 #ifdef IRSSI_ABI_VERSION
fish_abicheck(int * version)1508 void fish_abicheck(int *version)
1509 {
1510 *version = IRSSI_ABI_VERSION;
1511 }
1512 #endif
1513
fish_init(void)1514 void fish_init(void)
1515 {
1516 struct IniValue iniValue;
1517
1518 printtext(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
1519 "FiSH " FISH_VERSION " - encryption module for irssi loaded!\n"
1520 "URL: https://github.com/falsovsky/FiSH-irssi\n"
1521 "Try /helpfish or /fishhelp for a short command overview");
1522
1523 command_bind("fishhelp", NULL, (SIGNAL_FUNC) cmd_helpfish);
1524 command_bind("helpfish", NULL, (SIGNAL_FUNC) cmd_helpfish);
1525 command_bind("fishlogin", NULL, (SIGNAL_FUNC) cmd_fishlogin);
1526
1527 settings_add_bool_module("fish", "fish", "process_outgoing", 1);
1528 settings_add_bool_module("fish", "fish", "process_incoming", 1);
1529 settings_add_bool_module("fish", "fish", "auto_keyxchange", 1);
1530 settings_add_bool_module("fish", "fish", "nicktracker", 1);
1531 settings_add_str_module("fish", "fish", "mark_broken_block",
1532 "\002&\002");
1533 settings_add_str_module("fish", "fish", "mark_encrypted", "\002>\002 ");
1534 settings_add_str_module("fish", "fish", "plain_prefix", "+p ");
1535 settings_add_int_module("fish", "fish", "mark_position", 1);
1536
1537 if (DH1080_Init() == FALSE)
1538 return;
1539
1540 strcpy(iniPath, get_irssi_config()); // path to irssi config file
1541 strcpy(strrchr(iniPath, '/'), blow_ini);
1542
1543 iniValue = allocateIni("FiSH", "ini_password_Hash", iniPath);
1544
1545 get_ini_password_hash(iniValue.keySize, iniValue.key);
1546
1547 if (strlen(iniValue.key) != 43) {
1548 iniKey = (char *) calloc((strlen(default_iniKey)* 2), sizeof(char));
1549 iniUsed = 1;
1550
1551 strcpy(iniKey, default_iniKey);
1552 printtext(NULL, NULL, MSGLEVEL_CRAP,
1553 "\002FiSH:\002 Using default password to decrypt blow.ini... Try /setinipw to set a custom password.");
1554
1555 setup_fish();
1556 } else {
1557 printtext(NULL, NULL, MSGLEVEL_CRAP,
1558 "\002FiSH:\002 Current blow.ini is password protected.");
1559 cmd_fishlogin(NULL, NULL, NULL);
1560 }
1561
1562 module_register("fish", "core");
1563
1564 free(iniValue.key);
1565 }
1566
1567
fish_deinit(void)1568 void fish_deinit(void)
1569 {
1570 signal_remove("server sendmsg", (SIGNAL_FUNC) encrypt_msg);
1571 signal_remove("message private", (SIGNAL_FUNC) decrypt_msg);
1572 signal_remove("message public", (SIGNAL_FUNC) decrypt_msg);
1573 signal_remove("message irc notice", (SIGNAL_FUNC) decrypt_notice);
1574 signal_remove("message irc action", (SIGNAL_FUNC) decrypt_action);
1575
1576 signal_remove("message own_private", (SIGNAL_FUNC) format_msg);
1577 signal_remove("message own_public", (SIGNAL_FUNC) format_msg);
1578
1579 signal_remove("channel topic changed",
1580 (SIGNAL_FUNC) decrypt_changed_topic);
1581 signal_remove("message topic", (SIGNAL_FUNC) decrypt_topic);
1582 signal_remove("server incoming", (SIGNAL_FUNC) raw_handler);
1583
1584 signal_remove("query created", (SIGNAL_FUNC) do_auto_keyx);
1585 signal_remove("query nick changed", (SIGNAL_FUNC) query_nick_changed);
1586
1587 command_unbind("topic+", (SIGNAL_FUNC) cmd_crypt_topic);
1588 command_unbind("notice+", (SIGNAL_FUNC) cmd_crypt_notice);
1589 command_unbind("notfish", (SIGNAL_FUNC) cmd_crypt_notice);
1590 command_unbind("me+", (SIGNAL_FUNC) cmd_crypt_action);
1591 command_unbind("setkey", (SIGNAL_FUNC) cmd_setkey);
1592 command_unbind("delkey", (SIGNAL_FUNC) cmd_delkey);
1593 command_unbind("key", (SIGNAL_FUNC) cmd_key);
1594 command_unbind("showkey", (SIGNAL_FUNC) cmd_key);
1595 command_unbind("keyx", (SIGNAL_FUNC) cmd_keyx);
1596 command_unbind("setinipw", (SIGNAL_FUNC) cmd_setinipw);
1597 command_unbind("unsetinipw", (SIGNAL_FUNC) cmd_unsetinipw);
1598 command_unbind("fishlogin", (SIGNAL_FUNC) cmd_fishlogin);
1599
1600 command_unbind("fishhelp", (SIGNAL_FUNC) cmd_helpfish);
1601 command_unbind("helpfish", (SIGNAL_FUNC) cmd_helpfish);
1602
1603 DH1080_DeInit();
1604
1605 if (iniUsed == 1) {
1606 free(iniKey);
1607 iniUsed = 0;
1608 }
1609 }
1610
1611 /*
1612 * Removes leading and trailing blanks from string
1613 * @param dest destination buffer
1614 * @param buffer string to clean
1615 * @param destSize size of destination buffer
1616 * @return destination buffer
1617 */
strfcpy(char * dest,char * buffer,int destSize)1618 char *strfcpy(char *dest, char *buffer, int destSize)
1619 {
1620 int i = 0, k = strlen(buffer);
1621
1622 if (k < 2)
1623 return NULL;
1624
1625 while (buffer[i] == ' ')
1626 i++;
1627 while (buffer[k - 1] == ' ')
1628 k--;
1629
1630 buffer[k] = 0;
1631
1632 strncpy(dest, buffer + i, destSize);
1633 dest[destSize - 1] = '\0';
1634 return dest;
1635 }
1636
1637 /**
1638 * Checks is the message if prefixed with the "plain_prefix" variable
1639 * @param msg message to check
1640 * @returns the string without the "plain_prefix" prefix, or NULL if not prefixed with it
1641 */
isPlainPrefix(const char * msg)1642 char *isPlainPrefix(const char *msg)
1643 {
1644 char plainPrefix[20] = "";
1645
1646 strncpy(plainPrefix, settings_get_str("plain_prefix"),
1647 sizeof(plainPrefix));
1648 if (*plainPrefix != '\0') {
1649 int i = strlen(plainPrefix);
1650 if (strncasecmp(msg, plainPrefix, i) == 0)
1651 return (char *)msg + i;
1652 }
1653
1654 return NULL;
1655 }
1656