FiSH encryption for X-Chat Python
2011-10-29 23:11:16
Post: #1
thx for the great inspiration ;)
made a redo of your work and make the switches compatible to http://fish.secure.la/xchat/FiSH-XChat.txt

require's irccrypt.py from Bjorn Edstrom ( http://www.bjrn.se/ircsrp (http://pastebin.com/ZWw3WtR6)

download fishcrypt.py http://pastebin.com/ZWGAhvix

fishcrypt.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# FiSH/Mircryption clone for X-Chat in 100% Python
#
# Requirements: PyCrypto, and Python 2.5+
#
# Copyright 2011 Nam T. Nguyen ( http://www.vithon.org/forum/Thread/show/54 )
# Released under the BSD license
#
# rewritten by trubo for AnonOps #germany trubo@hush.ai 
#
# irccrypt module is copyright 2009 Bjorn Edstrom ( http://www.bjrn.se/ircsrp )
# with modification from Nam T. Nguyen
#
# Changelog:
#   * 3.08:
#      + some docu added
#
#   * 3.07:
#      + fixed notice in channel not send to user 
#
#   * 3.06:
#	   + support for /msg /msg+ /notice /notice+ (trubo)
#
#   * 3.04:
#	   + new lock design (by target) (trubo)
#
#   * 3.01:
#	   + change switches to be compatible with fish.secure.la/xchat/FiSH-XChat.txt (trubo)
#
#	* 3.0:
#	   + rewritten to class XChatCrypt (trubo)
#
#   * 2.0:
#      + Suport network mask in /key command
#      + Alias key_exchange to keyx
#      + Support plaintext marker '+p '
#      + Support encrypted key store
#
#   * 1.0:
#      + Initial release
#
###
from __future__ import with_statement
__module_name__ = 'fishcrypt'
__module_version__ = '3.08'
__module_description__ = 'fish encryption in pure python'
import sys
import os
try:
    import xchat
except ImportError:
    print "should be run from xchat plugin with python enabled"
    sys.exit(1)
try:
    import cPickle as pickle
except ImportError:
    import pickle
## append current path
import inspect
scriptname = inspect.currentframe().f_code.co_filename.split("/")
path = "/".join(scriptname[:-1])
sys.path.insert(1,path)
try:
    import irccrypt
except ImportError:
    print "Failed to import irccrypt"
    print "Get it from here http://pastebin.com/ZWw3WtR6"
    print "Download http://pastebin.com/download.php?i=ZWw3WtR6"
ONMODES = ["Y","y","j","J","1","yes","on","ON","Yes"]
YESNO = lambda x: (x==0 and "N") or "Y"
def makedict(**kwargs):
    return kwargs
COLOR = makedict(white="\0030", black="\0031", blue="\0032", red="\0034",
    dred="\0035", purple="\0036", dyellow="\0037", yellow="\0038", bgreen="\0039",
    dgreen="\00310", green="\00311", bpurple="\00313", dgrey="\00314",
    lgrey="\00315", close="\003")
class SecretKey(object):
    def __init__(self, dh, key=None):
        self.dh = dh
        self.key = key
        self.cbc_mode = False
        self.active = True
        self.cipher = 0
class XChatCrypt:
    def __init__(self):
        print "%sFishcrypt Version %s\003" % (COLOR['dred'],__module_version__)
        self.active = True
        self.__KeyMap = {}
        self.__lockMAP = {}
        self.config = {
            'PLAINTEXTMARKER' : '+p'
        }
        self.loadDB()
    ## Load key storage
    def loadDB(self):
        data = db = None
        try:
            try:
                hnd = open(os.path.join(xchat.get_info('xchatdir'),'fish3.pickle'),'rb')
                data = hnd.read()
            except:
                return
        finally:
            try:
                hnd.close()
            except:
                pass
        if data:
            try:
                db = pickle.loads(data)
            except pickle.UnpicklingError:
                ## ignore if file is invalid
                pass
        if type(db) == dict:
            ## fill dict with the loaded Keymap
            self.__KeyMap = db.get("KeyMap",{})
    ## save keys to storage
    def saveDB(self):
        try:
            data = pickle.dumps({
                'KeyMap': self.__KeyMap, 
                'Version': __module_version__ 
            })
            hnd = open(os.path.join(xchat.get_info('xchatdir'),'fish3.pickle'),'wb')
            hnd.write(data)
        finally:
            hnd.close()
    ## incoming notice received
    def on_notice(self,word, word_eol, userdata):
        ## check if this is not allready processed
        if self.__chk_proc():
            return xchat.EAT_NONE
        ## check if DH Key Exchange
        if word_eol[3].startswith(':DH1080_FINISH'):
            return self.dh1080_finish(word, word_eol, userdata)
        elif word_eol[3].startswith(':DH1080_INIT'):
            return self.dh1080_init(word, word_eol, userdata)
        ## check for encrypted Notice
        elif word_eol[3].startswith('::+OK ') or word_eol[3].startswith('::mcps '):
            #print "DEBUG(crypt): %r %r %r" % (word, word_eol, userdata)
            
            ## rewrite data to pass to default inMessage function
            ## change full ident to nick only
            nick = self.get_nick(word[0])
            
            ## strip :: from message
            msg = word_eol[3][2:]
            
            ## set userdata to Notice
            userdata = "Notice"
            return self.inMessage([nick,msg], ["%s %s" % (nick,msg),msg], userdata)
        ## ignore everything else
        else:
            #print "DEBUG: %r %r %r" % (word, word_eol, userdata)
            return xchat.EAT_NONE
    ## local notice send messages
    def on_notice_send(self,word, word_eol, userdata):
        ## get current nick
        nick = xchat.get_context().get_info('nick')
        #print "DEBUG_notice_send: %r - %r - %r %r" % (word,word_eol,userdata,nick)
        
        ## check if this is not allready processed
        if self.__chk_proc(target=nick):
            return xchat.EAT_NONE
        
        ## get the speakers nick only from full ident
        speaker = self.get_nick(word[0])
        
        ## strip first : from notice
        message = word_eol[1][1:]
        if message.startswith('+OK ') or message.startswith('mcps '):
            ## get the key id from the speaker
            id = self.get_id(nick=speaker)
            key = self.__KeyMap.get(id,None)
            
            ## if no key available for the speaker exit
            if not key:
                return xchat.EAT_NONE
            
            ## decrypt the message
            sndmessage = self.decrypt(key,message)
            ## if decryption was possible check for invalid chars
            if sndmessage:
                try:
                    message = sndmessage.decode("UTF8").encode("UTF8")
                    ## mark nick for encrypted msgg
                    speaker = %s" % (speaker)
                except:
                    ## mark nick with a question mark
                    speaker = "?%s" % (speaker)
                    ## send original message because invalid chars
                    message = message
            ## send the message back to incoming notice but with locked target status so it will not be processed again
            self.emit_print("Notice Send",speaker,message,target=nick)
            return xchat.EAT_XCHAT
        return xchat.EAT_NONE
            
    ## incoming messages
    def inMessage(self,word, word_eol, userdata):
        ## if message is allready processed ignore
        if self.__chk_proc():
            return xchat.EAT_NONE
        speaker = word[0]
        message = word_eol[1]
        #print "DEBUG(INMsg): %r - %r - %r" % (word,word_eol,userdata)
        # if there is mode char, remove it from the message
        #if len(word_eol) >= 3:
        #    message = message[ : -(len(word_eol[2]) + 1)]
        ## check if message is crypted
        if message.startswith('+OK ') or message.startswith('mcps '):
            id = self.get_id()
            target,network = id
            key = self.__KeyMap.get(id,None)
            
            ## if no key found exit
            if not key:
                return xchat.EAT_NONE
            
            ## decrypt the message
            sndmessage = self.decrypt(key,message)
            ## if decryption was possible check for invalid chars
            if sndmessage:
                try:
                    message = sndmessage.decode("UTF8").encode("UTF8")
                    ## mark nick for encrypted msgg
                    speaker = %s" % (speaker)
                    ## send the message to local xchat
                    self.emit_print(userdata,speaker,message)
                    return xchat.EAT_XCHAT
                except:
                    ## mark nick with a question mark
                    speaker = "?%s" % (speaker)
            ## mark the message with \003, it failed to be processed and there for the \003+OK  will no longer be excepted as encrypted so it wont loop
            self.emit_print(userdata,speaker,"%s%s" % (COLOR['close'],message))
            return xchat.EAT_XCHAT
        return xchat.EAT_NONE
    def decrypt(self,key,msg):
        ## check for CBC
        if 3 <= msg.find(' *') <= 4:
            decrypt_clz = irccrypt.BlowfishCBC
            decrypt_func = irccrypt.mircryption_cbc_unpack
            ## if we receive a messge with CBC enabled we asume the partner can also except it so actiate it
            key.cbc_mode = True
        else:
            decrypt_clz = irccrypt.Blowfish
            decrypt_func = irccrypt.blowcrypt_unpack
        try:
            b = decrypt_clz(key.key)
            ret = decrypt_func(msg, b)
        except irccrypt.MalformedError:
            #print "Error Malformed"
            ret = None
        except:
            print "Decrypt ERROR"
            ret = None
        return ret
    ## mark outgoing message being  prefixed with a command like /notice /msg ...
    def outMessageCmd(self,word, word_eol, userdata):
        return self.outMessage(word, word_eol, userdata,command=True)
    ## mark outgoing message being prefixed with a command that enforces encryption like /notice+ /msg+
    def outMessageForce(self,word, word_eol, userdata):
        return self.outMessage(word, word_eol, userdata, force=True,command=True)
    ## the outgoing messages will be proccesed herre
    def outMessage(self,word, word_eol, userdata,force=False,command=False):
        
        ## check if allready processed
        if self.__chk_proc():
            return xchat.EAT_NONE
        
        ## get the id
        id = self.get_id()
        target,network = id
        ## check if message is prefixed wit a command like /msg /notice
        if command:
            ## notice and notice+
            if word[0].upper().startswith("NOTICE"):
                command = "NOTICE"
            else:
                command = "PRIVMSG"
            ## the target is first parameter after the command, not the current channel
            target = word[1]
            ## change id
            id = (target,network)
            ## remove command and target from message
            message = word_eol[2]
        else:
            command = "PRIVMSG"
            message = word_eol[0]
        sendmsg = ''
        ## try to get a key for the target id
        key = self.__KeyMap.get(id,None)
        
        ## my own nick
        nick = xchat.get_context().get_info('nick')
        #print "DEBUG(outMsg1)(%r) %r : %r %r" % (id,xchat.get_context().get_info('network'),word,nick)
        ## if we don't have a key exit
        if not key:
            return xchat.EAT_NONE
        
        ## if the key object is there but the key deleted or marked not active...and force is not set by command like /msg+ or /notice+
        if key.key == None or (key.active == False and not force):
            return xchat.EAT_NONE
        
        ## if the message is marked with the plaintextmarker (default +p) don't encrypt
        if message.startswith(self.config['PLAINTEXTMARKER']):
            ## remove the plaintextmarker from the message
            sendmsg = message[len(self.config['PLAINTEXTMARKER'])+1:]
            message = sendmsg
        else:
            ## encrypt message
            sendmsg = self.encrypt(key,message)
            ## mark the nick with ° for encrypted messages
            nick = %s" % (nick)
        #print "DEBUG(outMsg2): %r %r %r %r" % (command,message,nick,target)
        ## lock the target
        self.__lock_proc(True)
        ## send the command (PRIVMSG / NOTICE)
        xchat.command('%s %s :%s' % (command,target, sendmsg))
        ## release the lock
        self.__lock_proc(False)
        
        ## if it is no notice it must be send plaintext to xchat for you
        if command == "PRIVMSG":
            self.emit_print('Your Message',  nick, message)
        return xchat.EAT_ALL
        
    def encrypt(self,key, msg):
        if key.cbc_mode:
            encrypt_clz = irccrypt.BlowfishCBC
            encrypt_func = irccrypt.mircryption_cbc_pack
        else:
            encrypt_clz = irccrypt.Blowfish
            encrypt_func = irccrypt.blowcrypt_pack
        b = encrypt_clz(key.key)
        return encrypt_func(msg, b)
    ## send message to local xchat and lock it
    def emit_print(self,userdata,speaker,message,target=None):
        if userdata <> None:
            ## check for Highlight
            if userdata == "Channel Message" and message.find(xchat.get_info('nick')) > -1:
                userdata = "Channel Msg Hilight"
        else:
            ## if userdata is none its possible Notice
            userdata = "Notice"
        if not target:
            ## if no special target for the lock is set, make it the speaker
            target = speaker
        ## lock the processing of that message
        self.__lock_proc(True,target=target)
        ## send the message
        xchat.emit_print(userdata,speaker, message)
        ## release the lock
        self.__lock_proc(False,target=target)
    ## set or release the lock on the processing to avoid loops
    def __lock_proc(self,state,target=None):
        ctx = xchat.get_context()
        if not target:
            ## if no target set, the current channel is the target
            target = ctx.get_info('channel')
        ## the lock is NETWORK-TARGET
        id = "%s-%s" % (ctx.get_info('network'),target)
        self.__lockMAP[id] = state
    ## check if that message is allready processed to avoid loops
    def __chk_proc(self,target=None):
        ctx = xchat.get_context()
        if not target:
            ## if no target set, the current channel is the target
            target = ctx.get_info('channel')
        id = "%s-%s" % (ctx.get_info('network'),target)
        return self.__lockMAP.get(id,False)
    # get an id from channel name and networkname
    def get_id(self,nick=None):
        ctx = xchat.get_context()
        if nick:
            target = nick
        else:
            target = ctx.get_info('channel')
        ##return the id
        return (target, ctx.get_info('network').lower())
    ## return the nick only
    def get_nick(self,full):
        if full[0] == ':':
            full = full[1:]
        try:
            ret = full[:full.index('!')]
        except ValueError:
            ret  = full
        return ret
    ## manual set a key for a nick or channel
    def set_key(self,word, word_eol, userdata):
        id = self.get_id()
        target, network = id
        
        ## if more than 2 parameter the nick/channel target is set to para 1 and the key is para 2
        if len(word) > 2:
            target = word[1]
            newkey = word[2]
            id = (target,network)
        ## else the current channel/nick is taken as target and the key is para 1
        else:
            newkey = word[1]
        ## get the Keyobject if available or get a new one
        key = self.__KeyMap.get(id,SecretKey(None))
        ## set the key 
        key.key = newkey
        ## put it in the key dict
        self.__KeyMap[id] = key
        print "Key for %s on Network %s set to %r" % ( target,network,newkey)
        ## save the key storage
        self.saveDB()
        return xchat.EAT_ALL
    ## delete a key or all
    def del_key(self,word, word_eol, userdata):
        ## don't accept no parameter
        if len(word) <2:
            print "Error: /DELKEY nick|channel|* (* deletes all keys)"
            return xchat.EAT_ALL
        target = word[1]
        ## if target name is * delete all
        if target == "*":
            self.__KeyMap = {}
        else:
            id = self.get_id(nick=target)
            target,network = id
            ## try to delete the key
            try:
                del self.__KeyMap[id]
                print "Key for %s on %s deleted" % (target,network)
            except KeyError:
                print "Key %r not found" % (id,)
        ## save the keystorage
        self.saveDB()
        return xchat.EAT_ALL
    ## show either key for current chan/nick or all
    def show_key(self,word, word_eol, userdata):
        ## if no parameter show key for current chan/nick
        if len(word) <2:
            id = self.get_id()
        else:
            target = word[1]
            ## if para 1 is * show all keys and there states
            if target == "*":
                print " -------- nick/chan ------- -------- network ------- -ON- -CBC- -------------------- Key --------------------"
                for id,keys in self.__KeyMap.items():
                    print "  %-26.26s %-22.22s  %2.2s   %3.3s    %s" % (id[0],id[1],YESNO(keys.active),YESNO(keys.cbc_mode),keys.key)
                return xchat.EAT_ALL
            ## else get the id for the target
            id = self.get_id(nick=target)
        
        ## get the Key
        key = self.__KeyMap.get(id,None)
        if key:
            ## show Key for the specified chan/nick
            print "Key: %s - Active: %s - CBC: %s" % (key.key,YESNO(key.active),YESNO(key.cbc_mode))
        else:
            print "No Key found"
        return xchat.EAT_ALL
    ## start the DH1080 Key Exchange
    def key_exchange(self,word, word_eol, userdata):
        id = self.get_id()
        target,network = id
        if len(word) >1:
            target = word[1]
            id = (target,network)
        ## fixme chan notice - what should happen when keyx is send to channel trillian seems to accept it and send me a key --
        if target.startswith("#"):
            print "Channel Exchange not implemented"
            return xchat.EAT_ALL
        ## create DH 
        dh = irccrypt.DH1080Ctx()
        self.__KeyMap[id] = self.__KeyMap.get(id,SecretKey(dh))
        self.__KeyMap[id].dh = dh
        ## lock the target
        self.__lock_proc(True)
        ## send key with notice to target
        xchat.command('NOTICE %s %s' % (target, irccrypt.dh1080_pack(dh)))
        ## release the lock
        self.__lock_proc(False)
        ## save the key storage
        self.saveDB()
        return xchat.EAT_ALL
    ## Answer to KeyExchange
    def dh1080_init(self,word, word_eol, userdata):
        id = self.get_id(nick=self.get_nick(word[0]))
        target,network = id
        message = word_eol[3]
        target,network = id
        key = self.__KeyMap.get(id,SecretKey(None))
        dh = irccrypt.DH1080Ctx()
        irccrypt.dh1080_unpack(message[1 : ], dh)
        key.key = irccrypt.dh1080_secret(dh)
        ## lock the target
        self.__lock_proc(True)
        ## send key with notice to target
        xchat.command('NOTICE %s %s' % (target, irccrypt.dh1080_pack(dh)))
        ## release the lock
        self.__lock_proc(False)
        self.__KeyMap[id] = key
        print "DH1080 Init: %s on %s" % (target,network)
        print "Key set to %r" % (key.key,)
        ## save key storage
        self.saveDB()
        return xchat.EAT_ALL
    ## Answer from targets init
    def dh1080_finish(self,word, word_eol, userdata):
        id = self.get_id(nick=self.get_nick(word[0]))
        message = word_eol[3]
        target,network = id
        ## fixme if not explicit send to the Target the received key is discarded - chan exchange 
        if id not in self.__KeyMap:
            print "Invalid DH1080 Received from %s on %s" % (target,network)
            return xchat.EAT_NONE
        key = self.__KeyMap[id]
        irccrypt.dh1080_unpack(message[1 : ], key.dh)
        key.key = irccrypt.dh1080_secret(key.dh)
        print "DH1080 Finish: %s on %s" % (target,network)
        print "Key set to %r" % (key.key,)
        ## save key storage
        self.saveDB()
        return xchat.EAT_ALL
    ## set cbc mode or show the status
    def set_cbc(self,word, word_eol, userdata):
        ## check for parameter
        if len(word) >2:
            # if both specified first is target second is mode on/off
            target = word[1]
            mode = word[2]
        else:
            ## if no target defined target is current chan/nick
            target = None
            if len(word) >1:
                ## if one parameter set mode to it else show only
                mode = word[1]
        id = self.get_id(nick=target)
        target,network = id
        ## check if there is a key
        if id not in self.__KeyMap:
            print "No Key found for %r" % (target,)
        else:
            ## if no parameter show only status
            if len(word) == 1:
                print "CBC Mode is %s" % ((self.__KeyMap[id].cbc_mode and "on" or "off"),)
            else:
                ## set cbc mode to on/off
                self.__KeyMap[id].cbc_mode = bool(mode in ONMODES)
                print "set CBC Mode for %s to %s" % (target,(self.__KeyMap[id].cbc_mode == True and "on") or "off")
                ## save key storage
                self.saveDB()
        return xchat.EAT_ALL
    ## activate/deaktivate encryption für chan/nick
    def set_act(self,word, word_eol, userdata):
        ## if two parameter first is target second is mode on/off
        if len(word) >2:
            target = word[1]
            mode = word[2]
        else:
            ## target is current chan/nick 
            target = None
            if len(word) >1:
                ## if one parameter set mode to on/off
                mode = word[1]
        id = self.get_id(nick=target)
        target,network = id
        ## key not found
        if id not in self.__KeyMap:
            print "No Key found for %r" % (target,)
        else:
            if len(word) == 1:
                ## show only
                print "Encryption is %s" % ((self.__KeyMap[id].active and "on" or "off"),)
            else:
                ## set mode to on/off 
                self.__KeyMap[id].active = bool(mode in ONMODES)
                print "set Encryption for %s to %s" % (target,(self.__KeyMap[id].active == True and "on") or "off")
                ## save key storage
                self.saveDB()
        return xchat.EAT_ALL
    ## handle topic server message
    def server_332_topic(self,word, word_eol, userdata):
        ## check if allready processing
        if self.__chk_proc():
            return xchat.EAT_NONE
        server, cmd, nick, channel, topic = word[0], word[1], word[2], word[3], word_eol[4]
        ## check if topic is crypted
        if not topic.startswith('+OK ') and not topic.startswith('mcps '):
            return xchat.EAT_NONE
        id = self.get_id()
        ## look for a key
        key = self__KeyMap.get(id,SecretKey(None))
        ## if no key exit
        if not key.key:
            return xchat.EAT_NONE
        ## decrypt
        topic = self.decrypt(key, topic)
        ##todo utf8 check for illegal chars
        
        ## lock the target
        self.__lock_proc(True)
        ## send the message to xchat
        xchat.command('RECV %s %s %s %s :%s' % (server, cmd, nick, channel, topic))
        ## release the lock
        self.__lock_proc(False)
        return xchat.EAT_ALL
    ## trace nick changes
    ## fixme: when a user change nick to nick|afk and then disconnect and come back with nick. it isn't recognized
    def nick_trace(self,word, word_eol, userdata):
        old, new = word[0], word[1]
        ## create id's for old and new nick
        oldid,newid = (self.get_id(nick=old),self.get_id(nick=new))
        try:
            ## make the new nick the entry the old
            self.__KeyMap[newid] = self.__KeyMap[oldid]
            ## delete the old 
            del self.__KeyMap[oldid]
            ## save key storage
            self.saveDB()
        except KeyError:
            ## ignore error's
            pass
        return xchat.EAT_NONE
xchatcrypt = XChatCrypt()
xchat.hook_command('SETKEY', xchatcrypt.set_key, help='set a new key for a nick or channel /SETKEY <nick>/#chan [new_key]')
xchat.hook_command('KEYX', xchatcrypt.key_exchange, help='exchange a new pub key, /KEYX <nick>')
xchat.hook_command('KEY', xchatcrypt.show_key, help='list key of a nick or channel or all (*), /KEY [nick/#chan/*]' )
xchat.hook_command('DELKEY', xchatcrypt.del_key, help='remove key, /DELKEY <nick>/#chan/*')
xchat.hook_command('CBCMODE', xchatcrypt.set_cbc, help='set or shows cbc mode for (current) channel/nick , /CBCMODE [<nick>] <0|1>')
xchat.hook_command('ENCRYPT', xchatcrypt.set_act, help='set or shows encryption on for (current) channel/nick , /ENCRYPT [<nick>] <0|1>')
xchat.hook_command('', xchatcrypt.outMessage)
xchat.hook_command('MSG', xchatcrypt.outMessageCmd)
xchat.hook_command('MSG+', xchatcrypt.outMessageForce)
xchat.hook_command('NOTICE', xchatcrypt.outMessageCmd)
xchat.hook_command('NOTICE+', xchatcrypt.outMessageForce)
#xchat.hook_command('fish_load_secure', fish_load_secure, help='load fish_secure.pickle, /fish_load_secure <passphrase>')
#xchat.hook_command('fish_unload_secure', fish_unload_secure, help='dump fish_secure.pickle, /fish_unload_secure <passphrase>')
xchat.hook_server('notice', xchatcrypt.on_notice)
xchat.hook_server('332', xchatcrypt.server_332_topic)
xchat.hook_print('Notice Send',xchatcrypt.on_notice_send  )
xchat.hook_print('Channel Message', xchatcrypt.inMessage, 'Channel Message')
xchat.hook_print('Change Nick', xchatcrypt.nick_trace)
xchat.hook_print('Private Message to Dialog', xchatcrypt.inMessage, 'Private Message to Dialog')

feedback wanted

either here or on irc.anonops.com #germany
2011-11-03 02:22:20
Post: #2
Hello trubo. Thank you for your contribution!

Is there any reason why you store keys in plaintext? Is it to facilitate init-time loading of saved keys?
2011-11-03 08:13:18
Post: #3
It's just not implemented, it will come ;)

Currently I'm stuck with a windows issue. The xchat-wdk wont import the required pycrypt modules.

Ticket:
https://code.google.com/p/xchat-wdk/issues/detail?id=117
2011-11-03 18:32:12
Post: #4
ok, update ready.

/DBPASS withour parameter to set password
/DBLOAD to load the Keystorage, but its allready tries to load it on startup.

Loading the Blowfish.pyd on Windows System still not working in xchat-wdk.
Ticket open again. https://code.google.com/p/xchat-wdk/issues/detail?id=117

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# FiSH/Mircryption clone for X-Chat in 100% Python
#
# Requirements: PyCrypto, and Python 2.5+
#
# Copyright 2011 Nam T. Nguyen ( http://www.vithon.org/forum/Thread/show/54 )
# Released under the BSD license
#
# rewritten by trubo for AnonOps #germany trubo@hush.ai 
#
# irccrypt module is copyright 2009 Bjorn Edstrom ( http://www.bjrn.se/ircsrp )
# with modification from Nam T. Nguyen
#
# Changelog:
#   * 3.11
#      + add Keystorage encryption
#
#   * 3.10
#      + Fix Path for Windows and provide download URL for pycrypto
#
#   * 3.09
#      + Bugfixes
#
#   * 3.08:
#      + some docu added
#
#   * 3.07:
#      + fixed notice in channel not send to user 
#
#   * 3.06:
#	   + support for /msg /msg+ /notice /notice+ (trubo)
#
#   * 3.04:
#	   + new lock design (by target) (trubo)
#
#   * 3.01:
#	   + change switches to be compatible with fish.secure.la/xchat/FiSH-XChat.txt (trubo)
#
#	* 3.0:
#	   + rewritten to class XChatCrypt (trubo)
#
#   * 2.0:
#      + Suport network mask in /key command
#      + Alias key_exchange to keyx
#      + Support plaintext marker '+p '
#      + Support encrypted key store
#
#   * 1.0:
#      + Initial release
#
###
import sys
import os
try:
    import xchat
except ImportError:
    print "should be run from xchat plugin with python enabled"
    raise
try:
    import cPickle as pickle
except ImportError:
    import pickle
ONMODES = ["Y","y","j","J","1","yes","on","ON","Yes"]
YESNO = lambda x: (x==0 and "N") or "Y"
def makedict(**kwargs):
    return kwargs
COLOR = makedict(white="\0030", black="\0031", blue="\0032", red="\0034",
    dred="\0035", purple="\0036", dyellow="\0037", yellow="\0038", bgreen="\0039",
    dgreen="\00310", green="\00311", bpurple="\00313", dgrey="\00314",
    lgrey="\00315", close="\003")
## append current path
import inspect
## check for Windows
import platform
sep = "/"
isWindows = (platform.system() == "Windows")
if isWindows:
    sep = "\\"
scriptname = inspect.currentframe().f_code.co_filename
path = sep.join(scriptname.split(sep)[:-1])
sys.path.insert(1,path)
try:
    import irccrypt
except ImportError,e:
    if str(e).find("crypto."):
        if isWindows:
            print "For Windows you can download here"
            print "http://www.voidspace.org.uk/python/modules.shtml#pycrypto"
    else:
        print "Failed to import irccrypt"
        print "Get it from here http://pastebin.com/ZWw3WtR6"
        print "Download http://pastebin.com/download.php?i=ZWw3WtR6"
    raise
__module_name__ = 'fishcrypt'
__module_version__ = '3.11'
__module_description__ = 'fish encryption in pure python'
class SecretKey(object):
    def __init__(self, dh, key=None):
        self.dh = dh
        self.key = key
        self.cbc_mode = False
        self.active = True
        self.cipher = 0
class XChatCrypt:
    def __init__(self):
        print "%sFishcrypt Version %s\003" % (COLOR['blue'],__module_version__)
        self.active = True
        self.__KeyMap = {}
        self.__lockMAP = {}
        self.config = {
            'PLAINTEXTMARKER' : '+p',
        }
        self.status = {
            'CHKPW': None,
            'DBPASSWD' : None,
            'CRYPTDB' : False,
            'LOADED' : True
        }
        xchat.hook_command('SETKEY', self.set_key, help='set a new key for a nick or channel /SETKEY <nick>/#chan [new_key]')
        xchat.hook_command('KEYX', self.key_exchange, help='exchange a new pub key, /KEYX <nick>')
        xchat.hook_command('KEY', self.show_key, help='list key of a nick or channel or all (*), /KEY [nick/#chan/*]' )
        xchat.hook_command('DELKEY', self.del_key, help='remove key, /DELKEY <nick>/#chan/*')
        xchat.hook_command('CBCMODE', self.set_cbc, help='set or shows cbc mode for (current) channel/nick , /CBCMODE [<nick>] <0|1>')
        xchat.hook_command('ENCRYPT', self.set_act, help='set or shows encryption on for (current) channel/nick , /ENCRYPT [<nick>] <0|1>')
        ## check for password sets
        xchat.hook_command('SET',self.settings)
        xchat.hook_command('DBPASS',self.set_dbpass)
        xchat.hook_command('DBLOAD',self.set_dbload)
        xchat.hook_command('', self.outMessage)
        xchat.hook_command('MSG', self.outMessageCmd)
        xchat.hook_command('MSG+', self.outMessageForce)
        xchat.hook_command('NOTICE', self.outMessageCmd)
        xchat.hook_command('NOTICE+', self.outMessageForce)
        xchat.hook_server('notice', self.on_notice)
        xchat.hook_server('332', self.server_332_topic)
        xchat.hook_print('Notice Send',self.on_notice_send, 'Notice')
        xchat.hook_print('Change Nick', self.nick_trace)
        xchat.hook_print('Channel Message', self.inMessage, 'Channel Message')
        xchat.hook_print('Private Message to Dialog', self.inMessage, 'Private Message to Dialog')
        self.loadDB()
    ## Load key storage
    def loadDB(self):
        data = db = None
        try:
            try:
                hnd = open(os.path.join(xchat.get_info('xchatdir'),'fish3.pickle'),'rb')
                data = hnd.read()
                ## set DB loaded to False as we have a file we don't want tu create a new
                self.status['LOADED'] = False
            except:
                return
        finally:
            try:
                hnd.close()
            except:
                pass
        if data:
            try:
                db = pickle.loads(data)
                print "%sUnenrypted Key Storage loaded" % (COLOR['bpurple'],)
            except pickle.UnpicklingError:
                ## ignore if file is invalid
                if data.startswith("+OK *"):
                    self.status['CRYPTDB'] = True
                    if self.status['DBPASSWD']:
                        try:
                            algo = irccrypt.BlowfishCBC(self.status['DBPASSWD'])
                            decrypted = irccrypt.mircryption_cbc_unpack(data,algo)
                            db = pickle.loads(decrypted)
                            print "%sEnrypted Key Storage loaded" % (COLOR['green'],)
                        except pickle.UnpicklingError:
                            self.status['DBPASSWD'] = None
                            print "%sKey Storage can't be loaded with this password" % (COLOR['dred'],)
                            
                    else:
                        xchat.command('GETSTR ""  "SET fishcrypt_passload" "Enter your Key Storage Password"')
                pass
        if type(db) == dict:
            self.status['LOADED'] = True
            ## save temp keymap
            oldKeyMap = self.__KeyMap
            ## fill dict with the loaded Keymap
            self.__KeyMap = db.get("KeyMap",{})
            if oldKeyMap:
                ## update the db with the keys received before load
                self.__KeyMap.update(oldKeyMap)
            self.config.update(db.get("KeyMap",{}))
    ## save keys to storage
    def saveDB(self):
        if not self.status['LOADED']:
            print "Key Storage not loaded, no save. use /DBLOAD to load it"
            return
        try:
            data = pickle.dumps({
                'KeyMap': self.__KeyMap, 
                'Config': self.config,
                'Version': __module_version__ 
            })
            hnd = open(os.path.join(xchat.get_info('xchatdir'),'fish3.pickle'),'wb')
            if self.status['DBPASSWD']:
                algo = irccrypt.BlowfishCBC(self.status['DBPASSWD'])
                encrypted = irccrypt.mircryption_cbc_pack(data,algo)
                data = encrypted
                self.status['CRYPTDB'] = True
            else:
                self.status['CRYPTDB'] = False
            hnd.write(data)
        finally:
            hnd.close()
    def set_dbload(self,word, word_eol, userdata):
        self.loadDB()
        return xchat.EAT_ALL
    def set_dbpass(self,word, word_eol, userdata):
        xchat.command('GETSTR "" "SET fishcrypt_passpre" "New Password"')
        return xchat.EAT_ALL
    ## set keydb passwd
    def settings(self,word, word_eol, userdata):
        if len(word) < 2:
            ## not for us 
            #print "fishcrypt_pass%s%s%s: \003%r" % (COLOR['blue'],"."*16,COLOR['green'],self.status['DBPASSWD'])
            return xchat.EAT_NONE
        if word[1] == "fishcrypt_passpre":
            if len(word) == 2:
                self.status['CHKPW'] = ""
            else:
                self.status['CHKPW'] = word_eol[2]
            xchat.command('GETSTR ""  "SET fishcrypt_pass" "Repeat the Password"')
            return xchat.EAT_ALL
        if word[1] == "fishcrypt_pass":
            if len(word) == 2:
                if self.status['CHKPW'] <> "" and self.status['CHKPW'] <> None:
                    print "Passwords don't match"
                    self.status['CHKPW'] = None
                    return xchat.EAT_ALL
                self.status['DBPASSWD'] = None
                print "%sPassword removed and Key Storage decrypted" % (COLOR['dred'],)
                print "%sWarning Keys are plaintext" % (COLOR['dred'],)
            else:
                if self.status['CHKPW'] <> None and self.status['CHKPW'] <> word_eol[2]:
                    print "Passwords don't match"
                    self.status['CHKPW'] = None
                    return xchat.EAT_ALL
                self.status['DBPASSWD'] = word_eol[2]
                ## don't show the pw on console if set per GETSTR
                if self.status['CHKPW'] == None:
                    print "%sPassword for Key Storage encryption set to %r" % (COLOR['dred'],self.status['DBPASSWD'])
                else:
                    print "%sKey Storage encrypted" % (COLOR['dred'])
            self.status['CHKPW'] = None
            self.saveDB()
            return xchat.EAT_ALL
        if word[1] == "fishcrypt_passload":
            if len(word) > 2:
                self.status['DBPASSWD'] = word_eol[2]
                self.loadDB()
            else:
                print "Key Storage Not loaded"
                self.status['DBPASSWD'] = None
            return xchat.EAT_ALL
        return xchat.EAT_NONE
    ## incoming notice received
    def on_notice(self,word, word_eol, userdata):
        ## check if this is not allready processed
        if self.__chk_proc():
            return xchat.EAT_NONE
        ## check if DH Key Exchange
        if word_eol[3].startswith(':DH1080_FINISH'):
            return self.dh1080_finish(word, word_eol, userdata)
        elif word_eol[3].startswith(':DH1080_INIT'):
            return self.dh1080_init(word, word_eol, userdata)
        ## check for encrypted Notice
        elif word_eol[3].startswith('::+OK ') or word_eol[3].startswith('::mcps '):
            #print "DEBUG(crypt): %r %r %r" % (word, word_eol, userdata)
            
            ## rewrite data to pass to default inMessage function
            ## change full ident to nick only
            nick = self.get_nick(word[0])
            
            ## strip :: from message
            msg = word_eol[3][2:]
            return self.inMessage([nick,msg], ["%s %s" % (nick,msg),msg], userdata)
        ## ignore everything else
        else:
            #print "DEBUG: %r %r %r" % (word, word_eol, userdata)
            return xchat.EAT_NONE
    ## local notice send messages
    def on_notice_send(self,word, word_eol, userdata):
        ## get current nick
        nick = xchat.get_context().get_info('nick')
        #print "DEBUG_notice_send: %r - %r - %r %r" % (word,word_eol,userdata,nick)
        
        ## check if this is not allready processed
        if self.__chk_proc(target=nick):
            return xchat.EAT_NONE
        
        ## get the speakers nick only from full ident
        speaker = self.get_nick(word[0])
        
        ## strip first : from notice
        message = word_eol[1][1:]
        if message.startswith('+OK ') or message.startswith('mcps '):
            ## get the key id from the speaker
            id = self.get_id(nick=speaker)
            key = self.__KeyMap.get(id,None)
            
            ## if no key available for the speaker exit
            if not key:
                return xchat.EAT_NONE
            
            ## decrypt the message
            sndmessage = self.decrypt(key,message)
            ## if decryption was possible check for invalid chars
            if sndmessage:
                try:
                    message = sndmessage.decode("UTF8").encode("UTF8")
                    ## mark nick for encrypted msgg
                    speaker = %s" % (speaker)
                except:
                    ## mark nick with a question mark
                    speaker = "?%s" % (speaker)
                    ## send original message because invalid chars
                    message = message
            ## send the message back to incoming notice but with locked target status so it will not be processed again
            self.emit_print("Notice Send",speaker,message,target=nick)
            return xchat.EAT_XCHAT
        return xchat.EAT_NONE
            
    ## incoming messages
    def inMessage(self,word, word_eol, userdata):
        ## if message is allready processed ignore
        if self.__chk_proc():
            return xchat.EAT_NONE
        speaker = word[0]
        message = word_eol[1]
        #print "DEBUG(INMsg): %r - %r - %r" % (word,word_eol,userdata)
        # if there is mode char, remove it from the message
        #if len(word_eol) >= 3:
        #    message = message[ : -(len(word_eol[2]) + 1)]
        ## check if message is crypted
        if message.startswith('+OK ') or message.startswith('mcps '):
            id = self.get_id()
            target,network = id
            key = self.__KeyMap.get(id,None)
            
            ## if no key found exit
            if not key:
                return xchat.EAT_NONE
            
            ## decrypt the message
            sndmessage = self.decrypt(key,message)
            ## if decryption was possible check for invalid chars
            if sndmessage:
                try:
                    message = sndmessage.decode("UTF8").encode("UTF8")
                    ## mark nick for encrypted msgg
                    speaker = %s" % (speaker)
                    ## send the message to local xchat
                    self.emit_print(userdata,speaker,message)
                    return xchat.EAT_XCHAT
                except:
                    ## mark nick with a question mark
                    speaker = "?%s" % (speaker)
            ## mark the message with \003, it failed to be processed and there for the \003+OK  will no longer be excepted as encrypted so it wont loop
            self.emit_print(userdata,speaker,"%s%s" % (COLOR['close'],message))
            return xchat.EAT_XCHAT
        return xchat.EAT_NONE
    def decrypt(self,key,msg):
        ## check for CBC
        if 3 <= msg.find(' *') <= 4:
            decrypt_clz = irccrypt.BlowfishCBC
            decrypt_func = irccrypt.mircryption_cbc_unpack
            ## if we receive a messge with CBC enabled we asume the partner can also except it so actiate it
            key.cbc_mode = True
        else:
            decrypt_clz = irccrypt.Blowfish
            decrypt_func = irccrypt.blowcrypt_unpack
        try:
            b = decrypt_clz(key.key)
            ret = decrypt_func(msg, b)
        except irccrypt.MalformedError:
            #print "Error Malformed"
            ret = None
        except:
            print "Decrypt ERROR"
            ret = None
        return ret
    ## mark outgoing message being  prefixed with a command like /notice /msg ...
    def outMessageCmd(self,word, word_eol, userdata):
        return self.outMessage(word, word_eol, userdata,command=True)
    ## mark outgoing message being prefixed with a command that enforces encryption like /notice+ /msg+
    def outMessageForce(self,word, word_eol, userdata):
        return self.outMessage(word, word_eol, userdata, force=True,command=True)
    ## the outgoing messages will be proccesed herre
    def outMessage(self,word, word_eol, userdata,force=False,command=False):
        
        ## check if allready processed
        if self.__chk_proc():
            return xchat.EAT_NONE
        
        ## get the id
        id = self.get_id()
        target,network = id
        ## check if message is prefixed wit a command like /msg /notice
        if command:
            if len(word) < 3:
                print "Usage: %s <nick/channel> <message>, sends a %s.%s are a type of message that should be auto reacted to" % (word[0],word[0],word[0])
                return xchat.EAT_ALL
            ## notice and notice+
            if word[0].upper().startswith("NOTICE"):
                command = "NOTICE"
            else:
                command = "PRIVMSG"
            ## the target is first parameter after the command, not the current channel
            target = word[1]
            ## change id
            id = (target,network)
            ## remove command and target from message
            message = word_eol[2]
        else:
            command = "PRIVMSG"
            message = word_eol[0]
        sendmsg = ''
        ## try to get a key for the target id
        key = self.__KeyMap.get(id,None)
        
        ## my own nick
        nick = xchat.get_context().get_info('nick')
        #print "DEBUG(outMsg1)(%r) %r : %r %r" % (id,xchat.get_context().get_info('network'),word,nick)
        ## if we don't have a key exit
        if not key:
            return xchat.EAT_NONE
        
        ## if the key object is there but the key deleted or marked not active...and force is not set by command like /msg+ or /notice+
        if key.key == None or (key.active == False and not force):
            return xchat.EAT_NONE
        
        ## if the message is marked with the plaintextmarker (default +p) don't encrypt
        if message.startswith(self.config['PLAINTEXTMARKER']):
            ## remove the plaintextmarker from the message
            sendmsg = message[len(self.config['PLAINTEXTMARKER'])+1:]
            message = sendmsg
        else:
            ## encrypt message
            sendmsg = self.encrypt(key,message)
            ## mark the nick with ° for encrypted messages
            nick = %s" % (nick)
        #print "DEBUG(outMsg2): %r %r %r %r" % (command,message,nick,target)
        ## lock the target
        self.__lock_proc(True)
        ## send the command (PRIVMSG / NOTICE)
        xchat.command('%s %s :%s' % (command,target, sendmsg))
        ## release the lock
        self.__lock_proc(False)
        
        ## if it is no notice it must be send plaintext to xchat for you
        if command == "PRIVMSG":
            self.emit_print('Your Message',  nick, message)
        return xchat.EAT_ALL
        
    def encrypt(self,key, msg):
        if key.cbc_mode:
            encrypt_clz = irccrypt.BlowfishCBC
            encrypt_func = irccrypt.mircryption_cbc_pack
        else:
            encrypt_clz = irccrypt.Blowfish
            encrypt_func = irccrypt.blowcrypt_pack
        b = encrypt_clz(key.key)
        return encrypt_func(msg, b)
    ## send message to local xchat and lock it
    def emit_print(self,userdata,speaker,message,target=None):
        if userdata <> None:
            ## check for Highlight
            if userdata == "Channel Message" and message.find(xchat.get_info('nick')) > -1:
                userdata = "Channel Msg Hilight"
        else:
            ## if userdata is none its possible Notice
            userdata = "Notice"
        if not target:
            ## if no special target for the lock is set, make it the speaker
            target = speaker
        ## lock the processing of that message
        self.__lock_proc(True,target=target)
        ## send the message
        xchat.emit_print(userdata,speaker, message)
        ## release the lock
        self.__lock_proc(False,target=target)
    ## set or release the lock on the processing to avoid loops
    def __lock_proc(self,state,target=None):
        ctx = xchat.get_context()
        if not target:
            ## if no target set, the current channel is the target
            target = ctx.get_info('channel')
        ## the lock is NETWORK-TARGET
        id = "%s-%s" % (ctx.get_info('network'),target)
        self.__lockMAP[id] = state
    ## check if that message is allready processed to avoid loops
    def __chk_proc(self,target=None):
        ctx = xchat.get_context()
        if not target:
            ## if no target set, the current channel is the target
            target = ctx.get_info('channel')
        id = "%s-%s" % (ctx.get_info('network'),target)
        return self.__lockMAP.get(id,False)
    # get an id from channel name and networkname
    def get_id(self,nick=None):
        ctx = xchat.get_context()
        if nick:
            target = nick
        else:
            target = str(ctx.get_info('channel'))
        ##return the id
        return (target, str(ctx.get_info('network')).lower())
    ## return the nick only
    def get_nick(self,full):
        if full[0] == ':':
            full = full[1:]
        try:
            ret = full[:full.index('!')]
        except ValueError:
            ret  = full
        return ret
    ## manual set a key for a nick or channel
    def set_key(self,word, word_eol, userdata):
        id = self.get_id()
        target, network = id
        
        ## if more than 2 parameter the nick/channel target is set to para 1 and the key is para 2
        if len(word) > 2:
            target = word[1]
            newkey = word[2]
            id = (target,network)
        ## else the current channel/nick is taken as target and the key is para 1
        else:
            newkey = word[1]
        ## get the Keyobject if available or get a new one
        key = self.__KeyMap.get(id,SecretKey(None))
        ## set the key 
        key.key = newkey
        ## put it in the key dict
        self.__KeyMap[id] = key
        print "Key for %s on Network %s set to %r" % ( target,network,newkey)
        ## save the key storage
        self.saveDB()
        return xchat.EAT_ALL
    ## delete a key or all
    def del_key(self,word, word_eol, userdata):
        ## don't accept no parameter
        if len(word) <2:
            print "Error: /DELKEY nick|channel|* (* deletes all keys)"
            return xchat.EAT_ALL
        target = word[1]
        ## if target name is * delete all
        if target == "*":
            self.__KeyMap = {}
        else:
            id = self.get_id(nick=target)
            target,network = id
            ## try to delete the key
            try:
                del self.__KeyMap[id]
                print "Key for %s on %s deleted" % (target,network)
            except KeyError:
                print "Key %r not found" % (id,)
        ## save the keystorage
        self.saveDB()
        return xchat.EAT_ALL
    ## show either key for current chan/nick or all
    def show_key(self,word, word_eol, userdata):
        ## if no parameter show key for current chan/nick
        if len(word) <2:
            id = self.get_id()
        else:
            target = word[1]
            ## if para 1 is * show all keys and there states
            if target == "*":
                print " -------- nick/chan ------- -------- network ------- -ON- -CBC- -------------------- Key --------------------"
                for id,keys in self.__KeyMap.items():
                    print "  %-26.26s %-22.22s  %2.2s   %3.3s    %s" % (id[0],id[1],YESNO(keys.active),YESNO(keys.cbc_mode),keys.key)
                return xchat.EAT_ALL
            ## else get the id for the target
            id = self.get_id(nick=target)
        
        ## get the Key
        key = self.__KeyMap.get(id,None)
        if key:
            ## show Key for the specified chan/nick
            print "Key: %s - Active: %s - CBC: %s" % (key.key,YESNO(key.active),YESNO(key.cbc_mode))
        else:
            print "No Key found"
        return xchat.EAT_ALL
    ## start the DH1080 Key Exchange
    def key_exchange(self,word, word_eol, userdata):
        id = self.get_id()
        target,network = id
        if len(word) >1:
            target = word[1]
            id = (target,network)
        ## fixme chan notice - what should happen when keyx is send to channel trillian seems to accept it and send me a key --
        if target.startswith("#"):
            print "Channel Exchange not implemented"
            return xchat.EAT_ALL
        ## create DH 
        dh = irccrypt.DH1080Ctx()
        self.__KeyMap[id] = self.__KeyMap.get(id,SecretKey(dh))
        self.__KeyMap[id].dh = dh
        ## lock the target
        self.__lock_proc(True)
        ## send key with notice to target
        xchat.command('NOTICE %s %s' % (target, irccrypt.dh1080_pack(dh)))
        ## release the lock
        self.__lock_proc(False)
        ## save the key storage
        self.saveDB()
        return xchat.EAT_ALL
    ## Answer to KeyExchange
    def dh1080_init(self,word, word_eol, userdata):
        id = self.get_id(nick=self.get_nick(word[0]))
        target,network = id
        message = word_eol[3]
        target,network = id
        key = self.__KeyMap.get(id,SecretKey(None))
        dh = irccrypt.DH1080Ctx()
        irccrypt.dh1080_unpack(message[1 : ], dh)
        key.key = irccrypt.dh1080_secret(dh)
        ## lock the target
        self.__lock_proc(True)
        ## send key with notice to target
        xchat.command('NOTICE %s %s' % (target, irccrypt.dh1080_pack(dh)))
        ## release the lock
        self.__lock_proc(False)
        self.__KeyMap[id] = key
        print "DH1080 Init: %s on %s" % (target,network)
        print "Key set to %r" % (key.key,)
        ## save key storage
        self.saveDB()
        return xchat.EAT_ALL
    ## Answer from targets init
    def dh1080_finish(self,word, word_eol, userdata):
        id = self.get_id(nick=self.get_nick(word[0]))
        message = word_eol[3]
        target,network = id
        ## fixme if not explicit send to the Target the received key is discarded - chan exchange 
        if id not in self.__KeyMap:
            print "Invalid DH1080 Received from %s on %s" % (target,network)
            return xchat.EAT_NONE
        key = self.__KeyMap[id]
        irccrypt.dh1080_unpack(message[1 : ], key.dh)
        key.key = irccrypt.dh1080_secret(key.dh)
        print "DH1080 Finish: %s on %s" % (target,network)
        print "Key set to %r" % (key.key,)
        ## save key storage
        self.saveDB()
        return xchat.EAT_ALL
    ## set cbc mode or show the status
    def set_cbc(self,word, word_eol, userdata):
        ## check for parameter
        if len(word) >2:
            # if both specified first is target second is mode on/off
            target = word[1]
            mode = word[2]
        else:
            ## if no target defined target is current chan/nick
            target = None
            if len(word) >1:
                ## if one parameter set mode to it else show only
                mode = word[1]
        id = self.get_id(nick=target)
        target,network = id
        ## check if there is a key
        if id not in self.__KeyMap:
            print "No Key found for %r" % (target,)
        else:
            ## if no parameter show only status
            if len(word) == 1:
                print "CBC Mode is %s" % ((self.__KeyMap[id].cbc_mode and "on" or "off"),)
            else:
                ## set cbc mode to on/off
                self.__KeyMap[id].cbc_mode = bool(mode in ONMODES)
                print "set CBC Mode for %s to %s" % (target,(self.__KeyMap[id].cbc_mode == True and "on") or "off")
                ## save key storage
                self.saveDB()
        return xchat.EAT_ALL
    ## activate/deaktivate encryption für chan/nick
    def set_act(self,word, word_eol, userdata):
        ## if two parameter first is target second is mode on/off
        if len(word) >2:
            target = word[1]
            mode = word[2]
        else:
            ## target is current chan/nick 
            target = None
            if len(word) >1:
                ## if one parameter set mode to on/off
                mode = word[1]
        id = self.get_id(nick=target)
        target,network = id
        ## key not found
        if id not in self.__KeyMap:
            print "No Key found for %r" % (target,)
        else:
            if len(word) == 1:
                ## show only
                print "Encryption is %s" % ((self.__KeyMap[id].active and "on" or "off"),)
            else:
                ## set mode to on/off 
                self.__KeyMap[id].active = bool(mode in ONMODES)
                print "set Encryption for %s to %s" % (target,(self.__KeyMap[id].active == True and "on") or "off")
                ## save key storage
                self.saveDB()
        return xchat.EAT_ALL
    ## handle topic server message
    def server_332_topic(self,word, word_eol, userdata):
        ## check if allready processing
        if self.__chk_proc():
            return xchat.EAT_NONE
        server, cmd, nick, channel, topic = word[0], word[1], word[2], word[3], word_eol[4]
        ## check if topic is crypted
        if not topic.startswith('+OK ') and not topic.startswith('mcps '):
            return xchat.EAT_NONE
        id = self.get_id()
        ## look for a key
        key = self__KeyMap.get(id,SecretKey(None))
        ## if no key exit
        if not key.key:
            return xchat.EAT_NONE
        ## decrypt
        topic = self.decrypt(key, topic)
        ##todo utf8 check for illegal chars
        
        ## lock the target
        self.__lock_proc(True)
        ## send the message to xchat
        xchat.command('RECV %s %s %s %s :%s' % (server, cmd, nick, channel, topic))
        ## release the lock
        self.__lock_proc(False)
        return xchat.EAT_ALL
    ## trace nick changes
    ## fixme: when a user change nick to nick|afk and then disconnect and come back with nick. it isn't recognized
    def nick_trace(self,word, word_eol, userdata):
        old, new = word[0], word[1]
        ## create id's for old and new nick
        oldid,newid = (self.get_id(nick=old),self.get_id(nick=new))
        try:
            ## make the new nick the entry the old
            self.__KeyMap[newid] = self.__KeyMap[oldid]
            ## delete the old 
            del self.__KeyMap[oldid]
            ## save key storage
            self.saveDB()
        except KeyError:
            ## ignore error's
            pass
        return xchat.EAT_NONE
XChatCrypt()
2011-11-04 00:42:30
Post: #5
Awesome Trubo! This version is more interactive than my original.

Some one wanted the plugin to load encrypted saved keys on initialization. This code effectively allows that with an interactive prompt. Very cool!
2012-03-04 01:02:59
Post: #6
Updates are out.

Changelog:
# * 3.21
# + BugFix

# * 3.20
# + partly show incomplete messages

# * 3.19
# + /FISHUPDATE update switch

# * 3.18
# + AUTO CBC Mode only in querys

# * 3.17
# + Highlight Bugfix

# * 3.16
# + Highlight

# * 3.15
# + Bugfixes

# * 3.13
# + split lines if longer then 334 Chars
#
# * 3.12
# + add PROTECTKEY to block dh1080 keyexchange on known Keys ( thx ^V^ )

http://pastebin.com/ZWGAhvix
New Reply
Related threads
Chào bạn. Muốn chạy 1 file code python thì có thể chạy bằng command: c:\python27\python path\your_file.py (giả sử bạn cài python 2.7 trên ổ C) Còn muốn chạy project bạn download về thì bạn phải xem document của nó
Công ty mình đang tuyển python developer, Part-time và Full-time. Các bạn vào đây xem nhé: http://www.vietnamworks.com/python-developer-388446-jd
Em định làm một bài nho nhỏ giải quyết sudoku trong python. Trước hết là đối với thuật toán cơ bản, mong các đại ca làm một tut hướng dẫn để học hỏi các kĩ thuật cần thiết trong python luôn. Thanks trước :)
VCCorp tuyển Python Software Engineer và Python Web Developer Thông tin chi tiết tại: [url]http://www.vccorp.vn/tuyen-dung/job-163/python-developer.htm[/url] Giới thiệu tổng quan về VCCorp: [url]http://www.vccorp.vn/gioi-thieu-vc.htm[/url]
Công ty mình cần tuyển 2 vị trí senior python/django developer. Yêu cầu: + Ít nhất 1 năm kinh nghiệm với python/django + Có trách nhiệm cao trong công việc + Chăm chỉ và sáng tạo Đãi ngộ: + Các chế độ xã hội theo luật pháp việt nam + Mức lương hấp dẫn + Môi trường làm việc thoải mái, thú


Statistic

Our users have posted a total of 694 posts | We have 464 registered users

Homepage | Tutorial | Return to Top |