From 1c61ec105ecc1c46be3fd9861aa083c6078be6fe Mon Sep 17 00:00:00 2001 From: David Flatz Date: Wed, 28 Jan 2026 10:01:47 +0100 Subject: [PATCH] fish.py 1.0 Handle keys with cbc: prefix and implement encryption in cbc mode Set server so that we send our key exchange reply correctly Handle DH1080 exchange in cbc-mode Use string_eval_expression when deriving key so that we can use ${sec.data.foo} for keys Derive keys from weechat settings Annouce whether stuff is encrypted in ecb or cbc mode Provide bar item that shows current buffer mode (ecb, cbc, plaintext) Change some default values for bar item color Add different option for bar item indicator Make sure we create the new buffer on the correct server Show different color in indicator when we don't know the current state Store encryption state in buffer localvar Announce plaintext when state was unknown before Add note about secure data Use weechat to parse irc lines Provide a command to add the bar item to weechat.bar.status.items Add information about fish in irc tags and use a line hook to show it in the prefix Don't encrypt empty messages Make sure we remove fish tag of plaintext messages so that server cannot fool us Disable announce by default since prefix marker are nicer Try to import Cryptodome first Add second DH1080_FINISH step to key exchange that other implementations seem to do Try better to decrypt CBC messages that were cut off See https://github.com/freshprince/weechat-fish/compare/v0.15-weechat-scripts...v1.0-weechat-scripts for full changelog --- python/fish.py | 1117 ++++++++++++++++++++++++------------------------ 1 file changed, 553 insertions(+), 564 deletions(-) diff --git a/python/fish.py b/python/fish.py index 17506076..21324811 100644 --- a/python/fish.py +++ b/python/fish.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2011-2022 David Flatz +# Copyright (C) David Flatz # Copyright (C) 2017-2020 Marcin Kurczewski # Copyright (C) 2017 Ricardo Ferreira # Copyright (C) 2014 Charles Franklin @@ -22,19 +22,27 @@ # # -# NOTE: Blowfish and DH1080 implementation is licenced under a different -# license: +# Changelog, Suggestions, Bugs, ...? +# https://github.com/freshprince/weechat-fish # -# Copyright (c) 2009, Bjorn Edstrom + # -# Permission to use, copy, modify, and distribute this software for any -# purpose with or without fee is hereby granted, provided that the above -# copyright notice and this permission notice appear in all copies. +# HINTS: +# ===== # - +# Getting long lines cut off by the irc server? Try setting +# irc.*.split_msg_max_length to something smaller: +# /set irc.server_default.split_msg_max_length 400 +# +# You can have an indicator showing whether a key is set and messages in a +# buffer are encrypted by adding the fish item to a bar: +# /blowkey setup_bar_item +# +# If you want to keep the keys stored on disk to be encrypted you can use +# weechat secure data: +# /secure set fish.foo cbc:verysecr1tkey +# /blowkey set #foo ${sec.data.fish.foo} # -# Suggestions, Bugs, ...? -# https://github.com/freshprince/weechat-fish # # NOTE ABOUT DH1080: @@ -57,14 +65,17 @@ import hashlib import base64 import sys +import traceback from os import urandom SCRIPT_NAME = "fish" SCRIPT_AUTHOR = "David Flatz " -SCRIPT_VERSION = "0.15" +SCRIPT_VERSION = "1.0" SCRIPT_LICENSE = "GPL3" SCRIPT_DESC = "FiSH for weechat" CONFIG_FILE_NAME = SCRIPT_NAME +BAR_ITEM_NAME = SCRIPT_NAME +TAG_NAME = SCRIPT_NAME import_ok = True @@ -76,10 +87,10 @@ import_ok = False try: - import Crypto.Cipher.Blowfish as CryptoBlowfish + import Cryptodome.Cipher.Blowfish as CryptoBlowfish except ImportError: try: - import Cryptodome.Cipher.Blowfish as CryptoBlowfish + import Crypto.Cipher.Blowfish as CryptoBlowfish except ImportError: print("Pycryptodome must be installed to use fish") import_ok = False @@ -90,15 +101,10 @@ # fish_config_file = None -fish_config_section = {} fish_config_option = {} -fish_keys = {} -fish_cyphers = {} +fish_config_keys = None fish_DH1080ctx = {} -fish_encryption_announced = {} - -fish_secure_key = "" -fish_secure_cipher = None +fish_bar_item = None # @@ -109,120 +115,203 @@ def fish_config_reload_cb(data, config_file): return weechat.config_reload(config_file) -def fish_config_keys_read_cb(data, config_file, section_name, option_name, - value): - global fish_keys +def fish_config_keys_create_cb(data, config_file, section, option_name, value): + secure_key = fish_secure_key() + if secure_key and not value.startswith('+OK '): + value = blowcrypt_pack(value.encode(), secure_key) + option_name = blowcrypt_pack(option_name.encode(), secure_key) + + option = weechat.config_search_option(config_file, section, option_name) + if option: + return weechat.config_option_set(option, value, 1) option = weechat.config_new_option( - config_file, section_name, option_name, "string", "key", "", 0, 0, - "", value, 0, "", "", "", "", "", "") + config_file, section, option_name, "string", "", "", 0, 0, "", + value, 0, "", "", "", "", "", "") if not option: return weechat.WEECHAT_CONFIG_OPTION_SET_ERROR - fish_keys[option_name] = value - - return weechat.WEECHAT_CONFIG_OPTION_SET_OK_CHANGED + weechat.bar_item_update(BAR_ITEM_NAME) + return weechat.WEECHAT_CONFIG_OPTION_SET_OK_SAME_VALUE -def fish_config_keys_write_cb(data, config_file, section_name): - global fish_keys, fish_secure_cipher - - weechat.config_write_line(config_file, section_name, "") - for target, key in sorted(fish_keys.items()): - if fish_secure_cipher is not None: - weechat.config_write_line( - config_file, blowcrypt_pack(target.encode(), fish_secure_cipher), - blowcrypt_pack(key.encode(), fish_secure_cipher)) - else: - weechat.config_write_line(config_file, target, key) - - return weechat.WEECHAT_RC_OK +def fish_config_keys_delete_cb(data, config_file, section, option): + option_name = weechat.config_option_get_string(option, 'name') + secure_key = fish_secure_key() + if secure_key: + try: + option_name, _ = blowcrypt_unpack(option_name, secure_key) + option_name = option_name.decode() + except Exception: + pass + weechat.config_option_free(option) + server, name = option_name.split('/', 1) + buffer = weechat.info_get("irc_buffer", f"{server},{name}") + if buffer: + fish_state_set(buffer, None) + return weechat.WEECHAT_CONFIG_OPTION_UNSET_OK_REMOVED def fish_config_init(): - global fish_config_file, fish_config_section, fish_config_option - global fish_secure_cipher + global fish_config_file, fish_config_option, fish_config_keys fish_config_file = weechat.config_new( - CONFIG_FILE_NAME, "fish_config_reload_cb", "") + CONFIG_FILE_NAME, "fish_config_reload_cb", "") if not fish_config_file: return # look - fish_config_section["look"] = weechat.config_new_section( - fish_config_file, "look", 0, 0, "", "", "", "", "", "", "", "", "", - "") - if not fish_config_section["look"]: + section_look = weechat.config_new_section( + fish_config_file, "look", 0, 0, "", "", "", "", "", "", "", "", "", "") + if not section_look: weechat.config_free(fish_config_file) return fish_config_option["announce"] = weechat.config_new_option( - fish_config_file, fish_config_section["look"], "announce", - "boolean", "announce if messages are being encrypted or not", "", - 0, 0, "on", "on", 0, "", "", "", "", "", "") + fish_config_file, section_look, "announce", "boolean", + "announce if messages are being encrypted or not", "", 0, 0, + "off", "off", 0, "", "", "", "", "", "") fish_config_option["marker"] = weechat.config_new_option( - fish_config_file, fish_config_section["look"], "marker", - "string", "marker for important FiSH messages", "", 0, 0, - "O<", "O<", 0, "", "", "", "", "", "") + fish_config_file, section_look, "marker", + "string", "marker for important FiSH messages", "", 0, 0, + "O<", "O<", 0, "", "", "", "", "", "") + fish_config_option["item"] = weechat.config_new_option( + fish_config_file, section_look, "item", "string", + "string used to show FiSH being used in current buffer", "", 0, 0, + "%", "%", 0, "", "", "", "", "", "") + fish_config_option["prefix"] = weechat.config_new_option( + fish_config_file, section_look, "prefix", "boolean", + "mark in prefix if message is encrypted or not", "", 0, 0, + "on", "on", 0, "", "", "", "", "", "") + fish_config_option["prefix_plaintext"] = weechat.config_new_option( + fish_config_file, section_look, "prefix.plaintext", "string", + "marker in prefix if message is plaintext", "", 0, 0, + "‼", "‼", 0, "", "", "", "", "", "") + fish_config_option["prefix_ecb"] = weechat.config_new_option( + fish_config_file, section_look, "prefix.ecb", "string", + "marker in prefix if message is encrypted in ecb mode", "", 0, 0, + "°", "°", 0, "", "", "", "", "", "") + fish_config_option["prefix_cbc"] = weechat.config_new_option( + fish_config_file, section_look, "prefix.cbc", "string", + "marker in prefix if message is encrypted in cbc mode", "", 0, 0, + "·", "·", 0, "", "", "", "", "", "") fish_config_option["mark_position"] = weechat.config_new_option( - fish_config_file, fish_config_section["look"], "mark_position", - "integer", - "put marker for encrypted INCOMING messages at start or end", - "off|begin|end", 0, 2, "off", "off", 0, "", "", "", "", "", "") + fish_config_file, section_look, "mark_position", "integer", + "put marker for encrypted INCOMING messages at start or end", + "off|begin|end", 0, 2, "off", "off", 0, "", "", "", "", "", "") fish_config_option["mark_encrypted"] = weechat.config_new_option( - fish_config_file, fish_config_section["look"], "mark_encrypted", - "string", "marker for encrypted INCOMING messages", "", 0, 0, - "*", "*", 0, "", "", "", "", "", "") + fish_config_file, section_look, "mark_encrypted", "string", + "marker for encrypted INCOMING messages", "", 0, 0, + "*", "*", 0, "", "", "", "", "", "") # color - fish_config_section["color"] = weechat.config_new_section( - fish_config_file, "color", 0, 0, "", "", "", "", "", "", "", "", - "", "") - if not fish_config_section["color"]: + section_color = weechat.config_new_section( + fish_config_file, "color", 0, 0, "", "", "", "", "", "", "", "", "", + "") + if not section_color: weechat.config_free(fish_config_file) return fish_config_option["alert"] = weechat.config_new_option( - fish_config_file, fish_config_section["color"], "alert", - "color", "color for important FiSH message markers", "", 0, 0, - "lightblue", "lightblue", 0, "", "", "", "", "", "") + fish_config_file, section_color, "alert", + "color", "color for important FiSH message markers", "", 0, 0, + "lightblue", "lightblue", 0, "", "", "", "", "", "") + fish_config_option["unknown"] = weechat.config_new_option( + fish_config_file, section_color, "unknown", "color", + "color for bar item when state of encryption is unknown", "", 0, 0, + "darkgray", "darkgray", 0, "", "", "", "", "", "") + fish_config_option["plaintext"] = weechat.config_new_option( + fish_config_file, section_color, "plaintext", "color", + "color for bar item when messages are in plain text", "", 0, 0, + "*red", "*red", 0, "", "", "", "", "", "") + fish_config_option["ecb"] = weechat.config_new_option( + fish_config_file, section_color, "ecb", "color", + "color for bar item when messages are encrypted in ECB mode", "", 0, 0, + "lightblue", "lightblue", 0, "", "", "", "", "", "") + fish_config_option["cbc"] = weechat.config_new_option( + fish_config_file, section_color, "cbc", "color", + "color for bar item when messages are encrypted in CBC mode", "", 0, 0, + "green", "green", 0, "", "", "", "", "", "") # secure - fish_config_section["secure"] = weechat.config_new_section( - fish_config_file, "secure", 0, 0, "", "", "", "", "", "", "", "", - "", "") - if not fish_config_section["secure"]: + section_secure = weechat.config_new_section( + fish_config_file, "secure", 0, 0, "", "", "", "", "", "", "", "", "", + "") + if not section_secure: weechat.config_free(fish_config_file) return fish_config_option["key"] = weechat.config_new_option( - fish_config_file, fish_config_section["secure"], "key", - "string", "key for securing blowfish keys", "", 0, 0, "", "", - 0, "", "", "", "", "", "") + fish_config_file, section_secure, "key", + "string", "key for encrypting blowfish keys", "", 0, 0, + "", "", 0, "", "", "", "", "", "") # keys - fish_config_section["keys"] = weechat.config_new_section( - fish_config_file, "keys", 0, 0, "fish_config_keys_read_cb", "", - "fish_config_keys_write_cb", "", "", "", "", "", "", "") - if not fish_config_section["keys"]: + fish_config_keys = weechat.config_new_section( + fish_config_file, "keys", 1, 1, "", "", "", "", "", "", + "fish_config_keys_create_cb", "", "fish_config_keys_delete_cb", "") + if not fish_config_keys: weechat.config_free(fish_config_file) return def fish_config_read(): - global fish_config_file - return weechat.config_read(fish_config_file) def fish_config_write(): - global fish_config_file - return weechat.config_write(fish_config_file) +def fish_key_set(target: str, key: str, cbc: bool): + value = f"cbc:{key}" if cbc else key + target = target.lower() + + return fish_config_keys_create_cb( + "", fish_config_file, fish_config_keys, target, value) + + +def fish_key_get(target: str): + target = target.lower() + if secure_key := fish_secure_key(): + target = blowcrypt_pack(target.encode(), secure_key) + + option = weechat.config_search_option( + fish_config_file, fish_config_keys, target) + if not option: + return None + + key = weechat.config_string(option) + if secure_key: + key, _ = blowcrypt_unpack(key, secure_key) + key = key.decode() + key = weechat.string_eval_expression( + key, {}, {}, {}) + cbc = False + if key.startswith('cbc:'): + cbc = True + key = key[4:] + + return (key, cbc) + + +def fish_key_delete(target: str): + target = target.lower() + if secure_key := fish_secure_key(): + target = blowcrypt_pack(target.encode(), secure_key) + + option = weechat.config_search_option( + fish_config_file, fish_config_keys, target) + if option: + fish_config_keys_delete_cb( + "", fish_config_file, fish_config_keys, option) + return True + + return False + + ## # Blowfish and DH1080 Code: ## @@ -237,7 +326,7 @@ def __init__(self, key=None): if len(key) > 72: key = key[:72] self.blowfish = CryptoBlowfish.new( - key.encode('utf-8'), CryptoBlowfish.MODE_ECB) + key.encode('utf-8'), CryptoBlowfish.MODE_ECB) def decrypt(self, data): return self.blowfish.decrypt(data) @@ -253,10 +342,10 @@ def blowcrypt_b64encode(s): res = '' while s: left, right = struct.unpack('>LL', s[:8]) - for i in range(6): + for _ in range(6): res += B64[right & 0x3f] right >>= 6 - for i in range(6): + for _ in range(6): res += B64[left & 0x3f] left >>= 6 s = s[8:] @@ -291,32 +380,44 @@ def padto(msg, length): return msg -def blowcrypt_pack(msg, cipher): +def blowcrypt_pack(msg, key, cbc=False): """.""" + if cbc: + cipher = CryptoBlowfish.new( + key.encode('utf-8'), CryptoBlowfish.MODE_CBC) + return '+OK *' + base64.b64encode( + cipher.iv + cipher.encrypt(padto(msg, 8))).decode('utf-8') + + cipher = Blowfish(key) return '+OK ' + blowcrypt_b64encode(cipher.encrypt(padto(msg, 8))) -def blowcrypt_unpack(msg, cipher, key): +def blowcrypt_unpack(msg, key): """.""" if not (msg.startswith('+OK ') or msg.startswith('mcps ')): raise ValueError _, rest = msg.split(' ', 1) if rest.startswith('*'): # CBC mode + cbc = True rest = rest[1:] - if len(rest) % 4: + if len(rest) % 4 == 1: + rest = rest[:-1] + elif len(rest) % 4: rest += '=' * (4 - len(rest) % 4) raw = base64.b64decode(rest) iv = raw[:8] raw = raw[8:] - cbcCipher = CryptoBlowfish.new( - key.encode('utf-8'), CryptoBlowfish.MODE_CBC, iv) + cipher = CryptoBlowfish.new( + key.encode('utf-8'), CryptoBlowfish.MODE_CBC, iv) - plain = cbcCipher.decrypt(padto(raw, 8)) + plain = cipher.decrypt(padto(raw, 8)) else: + cbc = False + cipher = Blowfish(key) if len(rest) < 12: raise ValueError @@ -326,14 +427,14 @@ def blowcrypt_unpack(msg, cipher, key): try: raw = blowcrypt_b64decode(padto(rest, 12)) - except TypeError: - raise ValueError + except TypeError as e: + raise ValueError from e if not raw: raise ValueError plain = cipher.decrypt(raw) - return plain.strip(b'\x00').replace(b'\n', b'') + return (plain.strip(b'\x00').replace(b'\n', b''), cbc) # @@ -456,11 +557,13 @@ def dh_validate_public(public, q, p): class DH1080Ctx: """DH1080 context.""" - def __init__(self): + + def __init__(self, cbc=True): self.public = 0 self.private = 0 self.secret = 0 self.state = 0 + self.cbc = cbc bits = 1080 while True: @@ -473,13 +576,16 @@ def __init__(self): def dh1080_pack(ctx): """.""" - cmd = None if ctx.state == 0: ctx.state = 1 cmd = "DH1080_INIT " - else: + elif ctx.state <= 2: + ctx.state += 1 cmd = "DH1080_FINISH " - return cmd + dh1080_b64encode(int2bytes(ctx.public)) + else: + return None + return cmd + dh1080_b64encode(int2bytes(ctx.public)) + ( + " CBC" if ctx.cbc else "") def dh1080_unpack(msg, ctx): @@ -488,34 +594,42 @@ def dh1080_unpack(msg, ctx): raise ValueError if ctx.state == 0: - if not msg.startswith("DH1080_INIT "): + if (not msg.startswith("DH1080_INIT ") and + not msg.startswith("DH1080_INIT_CBC ")): raise ValueError ctx.state = 1 try: - cmd, public_raw = msg.split(' ', 1) + cmd, public_raw, *rest = msg.split(' ') public = bytes2int(dh1080_b64decode(public_raw)) if not 1 < public < p_dh1080: raise ValueError ctx.secret = pow(public, ctx.private, p_dh1080) - except Exception: - raise ValueError + ctx.cbc = "CBC" in rest or cmd == "DH1080_INIT_CBC" + + except Exception as e: + raise ValueError from e - elif ctx.state == 1: + elif ctx.state <= 2: if not msg.startswith("DH1080_FINISH "): raise ValueError - ctx.state = 1 + ctx.state += 1 try: - cmd, public_raw = msg.split(' ', 1) + cmd, public_raw, *rest = msg.split(' ') public = bytes2int(dh1080_b64decode(public_raw)) if not 1 < public < p_dh1080: raise ValueError ctx.secret = pow(public, ctx.private, p_dh1080) - except Exception: - raise ValueError + ctx.cbc = "CBC" in rest + + except Exception as e: + raise ValueError from e + + else: + raise ValueError return True @@ -549,356 +663,251 @@ def sha256(s): # HOOKS # -def fish_secure_key_cb(data, option, value): - global fish_secure_key, fish_secure_cipher - - fish_secure_key = weechat.config_string( - weechat.config_get("fish.secure.key")) - - if fish_secure_key == "": - fish_secure_cipher = None - return weechat.WEECHAT_RC_OK - - if fish_secure_key[:6] == "${sec.": - decrypted = weechat.string_eval_expression(fish_secure_key, {}, {}, {}) - if decrypted: - fish_secure_cipher = Blowfish(decrypted) - return weechat.WEECHAT_RC_OK - else: - weechat.config_option_set(fish_config_option["key"], "", 0) - weechat.prnt("", "Decrypt sec.conf first\n") - return weechat.WEECHAT_RC_OK - - if fish_secure_key != "": - fish_secure_cipher = Blowfish(fish_secure_key) - - return weechat.WEECHAT_RC_OK - - def fish_modifier_in_notice_cb(data, modifier, server_name, string): - global fish_DH1080ctx, fish_keys, fish_cyphers - - if type(string) is bytes: - return string - - match = re.match( - r"^((?:@[^ ]* )?:(.*?)!.*? NOTICE (.*?) :)" - r"((DH1080_INIT |DH1080_FINISH |\+OK |mcps )?.*)$", - string) - # match.group(0): message - # match.group(1): msg without payload - # match.group(2): source - # match.group(3): target - # match.group(4): msg - # match.group(5): "DH1080_INIT "|"DH1080_FINISH "|"+OK "|"mcps " - if not match or not match.group(5): + if isinstance(string, bytes): return string - if match.group(3) != weechat.info_get("irc_nick", server_name): - return string - - target = "%s/%s" % (server_name, match.group(2)) - targetl = ("%s/%s" % (server_name, match.group(2))).lower() - buffer = weechat.info_get("irc_buffer", "%s,%s" % ( - server_name, match.group(2))) - - if match.group(5) == "DH1080_FINISH " and targetl in fish_DH1080ctx: - if not dh1080_unpack(match.group(4), fish_DH1080ctx[targetl]): - fish_announce_unencrypted(buffer, target) - return string + msg_info = weechat.info_get_hashtable('irc_message_parse', { + 'message': string, + 'server': server_name, + }) - fish_alert(buffer, "Key exchange for %s successful" % target) - - fish_keys[targetl] = dh1080_secret(fish_DH1080ctx[targetl]) - if targetl in fish_cyphers: - del fish_cyphers[targetl] - del fish_DH1080ctx[targetl] + is_direct = msg_info['channel'] == weechat.info_get( + 'irc_nick', server_name) + if is_direct: + dest = msg_info['nick'] + else: + dest = msg_info['channel'] + target = f'{server_name}/{dest}' + buffer = weechat.info_get("irc_buffer", f'{server_name},{dest}') + + text = msg_info['text'] + if (is_direct and text.startswith('DH1080_FINISH ') and + target in fish_DH1080ctx and + dh1080_unpack(text, fish_DH1080ctx[target])): + if reply := dh1080_pack(fish_DH1080ctx[target]): + fish_key_delete(target) + weechat.command( + buffer, f'/mute notice -server {server_name} {dest} {reply}') + fish_alert(buffer, f'Key exchange with {target} successful.') + fish_key_set(target, dh1080_secret(fish_DH1080ctx[target]), + fish_DH1080ctx[target].cbc) + del fish_DH1080ctx[target] return "" - if match.group(5) == "DH1080_INIT ": - fish_DH1080ctx[targetl] = DH1080Ctx() - - msg = ' '.join(match.group(4).split()[0:2]) - - if not dh1080_unpack(msg, fish_DH1080ctx[targetl]): - fish_announce_unencrypted(buffer, target) - return string - - reply = dh1080_pack(fish_DH1080ctx[targetl]) - - fish_alert(buffer, "Key exchange initiated by %s. Key set." % target) - - weechat.command(buffer, "/mute -all notice %s %s" % ( - match.group(2), reply)) - - fish_keys[targetl] = dh1080_secret(fish_DH1080ctx[targetl]) - if targetl in fish_cyphers: - del fish_cyphers[targetl] - del fish_DH1080ctx[targetl] + if ( + is_direct and + (text.startswith('DH1080_INIT ') or + text.startswith('DH1080_INIT_CBC ')) and + fish_DH1080ctx.__setitem__(target, DH1080Ctx()) is None and + dh1080_unpack(text, fish_DH1080ctx[target])): + reply = dh1080_pack(fish_DH1080ctx[target]) + fish_key_delete(target) + weechat.command( + buffer, f'/mute notice -server {server_name} {dest} {reply}') + fish_key_set(target, dh1080_secret( + fish_DH1080ctx[target]), fish_DH1080ctx[target].cbc) + fish_alert(buffer, f'Key exchange initiated by {target}. Key set.') return "" - if match.group(5) in ["+OK ", "mcps "]: - if targetl not in fish_keys: - fish_announce_unencrypted(buffer, target) - return string - - key = fish_keys[targetl] - - try: - if targetl not in fish_cyphers: - b = Blowfish(key) - fish_cyphers[targetl] = b - else: - b = fish_cyphers[targetl] - - clean = blowcrypt_unpack(match.group(4), b, key) - - fish_announce_encrypted(buffer, target) - - return b"%s%s" % ( - match.group(1).encode(), fish_msg_w_marker(clean)) - except Exception as e: - fish_announce_unencrypted(buffer, target) - - raise e - - fish_announce_unencrypted(buffer, target) - - return string - - -def fish_modifier_in_privmsg_cb(data, modifier, server_name, string): - global fish_keys, fish_cyphers - - if type(string) is bytes: - return string - - match = re.match( - r"^((?:@[^ ]* )?:(.*?)!.*? PRIVMSG (.*?) :)(\x01ACTION )?" - r"((\+OK |mcps )?.*?)(\x01)?$", - string) - # match.group(0): message - # match.group(1): msg without payload - # match.group(2): source - # match.group(3): target - # match.group(4): action - # match.group(5): msg - # match.group(6): "+OK "|"mcps " - if not match: + key = fish_key_get(target) + if key is None: return string - - if match.group(3) == weechat.info_get("irc_nick", server_name): - dest = match.group(2) - else: - dest = match.group(3) - target = "%s/%s" % (server_name, dest) - targetl = ("%s/%s" % (server_name, dest)).lower() - buffer = weechat.info_get("irc_buffer", "%s,%s" % (server_name, dest)) - - if not match.group(6): + if not (text.startswith('+OK ') or text.startswith('mcps ')): fish_announce_unencrypted(buffer, target) - - return string - - if targetl not in fish_keys: - fish_announce_unencrypted(buffer, target) - - return string - - key = fish_keys[targetl] + return fish_tag(string) try: - if targetl not in fish_cyphers: - b = Blowfish(key) - fish_cyphers[targetl] = b - else: - b = fish_cyphers[targetl] - - clean = blowcrypt_unpack(match.group(5), b, key) - - fish_announce_encrypted(buffer, target) - - if not match.group(4): - return b'%s%s' % ( - match.group(1).encode(), fish_msg_w_marker(clean)) - - return b"%s%s%s\x01" % ( - match.group(1).encode(), match.group(4).encode(), - fish_msg_w_marker(clean)) - - except Exception as e: + key, cbc = key + clean, cbc = blowcrypt_unpack(text, key) + clean = fish_msg_w_marker(clean) + preamble = fish_tag( + string[0:int(msg_info['pos_text'])], + 'cbc' if cbc else 'ecb') + fish_announce_encrypted(buffer, target, cbc) + + return b'%s%s' % (preamble.encode(), clean) + + except Exception: + fish_alert('', traceback.format_exc()) fish_announce_unencrypted(buffer, target) + return fish_tag(string) - raise e - - -def fish_modifier_in_topic_cb(data, modifier, server_name, string): - global fish_keys, fish_cyphers - - if type(string) is bytes: - return string - match = re.match(r"^((?:@[^ ]* )?:.*?!.*? TOPIC (.*?) :)((\+OK |mcps )?.*)$", string) - # match.group(0): message - # match.group(1): msg without payload - # match.group(2): channel - # match.group(3): topic - # match.group(4): "+OK "|"mcps " - if not match: +def fish_modifier_in_privmsg_cb(data, modifier, server_name, string): + if isinstance(string, bytes): return string - target = "%s/%s" % (server_name, match.group(2)) - targetl = ("%s/%s" % (server_name, match.group(2))).lower() - buffer = weechat.info_get("irc_buffer", "%s,%s" % ( - server_name, match.group(2))) - - if targetl not in fish_keys or not match.group(4): - fish_announce_unencrypted(buffer, target) + msg_info = weechat.info_get_hashtable('irc_message_parse', { + 'message': string, + 'server': server_name, + }) + if msg_info['channel'] == weechat.info_get('irc_nick', server_name): + dest = msg_info['nick'] + else: + dest = msg_info['channel'] + target = f'{server_name}/{dest}' + buffer = weechat.info_get('irc_buffer', f'{server_name},{dest}') + key = fish_key_get(target) + if key is None: return string - key = fish_keys[targetl] + key, cbc = key + text = msg_info['text'] + is_action = text.startswith("\x01ACTION ") and text.endswith("\x01") + if is_action: + text = text[8:-1] + if not (text.startswith('+OK ') or text.startswith('mcps ')): + fish_announce_unencrypted(buffer, target) + return fish_tag(string) try: - if targetl not in fish_cyphers: - b = Blowfish(key) - fish_cyphers[targetl] = b - else: - b = fish_cyphers[targetl] - - clean = blowcrypt_unpack(match.group(3), b, key) - - fish_announce_encrypted(buffer, target) - - return b"%s%s" % (match.group(1).encode(), fish_msg_w_marker(clean)) - except Exception as e: + clean, cbc = blowcrypt_unpack(text, key) + clean = fish_msg_w_marker(clean) + if is_action: + clean = b"\x01ACTION %s\x01" % clean + preamble = fish_tag( + string[0:int(msg_info['pos_text'])], + 'cbc' if cbc else 'ecb') + fish_announce_encrypted(buffer, target, cbc) + + return b"%s%s" % ( + preamble.encode(), clean) + + except Exception: + fish_alert('', traceback.format_exc()) fish_announce_unencrypted(buffer, target) + return fish_tag(string) - raise e - -def fish_modifier_in_332_cb(data, modifier, server_name, string): - global fish_keys, fish_cyphers - - if type(string) is bytes: +def fish_modifier_in_decrypt_cb(data, modifier, server_name, string): + if isinstance(string, bytes): return string - match = re.match(r"^((?:@[^ ]* )?:.*? 332 .*? (.*?) :)((\+OK |mcps )?.*)$", string) - if not match: - return string + msg_info = weechat.info_get_hashtable('irc_message_parse', { + 'message': string, + 'server': server_name, + }) - target = "%s/%s" % (server_name, match.group(2)) - targetl = ("%s/%s" % (server_name, match.group(2))).lower() - buffer = weechat.info_get("irc_buffer", "%s,%s" % ( - server_name, match.group(2))) - - if targetl not in fish_keys or not match.group(4): - fish_announce_unencrypted(buffer, target) + target = f"{server_name}/{msg_info['channel']}" + buffer = weechat.info_get( + "irc_buffer", f"{server_name},{msg_info['channel']}") + key = fish_key_get(target) + text = msg_info['text'] + if key is None: return string - key = fish_keys[targetl] + key, cbc = key + if not text: + return fish_tag(string, 'cbc' if cbc else 'ecb') + if not (text.startswith('+OK ') or text.startswith('mcps ')): + fish_announce_unencrypted(buffer, target) + return fish_tag(string) try: - if targetl not in fish_cyphers: - b = Blowfish(key) - fish_cyphers[targetl] = b - else: - b = fish_cyphers[targetl] - - clean = blowcrypt_unpack(match.group(3), b, key) + clean, cbc = blowcrypt_unpack(text, key) + clean = fish_msg_w_marker(clean) + preamble = fish_tag( + string[0:int(msg_info['pos_text'])], + 'cbc' if cbc else 'ecb') + fish_announce_encrypted(buffer, target, cbc) - fish_announce_encrypted(buffer, target) + return b"%s%s" % (preamble.encode(), clean) - return b"%s%s" % (match.group(1).encode(), fish_msg_w_marker(clean)) - except Exception as e: + except Exception: + fish_alert('', traceback.format_exc()) fish_announce_unencrypted(buffer, target) - - raise e + return fish_tag(string) -def fish_modifier_out_privmsg_cb(data, modifier, server_name, string): - global fish_keys, fish_cyphers - - if type(string) is bytes: - return string - - match = re.match(r"^(PRIVMSG (.*?) :)(.*)$", string) - if not match: +def fish_modifier_out_encrypt_cb(data, modifier, server_name, string): + if isinstance(string, bytes): return string - target = "%s/%s" % (server_name, match.group(2)) - targetl = ("%s/%s" % (server_name, match.group(2))).lower() - buffer = weechat.info_get("irc_buffer", "%s,%s" % ( - server_name, match.group(2))) + msg_info = weechat.info_get_hashtable('irc_message_parse', { + 'message': string, + 'server': server_name, + }) - if targetl not in fish_keys: - fish_announce_unencrypted(buffer, target) + target = f"{server_name}/{msg_info['channel']}" + buffer = weechat.info_get( + "irc_buffer", f"{server_name},{msg_info['channel']}") + key = fish_key_get(target) + text = msg_info['text'] + if key is None: return string - if targetl not in fish_cyphers: - b = Blowfish(fish_keys[targetl]) - fish_cyphers[targetl] = b - else: - b = fish_cyphers[targetl] - cypher = blowcrypt_pack(fish_msg_wo_marker(match.group(3)).encode(), b) + key, cbc = key + text = fish_msg_wo_marker(msg_info['text']) + cypher = blowcrypt_pack(text.encode(), key, cbc) if text else '' + preamble = string[0:int(msg_info['pos_text'])] if text else string + fish_announce_encrypted(buffer, target, cbc) - fish_announce_encrypted(buffer, target) + return f'{preamble}{cypher}' - return "%s%s" % (match.group(1), cypher) +def fish_line_cb(data: str, line): + buffer = line['buffer'] + server_name = weechat.buffer_get_string(buffer, "localvar_server") + target_user = weechat.buffer_get_string(buffer, "localvar_channel") + target = f'{server_name}/{target_user}' + key = fish_key_get(target) + if key is None: + return {} + if not weechat.config_boolean(fish_config_option['prefix']): + return {} + state = 'plaintext' + for tag in line['tags'].split(','): + if tag.startswith('irc_tag_fish='): + _, state = tag.split('=', 1) + if tag == 'self_msg': + _, cbc = key + state = 'cbc' if cbc else 'ecb' -def fish_modifier_out_topic_cb(data, modifier, server_name, string): - global fish_keys, fish_cyphers + item = weechat.config_string(fish_config_option[f'prefix_{state}']) + color = weechat.color( + weechat.config_color(fish_config_option[state])) + return {'prefix': line['prefix'] + f'{color}{item}'} - if type(string) is bytes: - return string - match = re.match(r"^(TOPIC (.*?) :)(.*)$", string) - if not match: - return string - if not match.group(3): - return string - - target = "%s/%s" % (server_name, match.group(2)) - targetl = ("%s/%s" % (server_name, match.group(2))).lower() - buffer = weechat.info_get("irc_buffer", "%s,%s" % ( - server_name, match.group(2))) +def fish_bar_cb(data, item, window, buffer, extra_info): + server_name = weechat.buffer_get_string(buffer, "localvar_server") + target_user = weechat.buffer_get_string(buffer, "localvar_channel") + target = f"{server_name}/{target_user}" - if targetl not in fish_keys: - fish_announce_unencrypted(buffer, target) + if fish_key_get(target) is None: + return '' - return string + state = fish_state_get(buffer, 'unknown') + item = weechat.config_string(fish_config_option['item']) + color = weechat.color(weechat.config_color(fish_config_option[state])) - if targetl not in fish_cyphers: - b = Blowfish(fish_keys[targetl]) - fish_cyphers[targetl] = b - else: - b = fish_cyphers[targetl] - cypher = blowcrypt_pack(match.group(3).encode(), b) - - fish_announce_encrypted(buffer, target) - - return "%s%s" % (match.group(1), cypher) + return f"{color}{item}" def fish_modifier_input_text(data, modifier, server_name, string): if weechat.string_is_command_char(string): return string + buffer = weechat.current_buffer() - name = weechat.buffer_get_string(buffer, "name") - target = name.replace(".", "/") - targetl = target.lower() - if targetl not in fish_keys: + server_name = weechat.buffer_get_string(buffer, 'localvar_server') + target_user = weechat.buffer_get_string(buffer, 'localvar_channel') + target = f'{server_name}/{target_user}' + + if fish_key_get(target.lower()) is None: return string - return "%s" % (fish_msg_w_marker(string.encode()).decode()) + + return fish_msg_w_marker(string.encode()).decode() def fish_unload_cb(): fish_config_write() + weechat.bar_item_remove(fish_bar_item) return weechat.WEECHAT_RC_OK @@ -908,20 +917,44 @@ def fish_unload_cb(): # def fish_cmd_blowkey(data, buffer, args): - global fish_keys, fish_cyphers, fish_DH1080ctx, fish_config_option - global fish_secure_cipher - - if args == "" or args == "list": + if args in ['', 'list']: fish_list_keys(buffer) return weechat.WEECHAT_RC_OK - elif args == "genkey": - fish_secure_genkey(buffer) + argv = args.split(" ") + + if argv[0] == 'setup_bar_item': + option_name = 'weechat.bar.status.items' + option = weechat.config_get(option_name) + if option is None: + weechat.prnt(buffer, f'{option_name} not found.') + return weechat.WEECHAT_RC_ERROR + value = weechat.config_string(option) + if re.search(r'\b' + re.escape(BAR_ITEM_NAME) + r'\b', value): + weechat.prnt(buffer, 'Bar item already set up.') + return weechat.WEECHAT_RC_ERROR + if re.search(r'\bbuffer_name\b', value): + value = re.sub( + r'(buffer_name(\+[^,]*)?)', + r'\1+' + BAR_ITEM_NAME, + value) + else: + value = (value + ',' if value else '') + BAR_ITEM_NAME + weechat.command(buffer, f'/set {option_name} "{value}"') return weechat.WEECHAT_RC_OK - argv = args.split(" ") + if argv[0] == "genkey": + if fish_secure_key(): + weechat.prnt(buffer, 'Key already set.') + return weechat.WEECHAT_RC_ERROR + new_key = blowcrypt_b64encode(urandom(32)) + weechat.command( + buffer, f'/set {CONFIG_FILE_NAME}.secure.key ${{sec.data.fish}}') + weechat.command(buffer, f'/secure set fish {new_key}') + + return weechat.WEECHAT_RC_OK if (len(argv) > 2 and argv[1] == "-server"): server_name = argv[2] @@ -939,7 +972,7 @@ def fish_cmd_blowkey(data, buffer, args): if argv[0] == "exchange" and len(argv) == 1 and buffer_type == "private": target_user = weechat.buffer_get_string(buffer, "localvar_channel") elif (argv[0] == "set" and - (buffer_type == "private" or buffer_type == "channel") and + buffer_type in ['private', 'channel'] and len(argv) == 2): target_user = weechat.buffer_get_string(buffer, "localvar_channel") elif len(argv) < 2: @@ -956,16 +989,18 @@ def fish_cmd_blowkey(data, buffer, args): else: argv2eol = args[args.find(" ") + 1:] - target = "%s/%s" % (server_name, target_user) - targetl = ("%s/%s" % (server_name, target_user)).lower() + target = f'{server_name}/{target_user}' if argv[0] == "set": - fish_keys[targetl] = argv2eol + cbc = False + key = argv2eol + if key.startswith('cbc:'): + cbc = True + key = argv2eol[4:] - if targetl in fish_cyphers: - del fish_cyphers[targetl] + fish_key_set(target, key, cbc) - weechat.prnt(buffer, "set key for %s to %s" % (target, argv2eol)) + weechat.prnt(buffer, f'set key for {target} to {argv2eol}') return weechat.WEECHAT_RC_OK @@ -973,15 +1008,10 @@ def fish_cmd_blowkey(data, buffer, args): if not len(argv) == 2: return weechat.WEECHAT_RC_ERROR - if targetl not in fish_keys: + if not fish_key_delete(target): return weechat.WEECHAT_RC_ERROR - del fish_keys[targetl] - - if targetl in fish_cyphers: - del fish_cyphers[targetl] - - weechat.prnt(buffer, "removed key for %s" % target) + weechat.prnt(buffer, f'removed key for {target}') return weechat.WEECHAT_RC_OK @@ -989,11 +1019,12 @@ def fish_cmd_blowkey(data, buffer, args): if server_name == "": return weechat.WEECHAT_RC_ERROR - weechat.prnt(buffer, "Initiating DH1080 Exchange with %s" % target) - fish_DH1080ctx[targetl] = DH1080Ctx() - msg = dh1080_pack(fish_DH1080ctx[targetl]) - weechat.command(buffer, "/mute -all notice -server %s %s %s" % ( - server_name, target_user, msg)) + weechat.prnt(buffer, f'Initiating DH1080 Exchange with {target}') + fish_DH1080ctx[target] = DH1080Ctx() + msg = dh1080_pack(fish_DH1080ctx[target]) + fish_key_delete(target) + weechat.command( + buffer, f'/mute notice -server {server_name} {target_user} {msg}') return weechat.WEECHAT_RC_OK @@ -1004,158 +1035,107 @@ def fish_cmd_blowkey(data, buffer, args): # HELPERS # -def fish_secure(): - global fish_secure_key, fish_secure_cipher - - fish_secure_key = weechat.config_string(fish_config_option["key"]) - - # if blank, do nothing - if fish_secure_key == "": - return - - # if ${sec.data.fish}, check if sec.conf is decrypted - # and decrypt - elif fish_secure_key[:6] == "${sec.": - decrypted = weechat.string_eval_expression(fish_secure_key, {}, {}, {}) - - if decrypted: - fish_secure_cipher = Blowfish(decrypted) - fish_decrypt_keys() - return - - else: - global SCRIPT_NAME - message = ("\n%s%sblowkey:%s unable to recover key from sec.conf\n" - "%s%sblowkey:%s fish.py %sNOT LOADED\n" - "%s%sblowkey:%s decrypt secured data first\n" - "%s%sblowkey:%s then reload fish.py\n\n") % ( - weechat.prefix("error"), - weechat.color("underline"), - weechat.color("reset"), weechat.prefix("error"), - weechat.color("underline"), - weechat.color("reset"), weechat.color("*red"), - weechat.prefix("error"), - weechat.color("underline"), - weechat.color("reset"), weechat.prefix("error"), - weechat.color("underline"), - weechat.color("reset")) - - weechat.prnt("", "%s" % message) - weechat.command(weechat.current_buffer(), - "/wait 1ms /python unload %s" % SCRIPT_NAME) - return - - # if key is neither ${sec.data.fish} or "" - # encrypt/decrypt with user supplied, plain text key - if fish_secure_key != "": - fish_secure_cipher = Blowfish(fish_secure_key) - fish_decrypt_keys() - return - - -def fish_decrypt_keys(): - global fish_keys, fish_secure_cipher - global fish_cyphers - - fish_keys_tmp = {} - for target, key in fish_keys.iteritems(): - fish_keys_tmp[blowcrypt_unpack( - target, fish_secure_cipher)] = blowcrypt_unpack( - key, fish_secure_cipher) - - fish_keys = fish_keys_tmp +def fish_secure_key(): + return weechat.string_eval_expression( + weechat.config_string(fish_config_option["key"]), {}, {}, {}) -def fish_secure_genkey(buffer): - global fish_secure_cipher, fish_config_option +def fish_tag(msg, mode=None): + tag = f'{TAG_NAME}={mode}' if mode is not None else None + if msg.startswith('@'): + msg = re.sub( + r'^@([^ ]*;|)' + + re.escape(TAG_NAME) + + r'(=[^ ;])?(;| )', r'@\1\3', + msg) + if tag is not None: + msg = re.sub(r'^@', f"@{tag};", msg) + elif tag is not None: + msg = f'@{tag} {msg}' - newKey = blowcrypt_b64encode(urandom(32)) - - # test to see if sec.conf decrypted - weechat.command(buffer, "/secure set fish test") - decrypted = weechat.string_eval_expression("${sec.data.fish}", {}, {}, {}) - - if decrypted == "test": - weechat.config_option_set(fish_config_option["key"], - "${sec.data.fish}", 0) - fish_secure_cipher = Blowfish(newKey) - weechat.command(buffer, "/secure set fish %s" % newKey) + return msg -def fish_announce_encrypted(buffer, target): - global fish_encryption_announced, fish_config_option +def fish_announce_encrypted(buffer, target, cbc): + new_state = 'cbc' if cbc else 'ecb' - if (not weechat.config_boolean(fish_config_option['announce']) or - fish_encryption_announced.get(target)): + if fish_state_get(buffer) == new_state: return - (server, nick) = target.split("/") + server, nick = target.split('/', 1) - if (weechat.info_get("irc_is_nick", nick) and - weechat.buffer_get_string(buffer, "localvar_type") != "private"): + if (weechat.info_get('irc_is_nick', nick) and + weechat.buffer_get_string(buffer, 'localvar_type') != 'private'): # if we get a private message and there no buffer yet, create one and # jump back to the previous buffer - weechat.command(buffer, "/mute -all query %s" % nick) - buffer = weechat.info_get("irc_buffer", "%s,%s" % (server, nick)) - weechat.command(buffer, "/input jump_previously_visited_buffer") + weechat.command(buffer, f'/mute query -server {server} {nick}') + buffer = weechat.info_get('irc_buffer', f'{server},{nick}') + weechat.command(buffer, '/input jump_previously_visited_buffer') - fish_alert(buffer, "Messages to/from %s are encrypted." % target) + if weechat.config_boolean(fish_config_option['announce']): + fish_alert( + buffer, f'Messages to/from {target} are encrypted ({new_state}).') - fish_encryption_announced[target] = True + fish_state_set(buffer, new_state) def fish_announce_unencrypted(buffer, target): - global fish_encryption_announced, fish_config_option - - if (not weechat.config_boolean(fish_config_option['announce']) or - not fish_encryption_announced.get(target)): + if fish_state_get(buffer) == 'plaintext': return - fish_alert(buffer, "Messages to/from %s are %s*not*%s encrypted." % ( - target, - weechat.color(weechat.config_color(fish_config_option["alert"])), - weechat.color("chat"))) - - del fish_encryption_announced[target] + if weechat.config_boolean(fish_config_option['announce']): + fish_alert(buffer, f"Messages to/from {target} are " + f"{weechat.color(weechat.config_color(fish_config_option['alert']))}*not*" + f"{weechat.color('chat')} encrypted.") + fish_state_set(buffer, "plaintext") def fish_alert(buffer, message): - mark = "%s%s%s\t" % ( - weechat.color(weechat.config_color(fish_config_option["alert"])), - weechat.config_string(fish_config_option["marker"]), - weechat.color("chat")) - - weechat.prnt(buffer, "%s%s" % (mark, message)) + mark = f"{weechat.color(weechat.config_color(fish_config_option['alert']))}{weechat.config_string(fish_config_option['marker'])}{weechat.color('chat')}" + weechat.prnt(buffer, f'{mark}\t{message}') def fish_list_keys(buffer): - global fish_keys + weechat.command(buffer, f"/set {CONFIG_FILE_NAME}.keys.*") - weechat.prnt(buffer, "\tFiSH Keys: form target(server): key") - for (target, key) in sorted(fish_keys.items()): - (server, nick) = target.split("/") - weechat.prnt(buffer, "\t%s(%s): %s" % (nick, server, key)) +def fish_state_set(buffer, state): + if state is None: + weechat.buffer_set(buffer, f'localvar_del_{SCRIPT_NAME}_state', '') + else: + weechat.buffer_set(buffer, f'localvar_set_{SCRIPT_NAME}_state', state) + weechat.bar_item_update(BAR_ITEM_NAME) + + +def fish_state_get(buffer, default=None): + state = weechat.buffer_get_string(buffer, f'localvar_{SCRIPT_NAME}_state') + if not state: + state = default + + return state def fish_msg_w_marker(msg): - marker = weechat.config_string(fish_config_option["mark_encrypted"]).encode() + marker = weechat.config_string( + fish_config_option["mark_encrypted"]).encode() if weechat.config_string(fish_config_option["mark_position"]) == "end": return b"%s%s" % (msg, marker) - elif weechat.config_string(fish_config_option["mark_position"]) == "begin": + + if weechat.config_string(fish_config_option["mark_position"]) == "begin": return b"%s%s" % (marker, msg) - else: - return msg + + return msg def fish_msg_wo_marker(msg): marker = weechat.config_string(fish_config_option["mark_encrypted"]) if weechat.config_string(fish_config_option["mark_position"]) == "end": return msg[0:-len(marker)] - elif weechat.config_string(fish_config_option["mark_position"]) == "begin": + + if weechat.config_string(fish_config_option["mark_position"]) == "begin": return msg[len(marker):] - else: - return msg + + return msg # @@ -1168,46 +1148,55 @@ def fish_msg_wo_marker(msg): SCRIPT_DESC, "fish_unload_cb", "")): weechat.hook_command( - "blowkey", "Manage FiSH keys", - "[list] | set [-server ] [] " - "| remove [-server ] " - "| exchange [-server ] [] " - "| genkey", - "Add, change or remove key for target or perform DH1080 key" - "exchange with .\n" - "Target can be a channel or a nick.\n" - "\n" - "Without arguments this command lists all keys.\n" - "\n" - "Examples:\n" - "Set the key for a channel: /blowkey set -server freenet #blowfish" - " key\n" - "Remove the key: /blowkey remove #blowfish\n" - "Set the key for a query: /blowkey set nick secret+key\n" - "List all keys: /blowkey\n" - "DH1080: /blowkey exchange nick\n" - "\nPlease read the source for a note about DH1080 key exchange\n", - "list || set %(irc_channel)|%(nicks)|-server %(irc_servers) %- " - "|| remove %(irc_channel)|%(nicks)|-server %(irc_servers) %- " - "|| exchange %(nick)|-server %(irc_servers) %-" - "|| genkey", - "fish_cmd_blowkey", "") + "blowkey", "Manage FiSH keys", + "[list] | set [-server ] [] " + "| remove [-server ] " + "| exchange [-server ] [] " + "| setup_bar_item " + "| genkey", + "Add, change or remove key for target or perform DH1080 key" + "exchange with .\n" + "Target can be a channel or a nick.\n" + "\n" + "Without arguments this command lists all keys.\n" + "\n" + "Examples:\n" + "Set the key for a channel: /blowkey set -server freenet #blowfish" + " key\n" + "Remove the key: /blowkey remove #blowfish\n" + "Set the key for a query: /blowkey set nick secret+key\n" + "List all keys: /blowkey\n" + "DH1080: /blowkey exchange nick\n" + "Set up bar item: /blowkey setup_bar_item\n" + "\nPlease read the source for a note about DH1080 key exchange\n", + "list || set %(irc_channel)|%(nicks)|-server %(irc_servers) %- " + "|| remove %(irc_channel)|%(nicks)|-server %(irc_servers) %- " + "|| exchange %(nick)|-server %(irc_servers) %- " + "|| setup_bar_item " + "|| genkey", + "fish_cmd_blowkey", "") fish_config_init() fish_config_read() - fish_secure() + + fish_bar_item = weechat.bar_item_new( + '(extra)' + BAR_ITEM_NAME, 'fish_bar_cb', '') + + weechat.hook_line( + "", "", "irc_privmsg,irc_topic,irc_notice,irc_332", "fish_line_cb", "") + + weechat.hook_modifier( + "input_text_for_buffer", "fish_modifier_input_text", "") weechat.hook_modifier("irc_in_notice", "fish_modifier_in_notice_cb", "") weechat.hook_modifier("irc_in_privmsg", "fish_modifier_in_privmsg_cb", "") - weechat.hook_modifier("irc_in_topic", "fish_modifier_in_topic_cb", "") - weechat.hook_modifier("irc_in_332", "fish_modifier_in_332_cb", "") - weechat.hook_modifier( - "irc_out_privmsg", "fish_modifier_out_privmsg_cb", "") - weechat.hook_modifier("irc_out_topic", "fish_modifier_out_topic_cb", "") + weechat.hook_modifier("irc_in_topic", "fish_modifier_in_decrypt_cb", "") + weechat.hook_modifier("irc_in_332", "fish_modifier_in_decrypt_cb", "") weechat.hook_modifier( - "input_text_for_buffer", "fish_modifier_input_text", "") - weechat.hook_config("fish.secure.key", "fish_secure_key_cb", "") + "irc_out_privmsg", "fish_modifier_out_encrypt_cb", "") + weechat.hook_modifier("irc_out_topic", "fish_modifier_out_encrypt_cb", "") + weechat.hook_modifier("irc_out_notice", "fish_modifier_out_encrypt_cb", "") elif (__name__ == "__main__" and len(sys.argv) == 3): key = sys.argv[1] msg = sys.argv[2] - print(blowcrypt_unpack(msg, Blowfish(key), key)) + print(blowcrypt_unpack(msg, key))