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