trubo

fishcrypt.py

Oct 29th, 2011
2,289
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 72.04 KB | None | 0 0
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. #
  4. # FiSH/Mircryption clone for X-Chat in 100% Python
  5. #
  6. # Requirements: PyCrypto, and Python 2.5+
  7. #
  8. # Copyright 2011 Nam T. Nguyen ( http://www.vithon.org/forum/Thread/show/54 )
  9. # Released under the BSD license
  10. #
  11. # rewritten by trubo/segfault for irc.prooops.eu #py-fishcrypt trubo00@gmail.com
  12. #
  13. # irccrypt module is copyright 2009 Bjorn Edstrom ( http://www.bjrn.se/ircsrp )
  14. # with modification from Nam T. Nguyen and trubo
  15. #
  16. # Changelog:
  17. #   * 4.21
  18. #      + Fixed Empty Action /me
  19.  
  20. #   * 4.20
  21. #      + Added support for Stealth mode >> no KeyExchange possible [/SET FISHSTEALTH True/False]
  22.  
  23. #   * 4.19
  24. #      + Added support for mIRC CBC KeyExchange, https://github.com/flakes/mirc_fish_10/
  25.  
  26. #   * 4.18
  27. #      + Buffix Topic use key from channel not context
  28.  
  29. #   * 4.17
  30. #      + CBC Default
  31.  
  32. #   * 4.16
  33. #      + Bugfix Topic
  34. #      + config plaintextmarker in keyprotection
  35. #      + config parameter DEFAULTPROTECT and DEFAULTCBC
  36.  
  37. #   * 4.15
  38. #      + Destroy object
  39.  
  40. #   * 4.14
  41. #      + Stable
  42.  
  43. #   * 4.13
  44. #      + new NickTrace
  45. #      + wildcard /KEY search
  46. #      + msg send to other target are marked with "Message Send"
  47. #      + Tab Completion for udpate command
  48. #      + using strxor from the pyCrypto packages if available
  49. #      + some performance enhancements
  50. #      + Pseudo Threading for Windows
  51.  
  52. #   * 4.12
  53. #      + Beta Support
  54.  
  55. #   * 4.11
  56. #      + BugFix /UPDATE
  57.  
  58. #   * 4.10
  59. #      + BugFix /FISHSETUP
  60.  
  61. #   * 4.09
  62. #      + BugFix again /FISHSETUP /UPDATE
  63.  
  64. #   * 4.08
  65. #      + BugFix settings are not saved
  66.  
  67. #   * 4.07
  68. #      + new Update function
  69.  
  70. #   * 4.06
  71. #      + Small BugFixes
  72.  
  73. #   * 4.05
  74. #      + BugFix Windows has no full xchatdir now using scriptpath for fish3.pickle
  75.  
  76. #   * 4.04
  77. #      + BugFix notices
  78.  
  79. #   * 4.03
  80. #      + BugFix /FISHSETUP
  81.  
  82. #   * 4.02
  83. #      + noproxy oprions for /FISHSETUP
  84.  
  85. #   * 4.01
  86. #      + BugFix pyBlowfish
  87.  
  88. #   * 4.00
  89. #      + Windows Support with pyBlowfish.py and irccrypt now included
  90.  
  91. #   * 3.31
  92. #      + BugFix unpack large messages
  93.  
  94. #   * 3.30
  95. #      + Added chksum for irccrypt with __module_name__ tags http://pastebin.com/vTrWyBKv
  96.  
  97. #   * 3.29
  98. #      + BugFix Update and Threaded Update
  99.  
  100. #   * 3.28
  101. #      + /SET [fishcrypt]
  102.  
  103. #   * 3.27
  104. #      + BugFix /ME+ in Query
  105.  
  106. #   * 3.26
  107. #      + Updates over Proxy
  108.  
  109. #   * 3.25
  110. #      + crypted /ME+
  111.  
  112. #   * 3.24
  113. #      + BugFix topic 332
  114.  
  115. #   * 3.23
  116. #      + BugFix notice send
  117.  
  118. #   * 3.22
  119. #      + BugFix
  120.  
  121. #   * 3.21
  122. #      + BugFix
  123.  
  124. #   * 3.20
  125. #      + partly show incomplete messages
  126.  
  127. #   * 3.19
  128. #      + /FISHUPDATE update switch
  129.  
  130. #   * 3.18
  131. #      + AUTO CBC Mode only in querys
  132.  
  133. #   * 3.17
  134. #      + Highlight Bugfix
  135.  
  136. #   * 3.16
  137. #      + Highlight
  138.  
  139. #   * 3.15
  140. #      + Bugfixes
  141.  
  142. #   * 3.13
  143. #      + split lines if longer then 334 Chars
  144. #
  145. #   * 3.12
  146. #      + add PROTECTKEY to block dh1080 keyexchange on known Keys ( thx ^V^ )
  147. #
  148. #   * 3.11
  149. #      + add Keystorage encryption
  150. #to
  151. #   * 3.10
  152. #      + Fix Path for Windows and provide download URL for pycrypto
  153. #
  154. #   * 3.09
  155. #      + Bugfixes
  156. #
  157. #   * 3.08:
  158. #      + some docu added
  159. #
  160. #   * 3.07:
  161. #      + fixed notice in channel not send to user
  162. #
  163. #   * 3.06:
  164. #      + support for /msg /msg+ /notice /notice+ (trubo)
  165. #
  166. #   * 3.04:
  167. #      + new lock design (by target) (trubo)
  168. #
  169. #   * 3.01:
  170. #      + change switches to be compatible with fish.secure.la/xchat/FiSH-XChat.txt (trubo)
  171. #
  172. #   * 3.0:
  173. #      + rewritten to class XChatCrypt (trubo)
  174. #
  175. #   * 2.0:
  176. #      + Suport network mask in /key command
  177. #      + Alias key_exchange to keyx
  178. #      + Support plaintext marker '+p '
  179. #      + Support encrypted key store
  180. #
  181. #   * 1.0:
  182. #      + Initial release
  183. #
  184. ###
  185.  
  186.  
  187. __module_name__ = 'fishcrypt'
  188. __module_version__ = '4.21'
  189. __module_description__ = 'fish encryption in pure python'
  190.  
  191. ISBETA = ""
  192.  
  193. UPDATEURL = 'http://pastebin.com/raw.php?i=ZWGAhvix'
  194. BETAUPDATEURL = 'http://pastebin.com/raw.php?i=MFUhcYA2'
  195. PYBLOWFISHURL = "http://pastebin.com/raw.php?i=nkExr9zu"
  196. SOCKSIPYURL = 'http://socksipy-branch.googlecode.com/svn/trunk/socks.py'
  197.  
  198. ONMODES = ["Y","y","j","J","1","yes","on","ON","Yes","True","true"]
  199. YESNO = lambda x: (x==0 and "N") or "Y"
  200.  
  201. import sys
  202. import os
  203. import re
  204. import base64
  205. import hashlib
  206. import struct
  207. import time
  208.  
  209. from math import log
  210.  
  211. try:
  212.     import xchat
  213. except ImportError:
  214.     sys.exit("should be run from xchat plugin with python enabled")
  215.  
  216. try:
  217.     import cPickle as pickle
  218. except ImportError:
  219.     import pickle
  220.  
  221. ## check for Windows
  222. import platform
  223. sep = "/"
  224. isWindows = (platform.system() == "Windows")
  225. if isWindows:
  226.     sep = "\\"
  227.  
  228. ## append current path
  229. import inspect
  230. scriptname = inspect.currentframe().f_code.co_filename
  231. script = "".join(scriptname.split(sep)[-1:])
  232. path = sep.join(scriptname.split(sep)[:-1])
  233. sys.path.insert(1,path)
  234.  
  235. SCRIPTCHKSUM = hashlib.sha1(open(scriptname,'rb').read()).hexdigest()
  236. REQUIRESETUP = False
  237.  
  238. try:
  239.     import Crypto.Cipher.Blowfish as cBlowfish
  240. except ImportError:
  241.     try:
  242.         import pyBlowfish as cBlowfish
  243.         pyBlowfishlocation = "%s.py" % str(cBlowfish)[str(cBlowfish).find("from '")+6:str(cBlowfish).find(".py")]
  244.         chksum = hashlib.sha1(open(pyBlowfishlocation,'rb').read()).hexdigest()
  245.         validVersion = {'35c1b6cd5af14add86dc0cf3f0309a185c308dcd':0.4,'877ae9de309685c975a6d120760c1ff9b4c55719':0.5, '57117e7c9c7649bf490589b7ae06a140e82664c6':0.5}.get(chksum,-1)
  246.         if validVersion == -1:
  247.             print "\0034** Loaded pyBlowfish.py with checksum: %s is untrusted" % (chksum)
  248.         else:
  249.             if validVersion < 0.5:
  250.                 print "\0034** Loaded pyBlowfish.py (%.1f) with checksum: %s is too old" % (validVersion,chksum)
  251.                 REQUIRESETUP = True
  252.             else:
  253.                 print "\0033** Loaded pyBlowfish.py Version %.1f with checksum: %s" % (validVersion,chksum)
  254.  
  255.     except ImportError:
  256.         import platform
  257.         print "\002\0034No Blowfish implementation"
  258.         if not isWindows:
  259.             print "This module requires PyCrypto / The Python Cryptographic Toolkit."
  260.             print "Get it from http://www.dlitz.net/software/pycrypto/. or"
  261.         else:
  262.             path = path.replace(sep,sep*2)
  263.         print "Download Python only Blowfish at %s" % PYBLOWFISHURL
  264.         print "or type \002/FISHSETUP\002 for automatic install of that"
  265.  
  266.         REQUIRESETUP = True
  267.  
  268. try:
  269.     from Crypto.Util.strxor import strxor as xorstring
  270. except ImportError:
  271.     ## use slower python only xor
  272.     def xorstring(a, b): # Slow.
  273.         """xor string a and b, both of length blocksize."""
  274.         xored = []
  275.         for i in xrange(8):
  276.             xored.append( chr(ord(a[i]) ^ ord(b[i])) )
  277.         return "".join(xored)
  278.  
  279. if not isWindows:
  280.     from threading import Thread
  281. else:
  282.     class Thread:
  283.         def __init__(self,target=None,args=[],kwargs={},name='Thread*'):
  284.             self.__target = target
  285.             self.__args = args
  286.             self.__kwargs = kwargs
  287.             self.__name = name
  288.             self.__hook = None
  289.         def start(self):
  290.             print "-Starting Pseudo Thread"
  291.             self.__hook = xchat.hook_timer(1,self.__thread,(self.__target,self.__args,self.__kwargs))
  292.         def __thread(self,userdata):
  293.             try:
  294.                 _thread,args,kwargs = userdata
  295.                 _thread(*args,**kwargs)
  296.             finally:
  297.                 xchat.unhook(self.__hook)
  298.                 self.__hook = None
  299.                 return False
  300.  
  301. import socket
  302. REALSOCKET = socket.socket
  303.  
  304. def makedict(**kwargs):
  305.     return kwargs
  306.  
  307. COLOR = makedict(white="\0030", black="\0031", blue="\0032", red="\0034",
  308.     dred="\0035", purple="\0036", dyellow="\0037", yellow="\0038", bgreen="\0039",
  309.     dgreen="\00310", green="\00311", bpurple="\00313", dgrey="\00314",
  310.     lgrey="\00315", close="\003")
  311.  
  312.  
  313. class SecretKey(object):
  314.     def __init__(self, dh, key=None,protectmode=False,cbcmode=False):
  315.         self.dh = dh
  316.         self.key = key
  317.         self.cbc_mode = cbcmode
  318.         self.protect_mode = protectmode
  319.         self.active = True
  320.         self.cipher = 0
  321.         self.keyname = (None,None)
  322.     def __str__(self):
  323.         return "%s@%s" % self.keyname
  324.  
  325. def proxyload(_thread,_useproxy,doExtra):
  326.     socket.socket = REALSOCKET
  327.     if xchat.get_prefs('net_proxy_type') > 0 and _useproxy:
  328.         try:
  329.             import socks
  330.         except ImportError:
  331.             print "\0034python-socksipy not installed"
  332.             print "sudo apt-get install python-socksipy"
  333.             print "or install %s" % SOCKSIPYURL
  334.             print "or just use the noproxy option with /FISHUPDATE and /FISHSETUP"
  335.             return xchat.EAT_ALL
  336.  
  337.         proxytype = [0,-1,socks.PROXY_TYPE_SOCKS4,socks.PROXY_TYPE_SOCKS5,socks.PROXY_TYPE_HTTP,-1][xchat.get_prefs('net_proxy_type')]
  338.         nameproxytype = ['','Socks4a','Socks5','HTTP','']
  339.         if proxytype < 0:
  340.             print "\0034Proxytype not suported for updates"
  341.             return xchat.EAT_ALL
  342.         proxyuser = xchat.get_prefs('net_proxy_user')
  343.         proxypass = xchat.get_prefs('net_proxy_pass')
  344.         if len(proxyuser) < 1 or len(proxypass) < 1:
  345.             proyxuser = proxypass = None
  346.         socks.setdefaultproxy(proxytype,xchat.get_prefs('net_proxy_host'),xchat.get_prefs('net_proxy_port'),rdns=True,username=proxyuser,password=proxypass)
  347.         print "\00310using xchat proxy settings \0037Type: %s Host: %s Port: %s" % (nameproxytype[proxytype],xchat.get_prefs('net_proxy_host'),xchat.get_prefs('net_proxy_port'))
  348.        
  349.         ## Replace default socket
  350.         socket.socket = socks.socksocket
  351.                    
  352.     import urllib2
  353.     _thread(urllib2,doExtra)
  354.  
  355. def destroyObject(userdata):
  356.     global loadObj
  357.     del loadObj
  358.     return False
  359.  
  360. class XChatCrypt:
  361.     def __init__(self):
  362.         print "%sFishcrypt Version %s %s\003" % (COLOR['blue'],__module_version__,ISBETA)
  363.         print "SHA1 checksum: %r" % SCRIPTCHKSUM
  364.         self.active = True
  365.         self.__KeyMap = {}
  366.         self.__TargetMap = {}
  367.         self.__lockMAP = {}
  368.         self.config = {
  369.             'PLAINTEXTMARKER' : '+p',
  370.             'DEFAULTCBC' : True,
  371.             'DEFAULTPROTECT' : False,
  372.             'FISHUPDATETIMEOUT' : 30,
  373.             'MAXMESSAGELENGTH' : 300,
  374.             'USEPROXYUPDATE' : True,
  375.             'FISHBETAVERSION': True,
  376.             'FISHDEVELOPDEBUG': False,
  377.             'AUTOBACKUP': True,
  378.             'FISHSTEALTH': False,
  379.         }
  380.         self.status = {
  381.             'CHKPW': None,
  382.             'DBPASSWD' : None,
  383.             'CRYPTDB' : False,
  384.             'LOADED' : True
  385.         }
  386.         self.__update_thread = None
  387.         self._updatedSource = None
  388.         self.__hooks = []
  389.         self.__hooks.append(xchat.hook_command('SETKEY', self.set_key, help='set a new key for a nick or channel /SETKEY <nick>/#chan [new_key]'))
  390.         self.__hooks.append(xchat.hook_command('KEYX', self.key_exchange, help='exchange a new pub key, /KEYX <nick>'))
  391.         self.__hooks.append(xchat.hook_command('KEY', self.show_key, help='list key of a nick or channel or all (*), /KEY [nick/#chan/*]' ))
  392.         self.__hooks.append(xchat.hook_command('DELKEY', self.del_key, help='remove key, /DELKEY <nick>/#chan/*'))
  393.         self.__hooks.append(xchat.hook_command('CBCMODE', self.set_cbc, help='set or shows cbc mode for (current) channel/nick , /CBCMODE [<nick>] <0|1>'))
  394.         self.__hooks.append(xchat.hook_command('PROTECTKEY', self.set_protect, help='sets or shows key protection mode for (current) nick, /PROTECTKEY [<nick>] <0|1>'))
  395.         self.__hooks.append(xchat.hook_command('ENCRYPT', self.set_act, help='set or shows encryption on for (current) channel/nick , /ENCRYPT [<nick>] <0|1>'))
  396.        
  397.         self.__hooks.append(xchat.hook_command('PRNCRYPT', self.prn_crypt, help='print msg encrpyted localy , /PRNCRYPT <msg>'))
  398.         self.__hooks.append(xchat.hook_command('PRNDECRYPT', self.prn_decrypt, help='print msg decrpyted localy , /PRNDECRYPT <msg>'))
  399.  
  400.         self.__hooks.append(xchat.hook_command('UPDATE', self.update, help='Update this Script'))
  401.         self.__hooks.append(xchat.hook_command('FISHUPDATE', self.fishupdate, help='Update this Script'))
  402.  
  403.         ## check for password sets
  404.         self.__hooks.append(xchat.hook_command('SET',self.settings))
  405.         self.__hooks.append(xchat.hook_command('DBPASS',self.set_dbpass))
  406.         self.__hooks.append(xchat.hook_command('DBLOAD',self.set_dbload))
  407.  
  408.         self.__hooks.append(xchat.hook_command('HELP',self.get_help))
  409.  
  410.         self.__hooks.append(xchat.hook_command('', self.outMessage))
  411.         self.__hooks.append(xchat.hook_command('ME+', self.outMessageCmd))
  412.         self.__hooks.append(xchat.hook_command('MSG', self.outMessageCmd))
  413.         self.__hooks.append(xchat.hook_command('MSG+', self.outMessageForce))
  414.         self.__hooks.append(xchat.hook_command('NOTICE', self.outMessageCmd))
  415.         self.__hooks.append(xchat.hook_command('NOTICE+', self.outMessageForce))
  416.  
  417.         self.__hooks.append(xchat.hook_server('notice', self.on_notice,priority=xchat.PRI_HIGHEST))
  418.         self.__hooks.append(xchat.hook_server('332', self.server_332_topic,priority=xchat.PRI_HIGHEST))
  419.  
  420.         self.__hooks.append(xchat.hook_print('Key Press',self.tabComplete))
  421.  
  422.         self.__hooks.append(xchat.hook_print('Notice Send',self.on_notice_send, 'Notice',priority=xchat.PRI_HIGHEST))
  423.         self.__hooks.append(xchat.hook_print('Change Nick', self.nick_trace))
  424.         self.__hooks.append(xchat.hook_print('Channel Action', self.inMessage, 'Channel Action',priority=xchat.PRI_HIGHEST))
  425.         self.__hooks.append(xchat.hook_print('Private Action to Dialog', self.inMessage, 'Private Action to Dialog',priority=xchat.PRI_HIGHEST))
  426.         self.__hooks.append(xchat.hook_print('Private Action ', self.inMessage, 'Private Action',priority=xchat.PRI_HIGHEST))
  427.         self.__hooks.append(xchat.hook_print('Channel Message', self.inMessage, 'Channel Message',priority=xchat.PRI_HIGHEST))
  428.         self.__hooks.append(xchat.hook_print('Private Message to Dialog', self.inMessage, 'Private Message to Dialog',priority=xchat.PRI_HIGHEST))
  429.         self.__hooks.append(xchat.hook_print('Private Message', self.inMessage, 'Private Message',priority=xchat.PRI_HIGHEST))
  430.         self.__hooks.append(xchat.hook_unload(self.__destroy))
  431.         self.loadDB()
  432.  
  433.     def __destroy(self,userdata):
  434.         for hook in self.__hooks:
  435.             xchat.unhook(hook)
  436.         destroyObject(None)
  437.  
  438.     def __del__(self):
  439.         print "\00311fishcrypt.py successful unloaded"
  440.  
  441.     def get_help(self,word, word_eol, userdata):
  442.         if len(word) < 2:
  443.             print "\n\0033 For fishcrypt.py help type /HELP FISHCRYPT"
  444.             return xchat.EAT_NONE
  445.         if word[1].upper() == "FISHCRYPT":
  446.             print ""
  447.             print "\002\0032 ****  fishcrypt.py Version: %s %s ****" % (__module_version__,ISBETA)
  448.             if self.config['FISHBETAVERSION']:
  449.                 print "\0036Beta download %s" % (BETAUPDATEURL)
  450.            
  451.             print "\0036 %s" % UPDATEURL
  452.             print "\n"
  453.             print " \002\00314***************** Fishcrypt Help ********************"
  454.             print " -----------------------------------------------------"
  455.             print "/MSG+ \00314send crypted msg regardless of /ENCRYPT setting"
  456.             print "/NOTICE+ \00314send crypted notice regardless of /ENCRYPT setting"
  457.             print "/ME+ \00314send crypted CTCP ACTION"
  458.             print "/SETKEY \00314set a new key for a nick or channel"
  459.             print "/KEYX \00314exchange pubkey for dialog"
  460.             print "/KEY \00314show Keys"
  461.             print "/DELKEY \00314delete Keys"
  462.             print "/CBCMODE \00314enable/disable CBC Mode for this Key"
  463.             print "/ENCRYPT \00314enable/disable encryption for this Key"
  464.             print "/PROTECTKEY \00314enable/disable protection for keyx key exchange"
  465.             print "/DBPASS \00314set/change the passphrase for the Key Storage"
  466.             print "/DBLOAD \00314loads the Key Storage"
  467.             print "/PRNDECRYPT \00314decrypts messages localy"
  468.             print "/PRNCRYPT \00314encrypts messages localy"
  469.             print "/FISHUPDATE \00314check online for new Version and update"
  470.             print "/SET [fishcrypt] \00314show/set fishcrypt settings"
  471.             return xchat.EAT_ALL
  472.  
  473.     def tabComplete(self,word, word_eol, userdata):
  474.         if word[0] not in ["65289","65056"]:
  475.             return xchat.EAT_NONE
  476.         input = xchat.get_info('inputbox')
  477.         if input.upper().startswith("/UPDATE FISHCRYPT I"):
  478.             newinput = "/UPDATE FISHCRYPT INSTALL"
  479.         elif input.upper().startswith("/UPDATE FISHCRYPT D"):
  480.             newinput = "/UPDATE FISHCRYPT DIFF"
  481.         elif input.upper().startswith("/UPDATE FISHCRYPT C"):
  482.             newinput = "/UPDATE FISHCRYPT CHANGES"
  483.         elif input.upper().startswith("/UPDATE FISHCRYPT L"):
  484.             newinput = "/UPDATE FISHCRYPT LOAD"
  485.         elif input.upper() == "/UPDATE FISHCRYPT ":
  486.             print "LOAD INSTALL DIFF CHANGES"
  487.             return xchat.EAT_NONE
  488.         elif input.upper().startswith("/UPDATE F"):
  489.             newinput = "/UPDATE FISHCRYPT "
  490.         elif input.upper().startswith("/HELP F"):
  491.             newinput = "/HELP FISHCRYPT "
  492.         elif input.upper().startswith("/SET F"):
  493.             newinput = "/SET FISHCRYPT "
  494.         else:
  495.             return xchat.EAT_NONE
  496.         xchat.command("SETTEXT %s" % newinput)
  497.         xchat.command("SETCURSOR %d" % len(newinput))
  498.         return xchat.EAT_PLUGIN
  499.  
  500.  
  501.     def fishupdate(self,word, word_eol, userdata):
  502.         return self.update(["UPDATE","FISHCRYPT","INSTALL"],None,None)
  503.  
  504.     def update(self,word, word_eol, userdata):
  505.         useproxy = self.config['USEPROXYUPDATE']
  506.         if len(word) <3:
  507.             print "\00313Fishcrypt.py Updater"
  508.             print "\00313/UPDATE FISHCRYPT [LOAD,CHANGES,DIFF,INSTALL]"
  509.             return xchat.EAT_XCHAT
  510.         if word[1].upper() != "FISHCRYPT":
  511.             return xchat.EAT_NONE
  512.         if self.__update_thread:
  513.             print "\0034Update Thread already running"
  514.             return xchat.EAT_ALL
  515.         _doExtra = None
  516.         if word[2].lower() == "diff":
  517.             if self._updatedSource:
  518.                 self._updateDiff(xchat.get_context())
  519.             else:
  520.                 _doExtra = self._updateDiff
  521.         if word[2].lower() == "changes":
  522.             if self._updatedSource:
  523.                 self._updateChanges(xchat.get_context())
  524.             else:
  525.                 _doExtra = self._updateChanges
  526.         if word[2].lower() == "install":
  527.             if self._updatedSource:
  528.                 self._updateInstall(xchat.get_context())
  529.             else:
  530.                 _doExtra = self._updateInstall
  531.         if word[2].lower() == "load" or _doExtra:
  532.             proxyload(self._update,useproxy,_doExtra)
  533.        
  534.         return xchat.EAT_ALL
  535.        
  536.     def _update(self,urllib2,doExtra):
  537.         self.__update_thread = Thread(target=self.__update,kwargs={'urllib2':urllib2,'context':xchat.get_context(),'doExtra':doExtra},name='fishcrypt_update')
  538.         self.__update_thread.start()
  539.  
  540.     def _updateInstall(self,context):
  541.         try:
  542.             try:
  543.                 __fd = open(scriptname,"wb")
  544.                 __fd.write(self._updatedSource)
  545.             finally:
  546.                 __fd.close()
  547.             context.prnt( "\00310UPDATE Complete \r\nplease reload the script (/py reload %s)" % (script,) )
  548.         except:
  549.             context.prnt( "\002\0034UPDATE FAILED" )
  550.             raise
  551.  
  552.     def _updateDiff(self,context):
  553.         currentscript = open(scriptname,"rb").read()
  554.         import difflib
  555.         for line in difflib.unified_diff(currentscript.splitlines(1),self._updatedSource.splitlines(1)):
  556.             context.prnt( line)
  557.  
  558.     def _updateChanges(self,context):
  559.         currentscript = open(scriptname,"rb").read()
  560.         import difflib
  561.         for line in difflib.ndiff(currentscript[currentscript.find("# Changelog:"):currentscript.find("__module_name__")].splitlines(1),self._updatedSource[self._updatedSource.find("# Changelog:"):self._updatedSource.find("__module_name__")].splitlines(1)):
  562.             if len(line) > 2:
  563.                 if line[0] in ["+","-"]:
  564.                     context.prnt( line[2:])
  565.  
  566.     def __update(self,urllib2,context,doExtra):
  567.         url = UPDATEURL
  568.         if self.config['FISHBETAVERSION']:
  569.             url = BETAUPDATEURL
  570.         context.prnt("\0038.....checking for updates at %r... please wait ...." % url)
  571.         try:
  572.             try:
  573.                 __updatescript = urllib2.urlopen(url,timeout=self.config['FISHUPDATETIMEOUT']).read()
  574.                 __updateversion = re.search("__module_version__ = '([0-9]+\.[0-9]+)'",__updatescript)
  575.                 if __updateversion:
  576.                     if float(__module_version__) < float(__updateversion.group(1)) or ISBETA != "":
  577.                         updatechksum = hashlib.sha1(__updatescript).hexdigest()
  578.                         if SCRIPTCHKSUM <> updatechksum:
  579.                             self._updatedSource = __updatescript
  580.                             context.prnt( "\00310Download Version %s with checksum %r complete" % (__updateversion.group(1),updatechksum))
  581.                         else:
  582.                             context.prnt( "\00310No new version available - checksums match")
  583.                     else:
  584.                         context.prnt( "\0032%sVersion %s is up to date (found Version %s)" % (__module_name__,__module_version__,__updateversion.group(1)) )
  585.                 else:
  586.                     context.prnt( "\0034NO VALID PLUGIN FOUND AT %s" % (url,) )
  587.             except urllib2.URLError,err:
  588.                 context.prnt( "\002\0034LOAD FAILED" )
  589.                 context.prnt( "%r" % (err,) )
  590.  
  591.             except:
  592.                 context.prnt( "\002\0034LOAD FAILED" )
  593.                 context.prnt("%r" % (sys.exc_info(),))
  594.         finally:
  595.             self.__update_thread = None
  596.             context.prnt( "\00310Update Thread finished" )
  597.         if doExtra and self._updatedSource:
  598.             doExtra(context)
  599.  
  600.  
  601.     ## Load key storage
  602.     def loadDB(self):
  603.         data = db = None
  604.         try:
  605.             try:
  606.                 hnd = open(os.path.join(path,'fish3.pickle'),'rb')
  607.                 data = hnd.read()
  608.                 ## set DB loaded to False as we have a file we don't want to create a new
  609.                 self.status['LOADED'] = False
  610.             except:
  611.                 return
  612.         finally:
  613.             try:
  614.                 hnd.close()
  615.             except:
  616.                 pass
  617.         if data:
  618.             try:
  619.                 db = pickle.loads(data)
  620.                 print "%sUnencrypted Key Storage loaded" % (COLOR['bpurple'],)
  621.             except pickle.UnpicklingError:
  622.                 ## ignore if file is invalid
  623.                 if data.startswith("+OK *"):
  624.                     self.status['CRYPTDB'] = True
  625.                     if self.status['DBPASSWD']:
  626.                         try:
  627.                             algo = BlowfishCBC(self.status['DBPASSWD'])
  628.                             decrypted = mircryption_cbc_unpack(data,algo)
  629.                             db = pickle.loads(decrypted)
  630.                             print "%sEncrypted Key Storage loaded" % (COLOR['green'],)
  631.                         except pickle.UnpicklingError:
  632.                             self.status['DBPASSWD'] = None
  633.                             print "%sKey Storage can't be loaded with this password" % (COLOR['dred'],)
  634.                             print "use /DBLOAD to load it later"
  635.                     else:
  636.                         xchat.command('GETSTR ""  "SET fishcrypt_passload" "Enter your Key Storage Password"')
  637.                 pass
  638.         if type(db) == dict:
  639.             self.status['LOADED'] = True
  640.             ## save temp keymap
  641.             oldKeyMap = self.__KeyMap
  642.             oldTargetMap = self.__TargetMap
  643.             ## fill dict with the loaded Keymap
  644.             self.__KeyMap = db.get("KeyMap",{})
  645.             self.__TargetMap = db.get("TargetMap",{})
  646.             self.__KeyMap.update(oldKeyMap)
  647.             self.__TargetMap.update(oldTargetMap)
  648.             for key in self.__KeyMap.keys():
  649.                 self.__KeyMap[key].keyname = key
  650.                 if not hasattr(self.__KeyMap[key],'protect_mode'):
  651.                     self.__KeyMap[key].protect_mode = False
  652.             self.cleanUpTargetMap()
  653.  
  654.             ## only import valid config values
  655.             for key in self.config.keys():
  656.                 try:
  657.                     self.config[key] = db["Config"][key]
  658.                 except KeyError:
  659.                     pass
  660.             if self.config['FISHDEVELOPDEBUG']:
  661.                 self.__hooks.append(xchat.hook_command('FISHEVAL',self.__evaldebug))
  662.  
  663.     def cleanUpTargetMap(self):
  664.         ## DB Cleanup
  665.         for network in self.__TargetMap.values():
  666.             for target,value in network.items():
  667.                 if type(value[1]) <> SecretKey or value[0] < time.time() - 60*60*24*7 or value[1] not in self.__KeyMap.values():
  668.                     del network[target]
  669.                     print "Expired: %r %r" % (target,value)
  670.  
  671.  
  672.     ## save keys to storage
  673.     def saveDB(self):
  674.         self.cleanUpTargetMap()
  675.         if not self.status['LOADED']:
  676.             print "Key Storage not loaded, no save. use /DBLOAD to load it"
  677.             return
  678.         try:
  679.             data = pickle.dumps({
  680.                 'KeyMap': self.__KeyMap,
  681.                 'TargetMap': self.__TargetMap,
  682.                 'Config': self.config,
  683.                 'Version': __module_version__
  684.             })
  685.             hnd = open(os.path.join(path,'fish3.pickle'),'wb')
  686.             if self.status['DBPASSWD']:
  687.                 algo = BlowfishCBC(self.status['DBPASSWD'])
  688.                 encrypted = mircryption_cbc_pack(data,algo)
  689.                 data = encrypted
  690.                 self.status['CRYPTDB'] = True
  691.             else:
  692.                 self.status['CRYPTDB'] = False
  693.             hnd.write(data)
  694.         finally:
  695.             hnd.close()
  696.  
  697.     def __evaldebug(self,word, word_eol, userdata):
  698.         eval(compile(word_eol[1],'develeval','exec'))
  699.         return xchat.EAT_ALL
  700.  
  701.     def set_dbload(self,word, word_eol, userdata):
  702.         self.loadDB()
  703.         return xchat.EAT_ALL
  704.  
  705.     def set_dbpass(self,word, word_eol, userdata):
  706.         xchat.command('GETSTR "" "SET fishcrypt_passpre" "New Password"')
  707.         return xchat.EAT_ALL
  708.  
  709.     ## set keydb passwd
  710.     def settings(self,word, word_eol, userdata):
  711.         fishonly = False
  712.         if len(word) == 2:
  713.             if word[1].upper() == "FISHCRYPT":
  714.                 fishonly = True
  715.         if len(word) < 2 or fishonly:
  716.             ## not for us
  717.             #print "fishcrypt_pass%s%s%s: \003%r" % (COLOR['blue'],"."*16,COLOR['green'],self.status['DBPASSWD'])
  718.             for key in self.config:
  719.                 keyname = "%s%s" % (key,"."*20)
  720.                 print "\00312%.29s: %s" % (keyname,str(self.config[key]))
  721.             if fishonly:
  722.                 return xchat.EAT_ALL
  723.             return xchat.EAT_NONE
  724.  
  725.  
  726.         if word[1] == "fishcrypt_passpre":
  727.             if len(word) == 2:
  728.                 self.status['CHKPW'] = ""
  729.             else:
  730.                 self.status['CHKPW'] = word_eol[2]
  731.             xchat.command('GETSTR ""  "SET fishcrypt_pass" "Repeat the Password"')
  732.             return xchat.EAT_ALL
  733.  
  734.         if word[1] == "fishcrypt_pass":
  735.             if len(word) == 2:
  736.                 if self.status['CHKPW'] <> "" and self.status['CHKPW'] <> None:
  737.                     print "Passwords don't match"
  738.                     self.status['CHKPW'] = None
  739.                     return xchat.EAT_ALL
  740.                 self.status['DBPASSWD'] = None
  741.                 print "%sPassword removed and Key Storage decrypted" % (COLOR['dred'],)
  742.                 print "%sWarning Keys are plaintext" % (COLOR['dred'],)
  743.             else:
  744.                 if self.status['CHKPW'] <> None and self.status['CHKPW'] <> word_eol[2]:
  745.                     print "Passwords don't match"
  746.                     self.status['CHKPW'] = None
  747.                     return xchat.EAT_ALL
  748.                 if len(word_eol[2]) < 8 or len(word_eol[2]) > 56:
  749.                     print "Passwords must be between 8 and 56 chars"
  750.                     self.status['CHKPW'] = None
  751.                     return xchat.EAT_ALL
  752.                 self.status['DBPASSWD'] = word_eol[2]
  753.                 ## don't show the pw on console if set per GETSTR
  754.                 if self.status['CHKPW'] == None:
  755.                     print "%sPassword for Key Storage encryption set to %r" % (COLOR['dred'],self.status['DBPASSWD'])
  756.                 else:
  757.                     print "%sKey Storage encrypted" % (COLOR['dred'])
  758.             self.status['CHKPW'] = None
  759.             self.saveDB()
  760.             return xchat.EAT_ALL
  761.  
  762.         if word[1] == "fishcrypt_passload":
  763.             if len(word) > 2:
  764.                 if len(word_eol[2]) < 8 or len(word_eol[2]) > 56:
  765.                     print "Password not between 8 and 56 chars"
  766.                 else:
  767.                     self.status['DBPASSWD'] = word_eol[2]
  768.                     self.loadDB()
  769.             else:
  770.                 print "Key Storage Not loaded"
  771.                 self.status['DBPASSWD'] = None
  772.             return xchat.EAT_ALL
  773.  
  774.         key = word[1].upper()
  775.         if key in self.config.keys():
  776.             if len(word) <3:
  777.                 keyname = "%s%s" % (key,"."*20)
  778.                 print "\00312%.29s: %s" % (keyname,str(self.config[key]))
  779.             else:
  780.                 try:
  781.                     if type(self.config[key]) == bool:
  782.                         self.config[key] = bool(word[2] in ONMODES)
  783.                     else:
  784.                         self.config[key] = type(self.config[key])(word_eol[2])
  785.                     print "\0035Set %r to %r" % (key,word_eol[2])
  786.                     self.saveDB()
  787.                 except ValueError:
  788.                     print "\0034Invalid Config Value %r for %s" % (word_eol[2],key)
  789.             return xchat.EAT_ALL
  790.  
  791.         return xchat.EAT_NONE
  792.  
  793.     ## incoming notice received
  794.     def on_notice(self,word, word_eol, userdata):
  795.         ## check if this is not allready processed
  796.         if self.__chk_proc():
  797.             return xchat.EAT_NONE
  798.  
  799.         ## check if DH Key Exchange
  800.         if word_eol[3].startswith(':DH1080_FINISH'):
  801.             return self.dh1080_finish(word, word_eol, userdata)
  802.         elif word_eol[3].startswith(':DH1080_INIT'):
  803.             return self.dh1080_init(word, word_eol, userdata)
  804.  
  805.         ## check for encrypted Notice
  806.         elif word_eol[3].startswith('::+OK ') or word_eol[3].startswith('::mcps '):
  807.            
  808.             ## rewrite data to pass to default inMessage function
  809.             ## change full ident to nick only
  810.             nick = self.get_nick(word[0])
  811.             target = word[2]
  812.             speaker = nick
  813.             ## strip :: from message
  814.             message = word_eol[3][2:]
  815.             if target.startswith("#"):
  816.                 id = self.get_id()
  817.                 speaker = "## %s" % speaker
  818.             else:
  819.                 id = self.get_id(nick=nick)
  820.             #print "DEBUG(crypt): key: %r word: %r" % (id,word,)
  821.             key = self.find_key(id)
  822.             ## if no key found exit
  823.             if not key:
  824.                 return xchat.EAT_NONE
  825.            
  826.             ## decrypt the message
  827.             try:
  828.                 sndmessage = self.decrypt(key,message)
  829.             except:
  830.                 sndmessage = None
  831.             isCBC=0
  832.             if message.startswith("+OK *"):
  833.                 isCBC=1
  834.             failcol = ""
  835.  
  836.             ## if decryption was possible check for invalid chars
  837.             if sndmessage:
  838.                 try:
  839.                     message = sndmessage.decode("UTF8").encode("UTF8")
  840.                     ## mark nick for encrypted msgg
  841.                     speaker = "%s %s" % ("°"*(1+isCBC),speaker)
  842.                 except UnicodeError:
  843.                     try:
  844.                         message = unicode(sndmessage,encoding='iso8859-1',errors='ignore').encode('UTF8')
  845.                         ## mark nick for encrypted msgg
  846.                         speaker = "%s %s" % ("°"*(1+isCBC),speaker)
  847.                     except:
  848.                         raise
  849.                     ## send the message to local xchat
  850.                     #self.emit_print(userdata,speaker,message)
  851.                     #return xchat.EAT_XCHAT
  852.                 except:
  853.                     ## mark nick with a question mark
  854.                     speaker = "?%s" % (speaker)
  855.                     failcol = "\003"
  856.             else:
  857.                 failcol = "\003"
  858.             ## 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
  859.             self.emit_print(userdata,speaker,"%s%s" % (failcol,message))
  860.             return xchat.EAT_XCHAT
  861. #            return self.inMessage([nick,msg], ["%s %s" % (nick,msg),msg], userdata)
  862.  
  863.         ## ignore everything else
  864.         else:
  865.             #print "DEBUG: %r %r %r" % (word, word_eol, userdata)
  866.             return xchat.EAT_NONE
  867.  
  868.     ## local notice send messages
  869.     def on_notice_send(self,word, word_eol, userdata):
  870.         ## get current nick
  871.         target = xchat.get_context().get_info('nick')
  872.         #print "DEBUG_notice_send: %r - %r - %r %r" % (word,word_eol,userdata,nick)
  873.        
  874.         ## check if this is not allready processed
  875.         if self.__chk_proc(target=target):
  876.             return xchat.EAT_NONE
  877.        
  878.         ## get the speakers nick only from full ident
  879.         speaker = self.get_nick(word[0])
  880.        
  881.         ## strip first : from notice
  882.         message = word_eol[1][1:]
  883.         if message.startswith('+OK ') or message.startswith('mcps '):
  884.             ## get the key id from the speaker
  885.             id = self.get_id(nick=speaker)
  886.             key = self.find_key(id)
  887.            
  888.             ## if no key available for the speaker exit
  889.             if not key:
  890.                 return xchat.EAT_NONE
  891.            
  892.             ## decrypt the message
  893.             sndmessage = self.decrypt(key,message)
  894.             isCBC = 0
  895.             if message.startswith("+OK *"):
  896.                 isCBC = 1
  897.                 if not target.startswith("#"):
  898.                     ## if we receive a messge with CBC enabled we asume the partner can also except it so activate it
  899.                     key.cbc_mode = True
  900.  
  901.             ## if decryption was possible check for invalid chars
  902.  
  903.             if sndmessage:
  904.                 try:
  905.                     message = sndmessage.decode("UTF8").encode("UTF8")
  906.                     ## mark nick for encrypted msgg
  907.                     speaker = "%s %s" % ("°"*(1+isCBC),speaker)
  908.                 except:
  909.                     ## mark nick with a question mark
  910.                     speaker = "?%s" % (speaker)
  911.                     ## send original message because invalid chars
  912.                     message = message
  913.  
  914.             ## send the message back to incoming notice but with locked target status so it will not be processed again
  915.             self.emit_print("Notice Send",speaker,message,target=target)
  916.             return xchat.EAT_XCHAT
  917.         return xchat.EAT_NONE
  918.            
  919.     ## incoming messages
  920.     def inMessage(self,word, word_eol, userdata):
  921.         ## if message is allready processed ignore
  922.         if self.__chk_proc() or len(word_eol) < 2:
  923.             return xchat.EAT_PLUGIN
  924.  
  925.         speaker = word[0]
  926.         message = word_eol[1]
  927.         #print "DEBUG(INMsg): %r - %r - %r" % (word,word_eol,userdata)
  928.         # if there is mode char, remove it from the message
  929.         if len(word_eol) >= 3:
  930.             #message = message[ : -(len(word_eol[2]) + 1)]
  931.             message = message[:-2]
  932.  
  933.         ## check if message is crypted
  934.         if message.startswith('+OK ') or message.startswith('mcps '):
  935.             target = None
  936.             if userdata == "Private Message":
  937.                 target = speaker
  938.             id = self.get_id(nick=target)
  939.             target,network = id
  940.             key = self.find_key(id)
  941.            
  942.             ## if no key found exit
  943.             if not key:
  944.                 return xchat.EAT_NONE
  945.            
  946.             ## decrypt the message
  947.             try:
  948.                 sndmessage = self.decrypt(key,message)
  949.             except:
  950.                 sndmessage = None
  951.             isCBC=0
  952.             if message.startswith("+OK *"):
  953.                 isCBC=1
  954.                 if not target.startswith("#"):
  955.                     ## if we receive a messge with CBC enabled we asume the partner can also except it so activate it
  956.                     key.cbc_mode = True
  957.  
  958.             failcol = ""
  959.  
  960.             ## if decryption was possible check for invalid chars
  961.             if sndmessage:
  962.                 try:
  963.                     message = sndmessage.decode("UTF8").encode("UTF8")
  964.                     ## mark nick for encrypted msgg
  965.                     speaker = "%s %s" % ("°"*(1+isCBC),speaker)
  966.                 except UnicodeError:
  967.                     try:
  968.                         message = unicode(sndmessage,encoding='iso8859-1',errors='ignore').encode('UTF8')
  969.                         ## mark nick for encrypted msgg
  970.                         speaker = "%s %s" % ("°"*(1+isCBC),speaker)
  971.                     except:
  972.                         raise
  973.                     ## send the message to local xchat
  974.                     #self.emit_print(userdata,speaker,message)
  975.                     #return xchat.EAT_XCHAT
  976.                 except:
  977.                     ## mark nick with a question mark
  978.                     speaker = "?%s" % (speaker)
  979.                     failcol = "\003"
  980.             else:
  981.                 failcol = "\003"
  982.             ## 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
  983.             self.emit_print(userdata,speaker,"%s%s" % (failcol,message))
  984.             return xchat.EAT_ALL
  985.  
  986.         return xchat.EAT_NONE
  987.  
  988.     def decrypt(self,key,msg):
  989.         ## check for CBC
  990.         if 3 <= msg.find(' *') <= 4:
  991.             decrypt_clz = BlowfishCBC
  992.             decrypt_func = mircryption_cbc_unpack
  993.         else:
  994.             decrypt_clz = Blowfish
  995.             decrypt_func = blowcrypt_unpack
  996.         try:
  997.             b = decrypt_clz(key.key)
  998.             #if msg[-2:-1] == " ":
  999.             #   msg = msg[:-2]
  1000.             ret = decrypt_func(msg, b)
  1001.         except MalformedError:
  1002.             try:
  1003.                 cut = (len(msg) -4)%12
  1004.                 if cut > 0:
  1005.                     msg = msg[:cut *-1]
  1006.                     ret = "%s%s" % ( decrypt_func(msg, b), " \0038<<incomplete>>" * (cut>0))
  1007.                 else:
  1008.                     #print "Error Malformed %r" % len(msg)
  1009.                     ret = None
  1010.             except MalformedError:
  1011.                 #print "Error2 Malformed %r" % len(msg)
  1012.                 ret = None
  1013.         except:
  1014.             print "Decrypt ERROR"
  1015.             ret = None
  1016.         return ret
  1017.  
  1018.  
  1019.     ## mark outgoing message being  prefixed with a command like /notice /msg ...
  1020.     def outMessageCmd(self,word, word_eol, userdata):
  1021.         return self.outMessage(word, word_eol, userdata,command=True)
  1022.  
  1023.     ## mark outgoing message being prefixed with a command that enforces encryption like /notice+ /msg+
  1024.     def outMessageForce(self,word, word_eol, userdata):
  1025.         return self.outMessage(word, word_eol, userdata, force=True,command=True)
  1026.  
  1027.     ## the outgoing messages will be proccesed herre
  1028.     def outMessage(self,word, word_eol, userdata,force=False,command=False):
  1029.        
  1030.         ## check if allready processed
  1031.         if self.__chk_proc():
  1032.             return xchat.EAT_NONE
  1033.        
  1034.         ## get the id
  1035.         id = self.get_id()
  1036.         target,network = id
  1037.         ## check if message is prefixed wit a command like /msg /notice
  1038.         action = False
  1039.         if command:
  1040.            
  1041.             if len(word) < (word[0].upper().startswith("ME") and 2 or 3):
  1042.                 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])
  1043.                 return xchat.EAT_ALL
  1044.             ## notice and notice+
  1045.             if word[0].upper().startswith("NOTICE"):
  1046.                 command = "NOTICE"
  1047.             else:
  1048.                 command = "PRIVMSG"
  1049.             if word[0].upper().startswith("ME"):
  1050.                 action = True
  1051.                 message = word_eol[1]
  1052.             else:
  1053.                 ## the target is first parameter after the command, not the current channel
  1054.                 target = word[1]
  1055.                 ## change id
  1056.                 id = (target,network)
  1057.                 ## remove command and target from message
  1058.                 message = word_eol[2]
  1059.         else:
  1060.             command = "PRIVMSG"
  1061.             message = word_eol[0]
  1062.  
  1063.         sendmsg = ''
  1064.         ## try to get a key for the target id
  1065.         key = self.find_key(id)
  1066.        
  1067.         ## my own nick
  1068.         nick = xchat.get_context().get_info('nick')
  1069.  
  1070.         #print "DEBUG(outMsg1)(%r) %r : %r %r" % (id,xchat.get_context().get_info('network'),word,nick)
  1071.  
  1072.         ## if we don't have a key exit
  1073.         if not key:
  1074.             return xchat.EAT_NONE
  1075.        
  1076.         ## 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+
  1077.         if key.key == None or (key.active == False and not force):
  1078.             return xchat.EAT_NONE
  1079.        
  1080.         ## if the message is marked with the plaintextmarker (default +p) don't encrypt
  1081.         if message.startswith(self.config['PLAINTEXTMARKER']):
  1082.             ## remove the plaintextmarker from the message
  1083.             sendmessages = [message[len(self.config['PLAINTEXTMARKER'])+1:]]
  1084.             messages = sendmessages
  1085.         else:
  1086.             ## encrypt message
  1087.             maxlen = self.config['MAXMESSAGELENGTH']
  1088.             cutmsg = message
  1089.             messages = []
  1090.             sendmessages = []
  1091.             while len(cutmsg) >0:
  1092.                 sendmessages.append(self.encrypt(key,cutmsg[:maxlen]))
  1093.                 messages.append(cutmsg[:maxlen])
  1094.                 cutmsg = cutmsg[maxlen:]
  1095.             ## mark the nick with ° for encrypted messages
  1096.             nick = "%s %s" % ("°"*(1+key.cbc_mode),nick)
  1097.  
  1098.         #print "DEBUG(outMsg2): %r %r %r %r" % (command,message,nick,target)
  1099.  
  1100.         for sendmsg in sendmessages:
  1101.             ## lock the target
  1102.             self.__lock_proc(True)
  1103.             ## send the command (PRIVMSG / NOTICE)
  1104.             if action:
  1105.                 sendmsg = "\001ACTION %s\001" % sendmsg
  1106.             xchat.command('%s %s :%s' % (command,target, sendmsg))
  1107.             ## release the lock
  1108.             self.__lock_proc(False)
  1109.        
  1110.         for message in messages:
  1111.             ## if it is no notice it must be send plaintext to xchat for you
  1112.             if command == "PRIVMSG":
  1113.                 if action:
  1114.                     self.emit_print('Channel Action',  nick, message)
  1115.                 else:
  1116.                     targetTab= xchat.find_context(channel=target)
  1117.                     if not targetTab and targetTab != xchat.get_context():
  1118.                         self.emit_print('Message Send',  "%s %s" % ("°"*(1+key.cbc_mode),target), message)
  1119.                     else:
  1120.                         self.emit_print('Your Message',  nick, message,toContext=targetTab)
  1121.         return xchat.EAT_ALL
  1122.        
  1123.     def encrypt(self,key, msg):
  1124.         if key.cbc_mode:
  1125.             encrypt_clz = BlowfishCBC
  1126.             encrypt_func = mircryption_cbc_pack
  1127.         else:
  1128.             encrypt_clz = Blowfish
  1129.             encrypt_func = blowcrypt_pack
  1130.         b = encrypt_clz(key.key)
  1131.         return encrypt_func(msg, b)
  1132.  
  1133.     ## send message to local xchat and lock it
  1134.     def emit_print(self,userdata,speaker,message,target=None,toContext=None):
  1135.         if not toContext:
  1136.             toContext = xchat.get_context()
  1137.         if userdata == None:
  1138.             ## if userdata is none its possible Notice
  1139.             userdata = "Notice"
  1140.         if not target:
  1141.             ## if no special target for the lock is set, make it the speaker
  1142.             target = speaker
  1143.         ## lock the processing of that message
  1144.         self.__lock_proc(True,target=target)
  1145.         ## check for Highlight
  1146.         for hl in [xchat.get_info('nick')] + xchat.get_prefs("irc_extra_hilight").split(","):
  1147.             if len(hl) >0 and message.find(hl) > -1:
  1148.                 if userdata == "Channel Message":
  1149.                     userdata = "Channel Msg Hilight"
  1150.                 xchat.command("GUI COLOR 3")
  1151.         ## send the message
  1152.         toContext.emit_print(userdata,speaker, message.replace('\0',''))
  1153.         ## release the lock
  1154.         self.__lock_proc(False,target=target)
  1155.  
  1156.     ## set or release the lock on the processing to avoid loops
  1157.     def __lock_proc(self,state,target=None):
  1158.         ctx = xchat.get_context()
  1159.         if not target:
  1160.             ## if no target set, the current channel is the target
  1161.             target = ctx.get_info('channel')
  1162.         ## the lock is NETWORK-TARGET
  1163.         id = "%s-%s" % (ctx.get_info('network'),target)
  1164.         self.__lockMAP[id] = state
  1165.  
  1166.     ## check if that message is allready processed to avoid loops
  1167.     def __chk_proc(self,target=None):
  1168.         ctx = xchat.get_context()
  1169.         if not target:
  1170.             ## if no target set, the current channel is the target
  1171.             target = ctx.get_info('channel')
  1172.         id = "%s-%s" % (ctx.get_info('network'),target)
  1173.         return self.__lockMAP.get(id,False)
  1174.  
  1175.     # get an id from channel name and networkname
  1176.     def get_id(self,nick=None):
  1177.         ctx = xchat.get_context()
  1178.         if nick:
  1179.             target = nick
  1180.         else:
  1181.             target = str(ctx.get_info('channel'))
  1182.         ##return the id
  1183.         return (target, str(ctx.get_info('network')).lower())
  1184.  
  1185.     def find_key(self,id,create=None):
  1186.         key = self.__KeyMap.get(id,None)
  1187.         target, network = id
  1188.         networkmap = self.__TargetMap.get(network,None)
  1189.         if not networkmap:
  1190.             networkmap = {}
  1191.             self.__TargetMap[network] = networkmap
  1192.         if not key:
  1193.             lastaxx,key = networkmap.get(target,(-1,None))
  1194.         else:
  1195.             for _target,_key in filter(lambda x: x[1] == key,networkmap.items()):
  1196.                 if _target != target:
  1197.                     del networkmap[_target]
  1198.         if not key and create:
  1199.             key = create
  1200.         if key:
  1201.             self.__TargetMap[network][target] = (int(time.time()),key)
  1202.         return key
  1203.  
  1204.     ## return the nick only
  1205.     def get_nick(self,full):
  1206.         if full[0] == ':':
  1207.             full = full[1:]
  1208.         try:
  1209.             ret = full[:full.index('!')]
  1210.         except ValueError:
  1211.             ret  = full
  1212.         return ret
  1213.  
  1214.     ## print encrypted localy
  1215.     def prn_crypt(self,word, word_eol, userdata):
  1216.         id = self.get_id()
  1217.         target, network = id
  1218.         key = self.find_key(id)
  1219.         if len(word_eol) < 2:
  1220.             print "usage: /PRNCRYPT <msg to encrypt>"
  1221.         else:    
  1222.             if key:
  1223.                 print "%s%s" % (COLOR['blue'],self.encrypt(key,word_eol[1]))
  1224.             else:
  1225.                 print "%sNo known Key found for %s" % (COLOR['red'],target,)
  1226.         return xchat.EAT_ALL
  1227.  
  1228.     ## print decrypted localy
  1229.     def prn_decrypt(self,word, word_eol, userdata):
  1230.         id = self.get_id()
  1231.         target, network = id
  1232.         key = self.find_key(id)
  1233.         if len(word_eol) < 2:
  1234.             print "usage: /PRNDECRYPT <msg to decrypt>"
  1235.         else:    
  1236.             if key:
  1237.                 print "%s%s" % (COLOR['blue'],self.decrypt(key,word_eol[1]))
  1238.             else:
  1239.                 print "%sNo known Key found for %s" % (COLOR['red'],target,)
  1240.         return xchat.EAT_ALL
  1241.  
  1242.  
  1243.     ## manual set a key for a nick or channel
  1244.     def set_key(self,word, word_eol, userdata):
  1245.         id = self.get_id()
  1246.         target, network = id
  1247.        
  1248.         ## if more than 2 parameter the nick/channel target is set to para 1 and the key is para 2
  1249.         if len(word) > 2:
  1250.             target = word[1]
  1251.             if target.find("@") > 0:
  1252.                 target,network = target.split("@",1)
  1253.             newkey = word[2]
  1254.             id = (target,network)
  1255.         ## else the current channel/nick is taken as target and the key is para 1
  1256.         else:
  1257.             newkey = word[1]
  1258.         if len(newkey) < 8 or len(newkey) > 56:
  1259.             print "Key must be between 8 and 56 chars"
  1260.             return xchat.EAT_ALL
  1261.         ## get the Keyobject if available or get a new one
  1262.         key = self.find_key(id,create=SecretKey(None,protectmode=self.config['DEFAULTPROTECT'],cbcmode=self.config['DEFAULTCBC']))
  1263.         ## set the key
  1264.         key.key = newkey
  1265.         key.keyname = id
  1266.         ## put it in the key dict
  1267.         self.__KeyMap[id] = key
  1268.  
  1269.         print "Key for %s on Network %s set to %r" % ( target,network,newkey)
  1270.         ## save the key storage
  1271.         self.saveDB()
  1272.         return xchat.EAT_ALL
  1273.  
  1274.     ## delete a key or all
  1275.     def del_key(self,word, word_eol, userdata):
  1276.         ## don't accept no parameter
  1277.         if len(word) <2:
  1278.             print "Error: /DELKEY nick|channel|* (* deletes all keys)"
  1279.             return xchat.EAT_ALL
  1280.         target = word_eol[1]
  1281.         ## if target name is * delete all
  1282.         if target == "*":
  1283.             self.__KeyMap = {}
  1284.         else:
  1285.             if target.find("@") > 0:
  1286.                 target,network = target.split("@",1)
  1287.                 id = target,network
  1288.             else:            
  1289.                 id = self.get_id(nick=target)
  1290.                 target,network = id
  1291.             ## try to delete the key
  1292.             try:
  1293.                 del self.__KeyMap[id]
  1294.                 print "Key for %s on %s deleted" % (target,network)
  1295.             except KeyError:
  1296.                 print "Key %r not found" % (id,)
  1297.         ## save the keystorage
  1298.         self.saveDB()
  1299.         return xchat.EAT_ALL
  1300.  
  1301.     ## show either key for current chan/nick or all
  1302.     def show_key(self,word, word_eol, userdata):
  1303.         ## if no parameter show key for current chan/nick
  1304.         if len(word) <2:
  1305.             id = self.get_id()
  1306.         else:
  1307.             target = word_eol[1]
  1308.             network = ""
  1309.             if target.find("@") > 0:
  1310.                 target,network = target.split("@",1)
  1311.                 if network.find("*") > -1:
  1312.                     network = network[:-1]
  1313.             ## if para 1 is * show all keys and there states
  1314.             if target.find("*") > -1:
  1315.                 print " -------- nick/chan ------- -------- network ------- -ON- -CBC- -PROTECT- -------------------- Key --------------------"
  1316.                 for id,keys in self.__KeyMap.items():
  1317.                     if id[0].startswith(target[:-1]) and id[1].startswith(network):
  1318.                         print "  %-26.26s %-22.22s  %2.2s   %3.3s   %5.5s      %s" % (id[0],id[1],YESNO(keys.active),YESNO(keys.cbc_mode),YESNO(keys.protect_mode),keys.key)
  1319.  
  1320.                 return xchat.EAT_ALL
  1321.             ## else get the id for the target
  1322.             id = self.get_id(nick=target)
  1323.        
  1324.         ## get the Key
  1325.         key = self.find_key(id)
  1326.         if key:
  1327.             ## show Key for the specified chan/nick
  1328.             print "[ %s ] Key: %s - Active: %s - CBC: %s - PROTECT: %s" % (key,key.key,YESNO(key.active),YESNO(key.cbc_mode),YESNO(key.protect_mode))
  1329.         else:
  1330.             print "No Key found"
  1331.         return xchat.EAT_ALL
  1332.  
  1333.     ## start the DH1080 Key Exchange
  1334.     def key_exchange(self,word, word_eol, userdata):
  1335.         id = self.get_id()
  1336.         target,network = id
  1337.         if len(word) >1:
  1338.             target = word[1]
  1339.             id = (target,network)
  1340.  
  1341.         ## fixme chan notice - what should happen when keyx is send to channel trillian seems to accept it and send me a key --
  1342.         if target.startswith("#"):
  1343.             print "Channel Exchange not implemented"
  1344.             return xchat.EAT_ALL
  1345.  
  1346.         ## create DH
  1347.         dh = DH1080Ctx()
  1348.  
  1349.         self.__KeyMap[id] = self.find_key(id,create=SecretKey(dh,protectmode=self.config['DEFAULTPROTECT'],cbcmode=self.config['DEFAULTCBC']))
  1350.         self.__KeyMap[id].keyname = id
  1351.         self.__KeyMap[id].dh = dh
  1352.  
  1353.         ## lock the target
  1354.         self.__lock_proc(True)
  1355.         ## send key with notice to target
  1356.         xchat.command('NOTICE %s %s' % (target, dh1080_pack(dh)))
  1357.         ## release the lock
  1358.         self.__lock_proc(False)
  1359.  
  1360.         ## save the key storage
  1361.         self.saveDB()
  1362.         return xchat.EAT_ALL
  1363.  
  1364.  
  1365.     ## Answer to KeyExchange
  1366.     def dh1080_init(self,word, word_eol, userdata):
  1367.         id = self.get_id(nick=self.get_nick(word[0]))
  1368.         target,network = id
  1369.         message = word_eol[3]
  1370.         key = self.find_key(id,create=SecretKey(None,protectmode=self.config['DEFAULTPROTECT'],cbcmode=self.config['DEFAULTCBC']))
  1371.         ## Protection against a new key if "/PROTECTKEY" is on for nick
  1372.         if key.protect_mode:
  1373.             print "%sKEYPROTECTION: %s on %s" % (COLOR['red'],target,network)
  1374.             xchat.command("notice %s %s KEYPROTECTION:%s %s" % (target,self.config['PLAINTEXTMARKER'],COLOR['red'],target))
  1375.             return xchat.EAT_ALL
  1376.  
  1377.         ## Stealth Check
  1378.         if self.config['FISHSTEALTH']:
  1379.             print "%sSTEALTHMODE: %s tried a keyexchange on %s" % (COLOR['green'],target,network)
  1380.             return xchat.EAT_ALL
  1381.  
  1382.         mirc_mode = False
  1383.         try:
  1384.             if word[5] == "CBC":
  1385.                 print "mIRC CBC KeyExchange detected."
  1386.                 message = "%s %s" % (word[3],word[4])
  1387.                 mirc_mode = True
  1388.         except IndexError:
  1389.             pass
  1390.  
  1391.         dh = DH1080Ctx()
  1392.         dh1080_unpack(message[1 : ], dh)
  1393.         key.key = dh1080_secret(dh)
  1394.         key.keyname = id
  1395.  
  1396.         ## lock the target
  1397.         self.__lock_proc(True)
  1398.         ## send key with notice to target
  1399.         if mirc_mode:
  1400.             xchat.command('NOTICE %s %s CBC' % (target, dh1080_pack(dh)))
  1401.         else:
  1402.             xchat.command('NOTICE %s %s' % (target, dh1080_pack(dh)))
  1403.            
  1404.         ## release the lock
  1405.         self.__lock_proc(False)
  1406.         self.__KeyMap[id] = key
  1407.         print "DH1080 Init: %s on %s" % (target,network)
  1408.         print "Key set to %r" % (key.key,)
  1409.         ## save key storage
  1410.         self.saveDB()
  1411.         return xchat.EAT_ALL
  1412.  
  1413.     ## Answer from targets init
  1414.     def dh1080_finish(self,word, word_eol, userdata):
  1415.         id = self.get_id(nick=self.get_nick(word[0]))
  1416.         message = word_eol[3]
  1417.         target,network = id
  1418.         ## fixme if not explicit send to the Target the received key is discarded - chan exchange
  1419.         if id not in self.__KeyMap:
  1420.             print "Invalid DH1080 Received from %s on %s" % (target,network)
  1421.             return xchat.EAT_NONE
  1422.         key = self.__KeyMap[id]
  1423.         dh1080_unpack(message[1 : ], key.dh)
  1424.         key.key = dh1080_secret(key.dh)
  1425.         key.keyname = id
  1426.         print "DH1080 Finish: %s on %s" % (target,network)
  1427.         print "Key set to %r" % (key.key,)
  1428.         ## save key storage
  1429.         self.saveDB()
  1430.         return xchat.EAT_ALL
  1431.  
  1432.     ## set cbc mode or show the status
  1433.     def set_cbc(self,word, word_eol, userdata):
  1434.         ## check for parameter
  1435.         if len(word) >2:
  1436.             # if both specified first is target second is mode on/off
  1437.             target = word[1]
  1438.             mode = word[2]
  1439.         else:
  1440.             ## if no target defined target is current chan/nick
  1441.             target = None
  1442.             if len(word) >1:
  1443.                 ## if one parameter set mode to it else show only
  1444.                 mode = word[1]
  1445.  
  1446.         id = self.get_id(nick=target)
  1447.         target,network = id
  1448.         ## check if there is a key
  1449.         key = self.find_key(id)
  1450.         if not key:
  1451.             print "No Key found for %r" % (target,)
  1452.         else:
  1453.             ## if no parameter show only status
  1454.             if len(word) == 1:
  1455.                 print "CBC Mode is %s" % ((key.cbc_mode and "on" or "off"),)
  1456.             else:
  1457.                 ## set cbc mode to on/off
  1458.                 key.cbc_mode = bool(mode in ONMODES)
  1459.                 print "set CBC Mode for %s to %s" % (target,(key.cbc_mode == True and "on") or "off")
  1460.                 ## save key storage
  1461.                 self.saveDB()
  1462.         return xchat.EAT_ALL
  1463.  
  1464.     ## set key protection mode or show the status
  1465.     def set_protect(self,word, word_eol, userdata):
  1466.         ## check for parameter
  1467.         if len(word) >2:
  1468.             # if both specified first is target second is mode on/off
  1469.             target = word[1]
  1470.             mode = word[2]
  1471.         else:
  1472.             ## if no target defined target is current nick, channel is not allowed/possible yet
  1473.             target = None
  1474.             if len(word) >1:
  1475.                 ## if one parameter set mode to it else show only
  1476.                 mode = word[1]
  1477.  
  1478.         id = self.get_id(nick=target)
  1479.         target,network = id
  1480.         if "#" in target:
  1481.             print "We don't make channel protection. Sorry!"
  1482.             return xchat.EAT_ALL
  1483.        
  1484.         key = self.find_key(id)
  1485.         ## check if there is a key
  1486.         if not key:
  1487.             print "No Key found for %r" % (target,)
  1488.         else:
  1489.             ## if no parameter show only status
  1490.             if len(word) == 1:
  1491.                 print "KEY Protection is %s" % ((key.protect_mode and "on" or "off"),)
  1492.             else:
  1493.                 ## set KEY Protection mode to on/off
  1494.                 key.protect_mode = bool(mode in ONMODES)
  1495.                 print "set KEY Protection for %s to %s" % (target,(key.protect_mode == True and "on") or "off")
  1496.                 ## save key storage
  1497.                 self.saveDB()
  1498.         return xchat.EAT_ALL
  1499.  
  1500.  
  1501.     ## activate/deaktivate encryption für chan/nick
  1502.     def set_act(self,word, word_eol, userdata):
  1503.         ## if two parameter first is target second is mode on/off
  1504.         if len(word) >2:
  1505.             target = word[1]
  1506.             mode = word[2]
  1507.         else:
  1508.             ## target is current chan/nick
  1509.             target = None
  1510.             if len(word) >1:
  1511.                 ## if one parameter set mode to on/off
  1512.                 mode = word[1]
  1513.  
  1514.         id = self.get_id(nick=target)
  1515.         target,network = id
  1516.         key = self.find_key(id)
  1517.         ## key not found
  1518.         if not key:
  1519.             print "No Key found for %r" % (target,)
  1520.         else:
  1521.             if len(word) == 1:
  1522.                 ## show only
  1523.                 print "Encryption is %s" % ((key.active and "on" or "off"),)
  1524.             else:
  1525.                 ## set mode to on/off
  1526.                 key.active = bool(mode in ONMODES)
  1527.                 print "set Encryption for %s to %s" % (target,(key.active == True and "on") or "off")
  1528.                 ## save key storage
  1529.                 self.saveDB()
  1530.         return xchat.EAT_ALL
  1531.  
  1532.     ## handle topic server message
  1533.     def server_332_topic(self,word, word_eol, userdata):
  1534.         ## check if allready processing
  1535.         if self.__chk_proc():
  1536.             return xchat.EAT_NONE
  1537.         server, cmd, nick, channel, topic = word[0], word[1], word[2], word[3], word_eol[4]
  1538.         ## check if topic is crypted
  1539.         if not topic.startswith(':+OK ') and not topic.startswith(':mcps '):
  1540.             return xchat.EAT_NONE
  1541.         id = self.get_id(nick=channel)
  1542.         ## look for a key
  1543.         key = self.find_key(id,create=SecretKey(None))
  1544.         ## if no key exit
  1545.         if not key.key:
  1546.             return xchat.EAT_NONE
  1547.         ## decrypt
  1548.         topic = self.decrypt(key, topic[1:])
  1549.         ##todo utf8 check for illegal chars
  1550.         if not topic:
  1551.             return xchat.EAT_NONE
  1552.         ## lock the target
  1553.         self.__lock_proc(True)
  1554.         ## send the message to xchat
  1555.         xchat.command('RECV %s %s %s %s :%s' % (server, cmd, nick, channel, topic.replace("\x00","")))
  1556.         ## release the lock
  1557.         self.__lock_proc(False)
  1558.         return xchat.EAT_ALL
  1559.  
  1560.     ## trace nick changes
  1561.     def nick_trace(self,word, word_eol, userdata):
  1562.         old, new = word[0], word[1]
  1563.         ## create id's for old and new nick
  1564.         oldid,newid = (self.get_id(nick=old),self.get_id(nick=new))
  1565.         target, network = newid
  1566.         networkmap = self.__TargetMap.get(network,None)
  1567.         if not networkmap:
  1568.             networkmap = {}
  1569.             self.__TargetMap[network] = networkmap
  1570.         key = self.__KeyMap.get(oldid,None)
  1571.         if not key:
  1572.             lastaxx,key = networkmap.get(old,(-1,None))
  1573.         if key:
  1574.             ## make the new nick the entry the old
  1575.             networkmap[new] = (int(time.time()),key)
  1576.             try:
  1577.                 del networkmap[old]
  1578.             except KeyError:
  1579.                 pass
  1580.             ## save key storage
  1581.             self.saveDB()
  1582.         return xchat.EAT_NONE
  1583.  
  1584. ## Preliminaries.
  1585.  
  1586. class MalformedError(Exception):
  1587.     pass
  1588.  
  1589.  
  1590. def sha256(s):
  1591.     """sha256"""
  1592.     return hashlib.sha256(s).digest()
  1593.  
  1594.  
  1595. def int2bytes(n):
  1596.     """Integer to variable length big endian."""
  1597.     if n == 0:
  1598.         return '\x00'
  1599.     b = []
  1600.     while n:
  1601.         b.insert(0,chr(n % 256))
  1602.         n /= 256
  1603.     return "".join(b)
  1604.  
  1605.  
  1606. def bytes2int(b):
  1607.     """Variable length big endian to integer."""
  1608.     n = 0
  1609.     for p in b:
  1610.         n *= 256
  1611.         n += ord(p)
  1612.     return n
  1613.  
  1614. def padto(msg, length):
  1615.     """Pads 'msg' with zeroes until it's length is divisible by 'length'.
  1616.    If the length of msg is already a multiple of 'length', does nothing."""
  1617.     L = len(msg)
  1618.     if L % length:
  1619.         msg = "%s%s" % (msg,'\x00' * (length - L % length))
  1620.     assert len(msg) % length == 0
  1621.     return msg
  1622.  
  1623. def cbc_encrypt(func, data, blocksize):
  1624.     """The CBC mode. The randomy generated IV is prefixed to the ciphertext.
  1625.    'func' is a function that encrypts data in ECB mode. 'data' is the
  1626.    plaintext. 'blocksize' is the block size of the cipher."""
  1627.     assert len(data) % blocksize == 0
  1628.    
  1629.     IV = os.urandom(blocksize)
  1630.     assert len(IV) == blocksize
  1631.    
  1632.     ciphertext = IV
  1633.     for block_index in xrange(len(data) / blocksize):
  1634.         xored = xorstring(data[:blocksize], IV)
  1635.         enc = func(xored)
  1636.        
  1637.         ciphertext += enc
  1638.         IV = enc
  1639.         data = data[blocksize:]
  1640.  
  1641.     assert len(ciphertext) % blocksize == 0
  1642.     return ciphertext
  1643.  
  1644.  
  1645. def cbc_decrypt(func, data, blocksize):
  1646.     """See cbc_encrypt."""
  1647.     assert len(data) % blocksize == 0
  1648.    
  1649.     IV = data[0:blocksize]
  1650.     data = data[blocksize:]
  1651.  
  1652.     plaintext = ''
  1653.     for block_index in xrange(len(data) / blocksize):
  1654.         temp = func(data[0:blocksize])
  1655.         temp2 = xorstring(temp, IV)
  1656.         plaintext += temp2
  1657.         IV = data[0:blocksize]
  1658.         data = data[blocksize:]
  1659.    
  1660.     assert len(plaintext) % blocksize == 0
  1661.     return plaintext
  1662.  
  1663.  
  1664. class Blowfish:
  1665.     def __init__(self, key=None):
  1666.         if key:
  1667.             self.blowfish = cBlowfish.new(key)
  1668.  
  1669.     def decrypt(self, data):
  1670.         return self.blowfish.decrypt(data)
  1671.    
  1672.     def encrypt(self, data):
  1673.         return self.blowfish.encrypt(data)
  1674.  
  1675.  
  1676. class BlowfishCBC:
  1677.    
  1678.     def __init__(self, key=None):
  1679.         if key:
  1680.             self.blowfish = cBlowfish.new(key)
  1681.  
  1682.     def decrypt(self, data):
  1683.         return cbc_decrypt(self.blowfish.decrypt, data, 8)
  1684.    
  1685.     def encrypt(self, data):
  1686.         return cbc_encrypt(self.blowfish.encrypt, data, 8)
  1687.  
  1688. ## blowcrypt, Fish etc.
  1689. # XXX: Unstable.
  1690. def blowcrypt_b64encode(s):
  1691.     """A non-standard base64-encode."""
  1692.     B64 = "./0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
  1693.     res = []
  1694.     while s:
  1695.         left, right = struct.unpack('>LL', s[:8])
  1696.         for i in xrange(6):
  1697.             res.append( B64[right & 0x3f] )
  1698.             right >>= 6
  1699.         for i in xrange(6):
  1700.             res.append( B64[left & 0x3f] )
  1701.             left >>= 6
  1702.         s = s[8:]
  1703.     return "".join(res)
  1704.  
  1705. def blowcrypt_b64decode(s):
  1706.     """A non-standard base64-decode."""
  1707.     B64 = "./0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
  1708.     res = []
  1709.     while s:
  1710.         left, right = 0, 0
  1711.         for i, p in enumerate(s[0:6]):
  1712.             right |= B64.index(p) << (i * 6)
  1713.         for i, p in enumerate(s[6:12]):
  1714.             left |= B64.index(p) << (i * 6)
  1715.         res.append( struct.pack('>LL', left, right) )
  1716.         s = s[12:]
  1717.     return "".join(res)
  1718.  
  1719. def blowcrypt_pack(msg, cipher):
  1720.     """."""
  1721.     return '+OK %s' % (blowcrypt_b64encode(cipher.encrypt(padto(msg, 8))))
  1722.  
  1723. def blowcrypt_unpack(msg, cipher):
  1724.     """."""
  1725.     if not (msg.startswith('+OK ') or msg.startswith('mcps ')):
  1726.         raise ValueError
  1727.     _, rest = msg.split(' ', 1)
  1728.     if (len(rest) % 12):
  1729.         raise MalformedError
  1730.  
  1731.     try:
  1732.         raw = blowcrypt_b64decode(rest)
  1733.     except TypeError:
  1734.         raise MalformedError
  1735.     if not raw:
  1736.         raise MalformedError
  1737.  
  1738.     try:
  1739.         plain = cipher.decrypt(raw)
  1740.     except ValueError:
  1741.         raise MalformedError
  1742.    
  1743.     return plain.strip('\x00')
  1744.  
  1745. ## Mircryption-CBC
  1746. def mircryption_cbc_pack(msg, cipher):
  1747.     """."""
  1748.     padded = padto(msg, 8)
  1749.     return '+OK *%s' % (base64.b64encode(cipher.encrypt(padded)))
  1750.  
  1751.  
  1752. def mircryption_cbc_unpack(msg, cipher):
  1753.     """."""
  1754.     if not (msg.startswith('+OK *') or msg.startswith('mcps *')):
  1755.         raise ValueError
  1756.  
  1757.     try:
  1758.         _, coded = msg.split('*', 1)
  1759.         raw = base64.b64decode(coded)
  1760.     except TypeError:
  1761.         raise MalformedError
  1762.     if not raw:
  1763.         raise MalformedError
  1764.  
  1765.     try:
  1766.         padded = cipher.decrypt(raw)
  1767.     except ValueError:
  1768.         raise MalformedError
  1769.     if not padded:
  1770.         raise MalformedError
  1771.  
  1772.     return padded.strip('\x00')
  1773.  
  1774. ## DH1080
  1775. g_dh1080 = 2
  1776. p_dh1080 = int('FBE1022E23D213E8ACFA9AE8B9DFAD'
  1777.                'A3EA6B7AC7A7B7E95AB5EB2DF85892'
  1778.                '1FEADE95E6AC7BE7DE6ADBAB8A783E'
  1779.                '7AF7A7FA6A2B7BEB1E72EAE2B72F9F'
  1780.                'A2BFB2A2EFBEFAC868BADB3E828FA8'
  1781.                'BADFADA3E4CC1BE7E8AFE85E9698A7'
  1782.                '83EB68FA07A77AB6AD7BEB618ACF9C'
  1783.                'A2897EB28A6189EFA07AB99A8A7FA9'
  1784.                'AE299EFA7BA66DEAFEFBEFBF0B7D8B', 16)
  1785. q_dh1080 = (p_dh1080 - 1) / 2
  1786.  
  1787. def dh1080_b64encode(s):
  1788.     """A non-standard base64-encode."""
  1789.     b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
  1790.     d = [0]*len(s)*2
  1791.  
  1792.     L = len(s) * 8
  1793.     m = 0x80
  1794.     i, j, k, t = 0, 0, 0, 0
  1795.     while i < L:
  1796.         if ord(s[i >> 3]) & m:
  1797.             t |= 1
  1798.         j += 1
  1799.         m >>= 1
  1800.         if not m:
  1801.             m = 0x80
  1802.         if not j % 6:
  1803.             d[k] = b64[t]
  1804.             t &= 0
  1805.             k += 1
  1806.         t <<= 1
  1807.         t %= 0x100
  1808.         #
  1809.         i += 1
  1810.     m = 5 - j % 6
  1811.     t <<= m
  1812.     t %= 0x100
  1813.     if m:
  1814.         d[k] = b64[t]
  1815.         k += 1
  1816.     d[k] = 0
  1817.     res = []
  1818.     for q in d:
  1819.         if q == 0:
  1820.             break
  1821.         res.append(q)
  1822.     return "".join(res)
  1823.  
  1824. def dh1080_b64decode(s):
  1825.     """A non-standard base64-encode."""
  1826.     b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
  1827.     buf = [0]*256
  1828.     for i in range(64):
  1829.         buf[ord(b64[i])] = i
  1830.  
  1831.     L = len(s)
  1832.     if L < 2:
  1833.         raise ValueError
  1834.     for i in reversed(range(L-1)):
  1835.         if buf[ord(s[i])] == 0:
  1836.             L -= 1
  1837.         else:
  1838.             break
  1839.     if L < 2:
  1840.         raise ValueError
  1841.  
  1842.     d = [0]*L
  1843.     i, k = 0, 0
  1844.     while True:
  1845.         i += 1
  1846.         if k + 1 < L:
  1847.             d[i-1] = buf[ord(s[k])] << 2
  1848.             d[i-1] %= 0x100
  1849.         else:
  1850.             break
  1851.         k += 1
  1852.         if k < L:
  1853.             d[i-1] |= buf[ord(s[k])] >> 4
  1854.         else:
  1855.             break
  1856.         i += 1
  1857.         if k + 1 < L:
  1858.             d[i-1] = buf[ord(s[k])] << 4
  1859.             d[i-1] %= 0x100
  1860.         else:
  1861.             break
  1862.         k += 1
  1863.         if k < L:
  1864.             d[i-1] |= buf[ord(s[k])] >> 2
  1865.         else:
  1866.             break
  1867.         i += 1
  1868.         if k + 1 < L:
  1869.             d[i-1] = buf[ord(s[k])] << 6
  1870.             d[i-1] %= 0x100
  1871.         else:
  1872.             break
  1873.         k += 1
  1874.         if k < L:
  1875.             d[i-1] |= buf[ord(s[k])] % 0x100
  1876.         else:
  1877.             break
  1878.         k += 1
  1879.     return ''.join(map(chr, d[0:i-1]))
  1880.  
  1881.  
  1882. def dh_validate_public(public, q, p):
  1883.     """See RFC 2631 section 2.1.5."""
  1884.     return 1 == pow(public, q, p)
  1885.  
  1886.  
  1887. class DH1080Ctx:
  1888.     """DH1080 context."""
  1889.     def __init__(self):
  1890.         self.public = 0
  1891.         self.private = 0
  1892.         self.secret = 0
  1893.         self.state = 0
  1894.        
  1895.         bits = 1080
  1896.         while True:
  1897.             self.private = bytes2int(os.urandom(bits/8))
  1898.             self.public = pow(g_dh1080, self.private, p_dh1080)
  1899.             if 2 <= self.public <= p_dh1080 - 1 and \
  1900.                dh_validate_public(self.public, q_dh1080, p_dh1080) == 1:
  1901.                 break
  1902.  
  1903. def dh1080_pack(ctx):
  1904.     """."""
  1905.     if ctx.state == 0:
  1906.         ctx.state = 1
  1907.         cmd = "DH1080_INIT"
  1908.     else:
  1909.         cmd = "DH1080_FINISH"
  1910.     return "%s %s" % (cmd,dh1080_b64encode(int2bytes(ctx.public)))
  1911.  
  1912. def dh1080_unpack(msg, ctx):
  1913.     """."""
  1914.     if not msg.startswith("DH1080_"):
  1915.         raise ValueError
  1916.  
  1917.     invalidmsg = "Key does not validate per RFC 2631. This check is not performed by any DH1080 implementation, so we use the key anyway. See RFC 2785 for more details."
  1918.  
  1919.     if ctx.state == 0:
  1920.         if not msg.startswith("DH1080_INIT "):
  1921.             raise MalformedError
  1922.         ctx.state = 1
  1923.         try:
  1924.             cmd, public_raw = msg.split(' ', 1)
  1925.             public = bytes2int(dh1080_b64decode(public_raw))
  1926.  
  1927.             if not 1 < public < p_dh1080:
  1928.                 raise MalformedError
  1929.            
  1930.             if not dh_validate_public(public, q_dh1080, p_dh1080):
  1931.                 print invalidmsg
  1932.                 pass
  1933.                
  1934.             ctx.secret = pow(public, ctx.private, p_dh1080)
  1935.         except:
  1936.             raise MalformedError
  1937.  
  1938.     elif ctx.state == 1:
  1939.         if not msg.startswith("DH1080_FINISH "):
  1940.             raise MalformedError
  1941.         ctx.state = 1
  1942.         try:
  1943.             cmd, public_raw = msg.split(' ', 1)
  1944.             public = bytes2int(dh1080_b64decode(public_raw))
  1945.  
  1946.             if not 1 < public < p_dh1080:
  1947.                 raise MalformedError
  1948.  
  1949.             if not dh_validate_public(public, q_dh1080, p_dh1080):
  1950.                 print invalidmsg
  1951.                 pass
  1952.            
  1953.             ctx.secret = pow(public, ctx.private, p_dh1080)
  1954.         except:
  1955.             raise MalformedError
  1956.  
  1957.     return True
  1958.        
  1959.  
  1960. def dh1080_secret(ctx):
  1961.     """."""
  1962.     if ctx.secret == 0:
  1963.         raise ValueError
  1964.     return dh1080_b64encode(sha256(int2bytes(ctx.secret)))
  1965.  
  1966. if REQUIRESETUP:
  1967.     __install_thread = None
  1968.     def _install_pyBlowfish(urllib2,doExtra):
  1969.         global __install_thread
  1970.         if __install_thread:
  1971.             print "Install is allready running"
  1972.             return
  1973.         __install_thread = Thread(target=__install_pyBlowfish,kwargs={'urllib2':urllib2,'context':xchat.get_context()})
  1974.         __install_thread.start()
  1975.     def __install_pyBlowfish(urllib2,context):
  1976.         global __install_thread
  1977.         context.prnt("\0038.....checking for pyBlowfish.py at %r... please wait ...." % PYBLOWFISHURL)
  1978.         try:
  1979.             __script = urllib2.urlopen(PYBLOWFISHURL,timeout=40).read()
  1980.             try:
  1981.                 __fd = open("%s%spyBlowfish.py" % (path,sep),"wb")
  1982.                 __fd.write(__script)
  1983.                 print "\002\0033Please type /py reload %s" % script
  1984.             finally:
  1985.                 __fd.close()
  1986.         except urllib2.URLError,err:
  1987.             print err
  1988.  
  1989.         except:
  1990.             context.prnt( "\002\0034INSTALL FAILED" )
  1991.             raise
  1992.         __install_thread = None
  1993.  
  1994.     def fishsetup(word, word_eol, userdata):
  1995.         useproxy = True
  1996.         if len(word) >1:
  1997.             if word[1].lower() == "noproxy":
  1998.                 useproxy = False
  1999.         proxyload(_install_pyBlowfish,useproxy,None)
  2000.         return xchat.EAT_ALL
  2001.     xchat.hook_command('FISHSETUP', fishsetup)
  2002. else:    
  2003.     loadObj = XChatCrypt()
  2004.  
  2005. # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
Add Comment
Please, Sign In to add comment