///***************************** // *** FiSH v0.97a for xchat *** ///***************************** #include "xchat-plugin.h" #include "FiSH.h" static xchat_plugin *ph=0; // plugin handle unsigned char default_iniKey[]="blowinikey\0ADDITIONAL SPACE FOR CUSTOM BLOW.INI PASSWORD"; unsigned char iniKey[100], iniPath[255], randomPath[255]; unsigned char g_myPrivKey[300], g_myPubKey[300]; #define DEFAULT_FORMAT "\00315\002<\002\003%s\00315\002>\002\003\t%s\n" // message #define FORMAT_MSG_SEND "\00315\002*\002\003%s\00315\002*\002\003\t%s" // *nick* message #define FORMAT_NOTICE_SEND "\00315\002]\002\003%s\00315\002[\002\003\t%s" // ]nick[ notice #define unsetiniFlag (void *)0xBEEF #ifdef WIN32 HINSTANCE g_hInstance; BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: PRNGInit(); if(GetModuleFileName(hModule, randomPath, sizeof(randomPath)) == 0) return FALSE; strcpy(strrchr(randomPath, 0x5C), "\\random.ECL"); PRNGLoad(randomPath); PRNGAddEvent(); g_hInstance=hModule; break; } return TRUE; } #endif // encrypt a message (using key for target contactName) static int FiSH_encrypt(char *msg_ptr, char *target, char *bf_dest) { unsigned char contactName[100]="", theKey[500]=""; if(msg_ptr==NULL || *msg_ptr=='\0' || target==NULL || *target=='\0' || bf_dest==NULL) return XCHAT_EAT_NONE; if(strlen(target) >= sizeof(contactName)) return XCHAT_EAT_NONE; strcpy(contactName, target); FixContactName(contactName); // replace '[' and ']' with '~' in contact name GetPrivateProfileString(contactName, "key", "", theKey, sizeof(theKey), iniPath); if(theKey[0]==0 || strlen(theKey)<4) return XCHAT_EAT_NONE; // don't process, key not found in ini if(strncmp(theKey, "+OK ", 4)==0) { // key is encrypted, lets decrypt decrypt_string((char *)iniKey, theKey+4, theKey, strlen(theKey+4)); if(*theKey=='\0') { // don't process, decrypted key is bad ZeroMemory(theKey, sizeof(theKey)); return XCHAT_EAT_NONE; } } encrypt_string(theKey, msg_ptr, bf_dest, strlen(msg_ptr)); ZeroMemory(theKey, sizeof(theKey)); bf_dest[512]=0; return 166; } // decrypt a base64 cipher text (using key for target contactName) static int FiSH_decrypt(char *msg_ptr, char *contactName) { unsigned char theKey[500]="", bf_dest[1500]="", myMark[20]=""; unsigned int msg_len, i, mark_broken_block=0; if(msg_ptr==NULL || *msg_ptr=='\0' || contactName==NULL || *contactName=='\0') return XCHAT_EAT_NONE; FixContactName(contactName); // replace '[' and ']' with '~' in contact name GetPrivateProfileString(contactName, "key", "", theKey, sizeof(theKey), iniPath); if(theKey[0]==0 || strlen(theKey)<4) return XCHAT_EAT_NONE; // don't process, key not found in ini if(strncmp(theKey, "+OK ", 4)==0) { // key is encrypted, lets decrypt decrypt_string((char *)iniKey, theKey+4, theKey, strlen(theKey+4)); if(*theKey=='\0') { // don't process, decrypted key is bad ZeroMemory(theKey, sizeof(theKey)); return XCHAT_EAT_NONE; } } // Verify base64 string msg_len=strlen(msg_ptr); if((strspn(msg_ptr, B64) != msg_len) || (msg_len < 12)) return XCHAT_EAT_NONE; // block-align blowcrypt strings if truncated by IRC server (each block is 12 chars long) // such a truncated block is destroyed and not needed anymore if(msg_len != (msg_len/12)*12) { msg_len=(msg_len/12)*12; msg_ptr[msg_len]='\0'; GetPrivateProfileString("FiSH", "mark_broken_block", " \002&\002", myMark, sizeof(myMark), iniPath); if(*myMark=='\0' || *myMark=='0' || *myMark=='n' || *myMark=='N') mark_broken_block=0; else mark_broken_block=1; } decrypt_string(theKey, msg_ptr, bf_dest, msg_len); ZeroMemory(theKey, sizeof(theKey)); if(*bf_dest=='\0') return XCHAT_EAT_NONE; // don't process, decrypted msg is bad i=0; while(bf_dest[i] != 0x0A && bf_dest[i] != 0x0D && bf_dest[i] != 0x00) i++; bf_dest[i]=0x00; // in case of wrong key, decrypted message might have control characters -> cut message // append broken-block-mark? if(mark_broken_block) strcat(bf_dest, myMark); strcpy(msg_ptr, bf_dest); // copy decrypted message back (overwriting the base64 cipher text) ZeroMemory(bf_dest, sizeof(bf_dest)); return 166; } static int decrypt_incoming(char *word[], char *word_eol[], void *userdata) { unsigned char *msg_ptr, contactName[100]="", from_nick[50], ini_incoming[20]="", msg_event[100]=""; unsigned int psylog_found=0; xchat_context *find_query_ctx; // word[1] = :fromnick!ident@host // word[2] = PRIVMSG/NOTICE/TOPIC // word[3] = target nick/#channel // word[4] = :+OK or :mcps // word[5] = BASe.64/STRinG if(word[5]==0 || word[5][0]==0) return XCHAT_EAT_NONE; if( (strcmp(word[4], ":+OK")!=0) && (strcmp(word[4], ":mcps")!=0) && (strncmp(word[1], ":-psyBNC!", 9)!=0)) return XCHAT_EAT_NONE; // prefix/psyBNC not found, don't process GetPrivateProfileString("FiSH", "process_incoming", "1", ini_incoming, sizeof(ini_incoming), iniPath); if(*ini_incoming=='0' || *ini_incoming=='n' || *ini_incoming=='N') return XCHAT_EAT_NONE; if(word[1][0] == ':') ExtractRnick(from_nick, word[1]); else from_nick[0]=0; msg_ptr = word[5]; if (word[3][0]=='#') strcpy(contactName, word[3]); else if(strcmp(from_nick, "-psyBNC")==0) { // word[8] = :(nick!ident@host.org) // word[9] = +OK or mcps // word[10] = BASe.64/STRinG if(word[10]==0) return XCHAT_EAT_NONE; if(strncmp(word[8], ":(", 2)==0) ExtractRnick(contactName, word[8]+2); else return XCHAT_EAT_NONE; if( (strcmp(word[9], "+OK")!=0) && (strcmp(word[9], "mcps")!=0)) return XCHAT_EAT_NONE; // prefix not found, don't process msg_ptr = word[10]; word[10] = 0; psylog_found=1; } else strcpy(contactName, from_nick); // decrypt the base64 cipher text (using key for target contactName) if(FiSH_decrypt(msg_ptr, contactName) == XCHAT_EAT_NONE) return XCHAT_EAT_NONE; // move message pointer to previous argument ('+OK' not needed) if(psylog_found) strcpy(strstr(word_eol[4], "+OK "), msg_ptr); else word_eol[4] = msg_ptr; // let xchat display ACTION/NOTICE/TOPIC messages (without crypt-mark ...) if (strncmp(msg_ptr, "\001ACTION ", 8)==0 || strcmp(word[2], "TOPIC")==0 || strcmp(word[2], "NOTICE")==0) return XCHAT_EAT_NONE; if(contactName[0] != '#') { find_query_ctx = xchat_find_context(ph, NULL, from_nick); if(find_query_ctx == 0) { // no query window yet, lets open one xchat_commandf(ph, "query %s", from_nick); find_query_ctx = xchat_find_context(ph, NULL, from_nick); } xchat_set_context(ph, find_query_ctx); GetPrivateProfileString("incoming_format", "crypted_privmsg", DEFAULT_FORMAT, msg_event, sizeof(msg_event), iniPath); } else GetPrivateProfileString("incoming_format", "crypted_chanmsg", DEFAULT_FORMAT, msg_event, sizeof(msg_event), iniPath); // display formatted nick and decrypted message xchat_printf(ph, msg_event, from_nick, word_eol[4]+psylog_found); return XCHAT_EAT_XCHAT; } static int encrypt_outgoing(char *word[], char *word_eol[], void *userdata) { unsigned char bf_dest[2000]="", new_msg[1000]=""; unsigned char ini_tmp_buf[20]=""; unsigned int pp; const char *contactPtr, *own_nick; if ((word_eol[1]==0) || (word_eol[1][0]==0)) return XCHAT_EAT_NONE; // don't process GetPrivateProfileString("FiSH", "process_outgoing", "1", ini_tmp_buf, sizeof(ini_tmp_buf), iniPath); if(ini_tmp_buf[0]=='0' || ini_tmp_buf[0]=='n' || ini_tmp_buf[0]=='N') return XCHAT_EAT_NONE; contactPtr = xchat_get_info(ph, "channel"); own_nick = xchat_get_info(ph, "nick"); // plain-prefix in msg found? pp=0; GetPrivateProfileString("FiSH", "plain_prefix", "+p ", ini_tmp_buf, sizeof(ini_tmp_buf), iniPath); if(ini_tmp_buf[0] != 0) { pp=strlen(ini_tmp_buf); if(strnicmp(word_eol[1], ini_tmp_buf, pp)==0) { sprintf(new_msg, "PRIVMSG %s :%s", contactPtr, word_eol[1]+pp); goto MySendMsg; } else pp=0; } // encrypt a message (using key for target) if(FiSH_encrypt(word_eol[1], (char *)contactPtr, bf_dest) == XCHAT_EAT_NONE) return XCHAT_EAT_NONE; sprintf(new_msg, "PRIVMSG %s :%s %s\n", contactPtr, "+OK", bf_dest); MySendMsg: if(contactPtr[0] != '#') { if(pp) xchat_emit_print(ph, "Private Message to Dialog", own_nick, word_eol[1]+pp, NULL, NULL); else GetPrivateProfileString("outgoing_format", "crypted_privmsg", DEFAULT_FORMAT, bf_dest, sizeof(bf_dest), iniPath); } else { if(pp) xchat_emit_print(ph, "Channel Message", own_nick, word_eol[1]+pp, NULL, NULL); else GetPrivateProfileString("outgoing_format", "crypted_chanmsg", DEFAULT_FORMAT, bf_dest, sizeof(bf_dest), iniPath); } // display formatted plain-text message in local IRC client if(!pp) xchat_printf(ph, bf_dest, own_nick, word_eol[1]+pp); ZeroMemory(bf_dest, sizeof(bf_dest)); xchat_command(ph, new_msg); // send message to IRC server return XCHAT_EAT_XCHAT; } static int decrypt_topic_332(char *word[], char *word_eol[], void *userdata) { unsigned char contactName[100]=""; unsigned char *msg_ptr, ini_incoming[20]=""; // word[1] = :irc.server.com // word[2] = 332 // word[3] = own_nick // word[4] = target #channel // word[5] = :+OK or :mcps // word[6] = BASe.64/T0PiC.STRinG if(word[6]==0 || word[6][0]==0) return XCHAT_EAT_NONE; if( (strcmp(word[5], ":+OK")!=0) && (strcmp(word[5], ":mcps")!=0) && (word[4][0] != '#')) return XCHAT_EAT_NONE; // prefix not found/not a channel, don't process GetPrivateProfileString("FiSH", "process_incoming", "1", ini_incoming, sizeof(ini_incoming), iniPath); if(*ini_incoming=='0' || *ini_incoming=='n' || *ini_incoming=='N') return XCHAT_EAT_NONE; msg_ptr = word[6]; strcpy(contactName, word[4]); // decrypt the base64 cipher text (using key for target contactName) if(FiSH_decrypt(msg_ptr, contactName) == XCHAT_EAT_NONE) return XCHAT_EAT_NONE; // move message pointer to previous argument ('+OK' not needed) word_eol[5] = msg_ptr; return XCHAT_EAT_NONE; } // copy key for old nick to use with the new one static int nick_changed(char *word[], char *word_eol[], void *userdata) { unsigned char contactName[100]="", theKey[500]="", ini_nicktracker[10]; // word[1] = :user@host.com // word[2] = NICK // word[3] = :newNick if(word[3]==0 || word[3][1]=='\0') return XCHAT_EAT_NONE; GetPrivateProfileString("FiSH", "nicktracker", "1", ini_nicktracker, sizeof(ini_nicktracker), iniPath); if( *ini_nicktracker=='0' || *ini_nicktracker=='N' || *ini_nicktracker=='n' || (ExtractRnick(contactName, word[1])==0) || (stricmp(contactName, word[3]+1)==0)) return XCHAT_EAT_NONE; // process only if a query is open if(xchat_find_context(ph, NULL, contactName)==0) return XCHAT_EAT_NONE; FixContactName(contactName); // replace '[' and ']' with '~' in contact name GetPrivateProfileString(contactName, "key", "", theKey, sizeof(theKey), iniPath); if(strlen(theKey) < 4) return XCHAT_EAT_NONE; // don't process, key not found in ini strcpy(contactName, word[3]+1); FixContactName(contactName); WritePrivateProfileString(contactName, "key", theKey, iniPath); ZeroMemory(theKey, sizeof(theKey)); return XCHAT_EAT_NONE; } // Set a custom blow.ini password, xchat syntax: /setinipw static int command_setinipw(char *word[], char *word_eol[], void *userdata) { unsigned int i=0, pw_len, re_enc=0; unsigned char B64digest[50], SHA256digest[35]; unsigned char bfKey[500], old_iniKey[100], *fptr, *ok_ptr, line_buf[1000], iniPath_new[300]; FILE *h_ini, *h_ini_new; //char *cmd = word[1]; char *iniPW = word[2]; pw_len=strlen(iniPW); if(pw_len < 7) { xchat_printf(ph, "\002FiSH:\002 Password too short, at least 7 characters needed! Usage: /setinipw "); return XCHAT_EAT_ALL; } SHA256_memory(iniPW, pw_len, SHA256digest); memset(iniPW, 0x20, pw_len); for(i=0;i<40872;i++) SHA256_memory(SHA256digest, 32, SHA256digest); htob64(SHA256digest, B64digest, 32); strcpy(old_iniKey, iniKey); if(userdata == unsetiniFlag) strcpy(iniKey, default_iniKey); // unsetinipw else strcpy(iniKey, B64digest); // this is used for encrypting blow.ini for(i=0;i<30752;i++) SHA256_memory(SHA256digest, 32, SHA256digest); htob64(SHA256digest, B64digest, 32); // this is used to verify the entered password ZeroMemory(SHA256digest, sizeof(SHA256digest)); // re-encrypt blow.ini with new password strcpy(iniPath_new, iniPath); strcat(iniPath_new, "_new"); h_ini_new=fopen(iniPath_new, "w"); h_ini=fopen(iniPath,"r"); if(h_ini && h_ini_new) { do { fptr=fgets(line_buf,sizeof(line_buf)-2,h_ini); if(fptr) { ok_ptr=strstr(line_buf, "+OK "); if(ok_ptr) { re_enc=1; strtok(ok_ptr+4," \n\r"); decrypt_string(old_iniKey, ok_ptr+4, bfKey, strlen(ok_ptr+4)); memset(ok_ptr+4, 0, strlen(ok_ptr+4)+1); encrypt_string(iniKey, bfKey, ok_ptr+4, strlen(bfKey)); strcat(line_buf, "\n"); } fprintf(h_ini_new,"%s",line_buf); } } while (!feof(h_ini)); ZeroMemory(bfKey, sizeof(bfKey)); ZeroMemory(line_buf, sizeof(line_buf)); ZeroMemory(old_iniKey, sizeof(old_iniKey)); fclose(h_ini); fclose(h_ini_new); remove(iniPath); rename(iniPath_new, iniPath); } WritePrivateProfileString("FiSH", "ini_password_Hash", B64digest, iniPath); ZeroMemory(B64digest, sizeof(B64digest)); if(re_enc) xchat_printf(ph, "\002FiSH: Re-encrypted blow.ini\002 with new password."); if(userdata != unsetiniFlag) xchat_printf(ph, "\002FiSH:\002 blow.ini password hash saved."); #ifndef WIN32 if(userdata != unsetiniFlag) { xchat_print(ph, "\002FiSH:\002 You could use \002/load /path/to/fish.so \002 next time to load FiSH and forward your blow.ini password as argument."); xchat_print(ph, "\002FiSH:\002 Or you enter your blow.ini password using \002/fishpw \002 after loading FiSH..."); } #endif return XCHAT_EAT_ALL; } // Change back to default blow.ini password, xchat syntax: /unsetinipw static int command_unsetinipw(char *word[], char *word_eol[], void *userdata) { word[2] = "Some_boogie_dummy_key"; command_setinipw(word, word_eol, unsetiniFlag); WritePrivateProfileString("FiSH", "ini_password_Hash", "0", iniPath); xchat_print(ph, "\002FiSH:\002 Changed back to default blow.ini password, you won't have to enter it on start-up anymore."); return XCHAT_EAT_ALL; } #ifndef WIN32 static int command_fishpw(char *word[], char *word_eol[], void *userdata) { unsigned int i=0, pw_len; unsigned char iniPasswordHash[50], B64digest[50], SHA256digest[35]; //char *cmd = word[1]; char *iniPW = word[2]; pw_len=strlen(iniPW); if(pw_len < 7) { xchat_printf(ph, "\002FiSH:\002 Password too short, at least 7 characters needed! Usage: /fishpw "); return XCHAT_EAT_ALL; } GetPrivateProfileString("FiSH", "ini_Password_hash", "0", iniPasswordHash, sizeof(iniPasswordHash), iniPath); if(strlen(iniPasswordHash) == 43) { SHA256_memory(iniPW, pw_len, SHA256digest); memset(iniPW, 0x20, pw_len); for(i=0;i<40872;i++) SHA256_memory(SHA256digest, 32, SHA256digest); htob64(SHA256digest, B64digest, 32); strcpy(iniKey, B64digest); // this is used for encrypting blow.ini for(i=0;i<30752;i++) SHA256_memory(SHA256digest, 32, SHA256digest); htob64(SHA256digest, B64digest, 32); // this is used to verify the entered password if(strcmp(B64digest, iniPasswordHash) != 0) { xchat_print(ph, "\002FiSH:\002 Wrong blow.ini password entered, try again...\n"); iniKey[0]=0; return XCHAT_EAT_ALL; } xchat_print(ph, "\002FiSH:\002 Correct blow.ini password entered, lets go!\n"); ZeroMemory(SHA256digest, sizeof(SHA256digest)); ZeroMemory(B64digest, sizeof(B64digest)); } else xchat_print(ph, "\002FiSH:\002 ERROR: Invalid ini_Password_hash in blow.ini found!\n"); return XCHAT_EAT_ALL; } #endif // xchat syntax: /setkey [] static int command_setkey(char *word[], char *word_eol[], void *userdata) { unsigned char contactName[100]="", encryptedKey[500]=""; //char *cmd = word[1]; char *target = word[2]; char *key = word[3]; if (*target==0 || target==0) { xchat_printf(ph, "\002FiSH:\002 No parameters. Usage: /setkey [] "); return XCHAT_EAT_ALL; } if (*key==0 || key==0) { // only one paramter given - it's the key key = target; target = (char *)xchat_get_info(ph, "channel"); if (target==NULL || stricmp(target, xchat_get_info(ph, "network"))==0) { xchat_printf(ph, "\002FiSH:\002 Please define nick/#channel. Usage: /setkey [] "); return XCHAT_EAT_ALL; } } if(strlen(target) >= sizeof(contactName)) return XCHAT_EAT_NONE; strcpy(contactName, target); FixContactName(contactName); // replace '[' and ']' with '~' in contact name strcpy(encryptedKey, key); memset(key, 0x20, strlen(key)); encrypt_key(encryptedKey); WritePrivateProfileString(contactName, "key", encryptedKey, iniPath); ZeroMemory(encryptedKey, sizeof(encryptedKey)); xchat_printf(ph, "\002FiSH:\002 Key for %s successfully set!", target); return XCHAT_EAT_ALL; } // xchat syntax: /delkey "); return XCHAT_EAT_ALL; } if(strlen(target) >= sizeof(contactName)) return XCHAT_EAT_NONE; strcpy(contactName, target); FixContactName(contactName); // replace '[' and ']' with '~' in contact name WritePrivateProfileString(contactName, "key", "0\r\n", iniPath); xchat_printf(ph, "\002FiSH:\002 Key for %s successfully removed!", target); return XCHAT_EAT_ALL; } // display key, xchat syntax: /key [] static int command_key(char *word[], char *word_eol[], void *userdata) { unsigned char contactName[100]="", theKey[500]=""; //char *cmd = word[1]; char *target = word[2]; if(*target==0 || target==0) { // no paramter given - try current window target = (char *)xchat_get_info(ph, "channel"); if (target==NULL || stricmp(target, xchat_get_info(ph, "network"))==0) { xchat_printf(ph, "\002FiSH:\002 Please define nick/#channel. Usage: /key "); return XCHAT_EAT_ALL; } } if(strlen(target) >= sizeof(contactName)) return XCHAT_EAT_NONE; strcpy(contactName, target); FixContactName(contactName); // replace '[' and ']' with '~' in contact name GetPrivateProfileString(contactName, "key", "", theKey, sizeof(theKey), iniPath); if(*theKey=='\0' || strlen(theKey)<4) { // don't process, key not found in ini xchat_printf(ph, "\002FiSH:\002 Key for %s not found!", target); return XCHAT_EAT_ALL; } if(strncmp(theKey, "+OK ", 4)==0) { // key is encrypted, lets decrypt decrypt_string((char *)iniKey, theKey+4, theKey, strlen(theKey+4)); if(*theKey=='\0') { // don't process, decrypted key is bad xchat_printf(ph, "\002FiSH:\002 Key for %s invalid!", target); return XCHAT_EAT_ALL; } } xchat_printf(ph, "\002FiSH:\002 Key for %s: %s", target, theKey); ZeroMemory(theKey, sizeof(theKey)); return XCHAT_EAT_ALL; } // set encrypted topic for current channel, xchat syntax: /topic+ static int command_crypt_TOPIC(char *word[], char *word_eol[], void *userdata) { unsigned char *target, bf_dest[2000]=""; //char *cmd = word[1]; char *topic = word_eol[2]; if (*topic==0 || topic==0) { xchat_printf(ph, "\002FiSH:\002 No parameters. Usage: /topic+ "); return XCHAT_EAT_ALL; } target = (char *)xchat_get_info(ph, "channel"); if (target==NULL || *target!='#') { xchat_printf(ph, "\002FiSH:\002 Please change to the channel window where you want to set the topic!"); return XCHAT_EAT_ALL; } // encrypt a message (using key for target) if(FiSH_encrypt(topic, target, bf_dest) == XCHAT_EAT_NONE) { xchat_printf(ph, "\002FiSH:\002 /topic+ error, no key found for %s. Usage: /topic+ ", target); return XCHAT_EAT_ALL; } xchat_commandf(ph, "TOPIC %s %s %s\n", target, "+OK", bf_dest); ZeroMemory(bf_dest, sizeof(bf_dest)); return XCHAT_EAT_ALL; } // send an encrypted NOTICE message, xchat syntax: /notice+ static int command_crypt_NOTICE(char *word[], char *word_eol[], void *userdata) { unsigned char bf_dest[2000]=""; //char *cmd = word[1]; char *target = word[2]; char *notice = word_eol[3]; if (*target==0 || target==0 || notice==0 || *notice==0) { xchat_printf(ph, "\002FiSH:\002 Bad parameters. Usage: /notice+ "); return XCHAT_EAT_ALL; } // encrypt a message (using key for target) if(FiSH_encrypt(notice, target, bf_dest) == XCHAT_EAT_NONE) { xchat_printf(ph, "\002FiSH:\002 /notice+ error, no key found for %s. Usage: /notice+ ", target); return XCHAT_EAT_ALL; } xchat_commandf(ph, "quote NOTICE %s :%s %s", target, "+OK", bf_dest); ZeroMemory(bf_dest, sizeof(bf_dest)); xchat_printf(ph, FORMAT_NOTICE_SEND, target, notice); // display notice in current window return XCHAT_EAT_ALL; } // send an encrypted message, xchat syntax: /msg+ static int command_crypt_MSG(char *word[], char *word_eol[], void *userdata) { unsigned char bf_dest[2000]=""; xchat_context *find_query_ctx; char *target = word[2]; char *message = word_eol[3]; if (*target==0 || target==0 || message==0 || *message==0) { xchat_printf(ph, "\002FiSH:\002 Bad parameters. Usage: /msg+ "); return XCHAT_EAT_ALL; } // encrypt a message (using key for target) if(FiSH_encrypt(message, target, bf_dest) == XCHAT_EAT_NONE) { xchat_printf(ph, "\002FiSH:\002 /msg+ error, no key found for %s. Usage: /msg+ ", target); return XCHAT_EAT_ALL; } xchat_commandf(ph, "PRIVMSG %s :%s %s", target, "+OK", bf_dest); find_query_ctx = xchat_find_context(ph, NULL, target); if(find_query_ctx != 0) { // open query/channel window found, display the event there xchat_set_context(ph, find_query_ctx); if(target[0] != '#') GetPrivateProfileString("outgoing_format", "crypted_privmsg", DEFAULT_FORMAT, bf_dest, sizeof(bf_dest), iniPath); else GetPrivateProfileString("outgoing_format", "crypted_chanmsg", DEFAULT_FORMAT, bf_dest, sizeof(bf_dest), iniPath); xchat_printf(ph, bf_dest, xchat_get_info(ph, "nick"), message); } else xchat_printf(ph, FORMAT_MSG_SEND, target, message); // no open window found, display in current window ZeroMemory(bf_dest, sizeof(bf_dest)); return XCHAT_EAT_ALL; } // Start a DH1080 key-exchange, xchat syntax: /keyx [] static int command_keyx(char *word[], char *word_eol[], void *userdata) { //char *cmd = word[1]; char *target = word[2]; xchat_context *find_query_ctx; if (*target==0 || target==0) { // no paramter given - try current window target = (char *)xchat_get_info(ph, "channel"); if (target==NULL || stricmp(target, xchat_get_info(ph, "network"))==0) { xchat_printf(ph, "\002FiSH:\002 Please define target nick. Usage: /keyx "); return XCHAT_EAT_ALL; } } if(target[0]=='#') { xchat_printf(ph, "\002FiSH:\002 KeyXchange does not work for channels!"); return XCHAT_EAT_ALL; } DH1080_gen(g_myPrivKey, g_myPubKey); xchat_commandf(ph, "quote NOTICE %s :%s %s", target, "DH1080_INIT", g_myPubKey); // send DH1080 init to target // if a query is already open, display info text there find_query_ctx = xchat_find_context(ph, NULL, target); if(find_query_ctx != 0) xchat_set_context(ph, find_query_ctx); xchat_printf(ph, "\002FiSH:\002 Sent my DH1080 public key to %s, waiting for reply ...", target); return XCHAT_EAT_ALL; } // DH1080 notice handling static int notice_received(char *word[], char *word_eol[], void *userdata) { unsigned int i; unsigned char hisPubKey[300], contactName[25]="", from_nick[25]=""; xchat_context *find_query_ctx; //word[1]; // :nick!ident@host.com //char *cmd = word[2]; //const char *target = word[3]; // target nick or #channel char *DH_msg = word[4]; char *DH_pubkey = word[5]; if( word[5]==NULL || *word[5]=='\0' || *word[4]=='\0' || *word[3]=='\0' || *word[1]=='\0') return XCHAT_EAT_NONE; if(strcmp(word[4], ":+OK")==0 || strcmp(word[4], ":mcps")==0) // encrypted notice message? return decrypt_incoming(word, word_eol, userdata); if(ExtractRnick(from_nick, word[1])==0) return XCHAT_EAT_NONE; i=strlen(DH_pubkey); if(i<179 || i>181) return XCHAT_EAT_NONE; // if a query is already open, display info text there find_query_ctx = xchat_find_context(ph, NULL, from_nick); if(strncmp(DH_msg, ":DH1080_INIT", 12)==0) { if(find_query_ctx != 0) xchat_set_context(ph, find_query_ctx); xchat_printf(ph, "\002FiSH:\002 Received DH1080 public key from %s, sending mine...", from_nick); DH1080_gen(g_myPrivKey, g_myPubKey); xchat_commandf(ph, "quote NOTICE %s :%s %s", from_nick, "DH1080_FINISH", g_myPubKey); // send DH1080_FINISH (own pubkey) to from_nick } else if(strncmp(DH_msg, ":DH1080_FINISH", 14)!=0) return XCHAT_EAT_NONE; strcpy(hisPubKey, DH_pubkey); if(DH1080_comp(g_myPrivKey, hisPubKey)==0) return XCHAT_EAT_NONE; strcpy(contactName, from_nick); FixContactName(contactName); // replace '[' and ']' with '~' in contact name encrypt_key(hisPubKey); WritePrivateProfileString(contactName, "key", hisPubKey, iniPath); ZeroMemory(hisPubKey, sizeof(hisPubKey)); if(find_query_ctx != 0) xchat_set_context(ph, find_query_ctx); xchat_printf(ph, "\002FiSH:\002 Key for %s successfully set!", from_nick); return XCHAT_EAT_ALL; } int xchat_plugin_init(xchat_plugin *plugin_handle, char **plugin_name, char **plugin_desc, char **plugin_version, char *arg) { unsigned char iniPasswordHash[50], SHA256digest[35], B64digest[50]; unsigned int i; if (ph != 0) { xchat_print (ph, "\002FiSH is already loaded!\002\n"); return FALSE; } // we need to save this for use with any xchat_* functions ph = plugin_handle; *plugin_name = "FiSH"; *plugin_desc = "Blowfish IRC encryption, including secure Diffie-Hellman 1080 bit key-exchange"; *plugin_version = "0.97a"; initb64(); mip=mirsys(500, 16); if(mip==NULL) return FALSE; strcpy(iniPath, xchat_get_info(ph, "xchatdir")); // path to xchat config file #ifdef WIN32 strcat(iniPath, "\\blow.ini"); PRNGAddEvent(); #else strcpy(randomPath, iniPath); strcat(randomPath, "/random.ECL"); strcat(iniPath, "/blow.ini"); #endif GetPrivateProfileString("FiSH", "ini_Password_hash", "0", iniPasswordHash, sizeof(iniPasswordHash), iniPath); if(strlen(iniPasswordHash) == 43) { #ifdef WIN32 if(DialogBox(g_hInstance, MAKEINTRESOURCE(IDD_PASS), NULL, (DLGPROC)PassFunc)==0) { xchat_print(ph, "\002FiSH:\002 No blow.ini password entered, try again...\n"); xchat_print(ph, "\002FiSH not loaded.\002\n"); return 0; } #else if(arg==0 || *arg==0) { xchat_print(ph, "\002FiSH:\002 Please enter your blow.ini password using \002/fishpw \002\n"); xchat_print(ph, "\002FiSH:\002 You could also use \002/load /path/to/fish.so \002 to load FiSH and forward your blow.ini password as argument.\n"); iniKey[0]=0; } else strcpy(iniKey, arg); // blow.ini password forwarded as argument #endif if(*iniKey) { SHA256_memory(iniKey, strlen(iniKey), SHA256digest); for(i=0;i<40872;i++) SHA256_memory(SHA256digest, 32, SHA256digest); htob64(SHA256digest, B64digest, 32); strcpy(iniKey, B64digest); // this is used for encrypting blow.ini for(i=0;i<30752;i++) SHA256_memory(SHA256digest, 32, SHA256digest); htob64(SHA256digest, B64digest, 32); // this is used to verify the entered password if(strcmp(B64digest, iniPasswordHash) != 0) { xchat_print(ph, "\002FiSH:\002 Wrong blow.ini password entered, try again...\n"); xchat_print(ph, "\002FiSH not loaded.\002\n"); return 0; } xchat_print(ph, "\002FiSH:\002 Correct blow.ini password entered, lets go!\n"); } } else { strcpy(iniKey, default_iniKey); xchat_print(ph, "\002FiSH:\002 Using default password to decrypt blow.ini... Try /setinipw to set a custom password.\n"); } xchat_hook_server(ph, "PRIVMSG", XCHAT_PRI_NORM, decrypt_incoming, 0); xchat_hook_server(ph, "NOTICE", XCHAT_PRI_NORM, notice_received, 0); xchat_hook_server(ph, "TOPIC", XCHAT_PRI_NORM, decrypt_incoming, 0); xchat_hook_server(ph, "NICK", XCHAT_PRI_NORM, nick_changed, 0); xchat_hook_server(ph, "332", XCHAT_PRI_NORM, decrypt_topic_332, 0); xchat_hook_command(ph, "", XCHAT_PRI_NORM, encrypt_outgoing, 0, 0); xchat_hook_command(ph, "setkey", XCHAT_PRI_NORM, command_setkey, "Set key for target to sekure_key. If no target specified, the key for current window will be set to sekure_key. Usage: /setkey [] ", NULL); xchat_hook_command(ph, "delkey", XCHAT_PRI_NORM, command_delkey, "Delete key for target. You have to specify the target. Usage: /delkey ", 0); xchat_hook_command(ph, "key", XCHAT_PRI_NORM, command_key, "Show key for target. If no target specified, the key for current window will be shown.\nUsage: /key []", 0); xchat_hook_command(ph, "keyx", XCHAT_PRI_NORM, command_keyx, "Perform DH1080 KeyXchange with target. If no target specified, the KeyXchange takes place with the current query window. Usage: /keyx []", 0); xchat_hook_command(ph, "setinipw", XCHAT_PRI_NORM, command_setinipw, "Set a custom password to encrypt your key-container (blow.ini) - you will be forced to enter it each time you load the module.\nUsage: /setinipw ", 0); xchat_hook_command(ph, "unsetinipw", XCHAT_PRI_NORM, command_unsetinipw, "Change back to default blow.ini password. Usage: /unsetinipw", 0); xchat_hook_command(ph, "topic+", XCHAT_PRI_NORM, command_crypt_TOPIC, "Set a new encrypted topic for the current channel. Usage: /topic+ ", 0); xchat_hook_command(ph, "notice+", XCHAT_PRI_NORM, command_crypt_NOTICE, "Send an encrypted notice. Usage: /notice+ ", 0); xchat_hook_command(ph, "msg+", XCHAT_PRI_NORM, command_crypt_MSG, "Send an encrypted message. Usage: /msg+ ", 0); #ifndef WIN32 // only needed if blow.ini password was not forwarded using /load command if(iniKey[0]==0) xchat_hook_command(ph, "fishpw", XCHAT_PRI_NORM, command_fishpw, "Set FiSH password to decrypt your key-container (blow.ini).\nUsage: /fishpw ", 0); #endif xchat_print(ph, "\002FiSH v0.97a\002 - encryption plugin for xchat \002loaded!\002\n"); return 1; // return 1 for success } int xchat_plugin_deinit(xchat_plugin *plugin_handle) { xchat_set_context(ph, xchat_find_context(ph, NULL, NULL)); // set context to current window xchat_print(ph, "\002FiSH unloaded.\002\n"); ph=0; if(mip) mirexit(); #ifdef WIN32 PRNGSave(randomPath); PRNGCleanup(); #endif return 1; } // replace '[' and ']' from nick/channel with '~' (else problems with .ini files) void FixContactName(char *contactName) { while(*contactName != 0) { if((*contactName == '[') || (*contactName == ']')) *contactName='~'; contactName++; } } // :somenick!ident@host.net PRIVMSG leetguy :Some Text -> Result: Rnick="somenick" int ExtractRnick(char *Rnick, char *incoming_msg) // needs pointer to "nick@host" or ":nick@host" { int k=0; if(*incoming_msg == ':') incoming_msg++; while(*incoming_msg!='!' && *incoming_msg!=0) { Rnick[k]=*incoming_msg; incoming_msg++; k++; } Rnick[k]=0; if (*Rnick < '0') return FALSE; else return TRUE; } void memXOR(unsigned char *s1, const unsigned char *s2, int n) { while(n--) *s1++ ^= *s2++; } #ifdef WIN32 BOOL CALLBACK PassFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) { HICON hicon; switch (msg) { case WM_INITDIALOG: SetFocus(GetDlgItem(hwndDlg, IDC_PASS)); hicon=LoadIcon(g_hInstance, MAKEINTRESOURCE(IDI_KEY)); SendMessage(hwndDlg, WM_SETICON, ICON_BIG, (long)hicon); SendDlgItemMessage(hwndDlg, IDC_PASS, EM_LIMITTEXT, 99, 0); // Allow only 99 chars for IDC_PASS editbox break; case WM_COMMAND: if(LOWORD(wParam)==IDOK) { PRNGAddEvent(); iniKey[0]=0; if(GetDlgItemText(hwndDlg, IDC_PASS, iniKey, 99)==0) { EndDialog(hwndDlg, 0); // quit password dialog and return FALSE (error) return 0; } EndDialog(hwndDlg, 1); // quit password dialog and return TRUE (success) break; } return TRUE; case WM_DESTROY: case WM_CLOSE: EndDialog(hwndDlg, 0); return TRUE; } return FALSE; } #endif