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