Add olm account loading/creation and a initial olm command.
This commit is contained in:
parent
45e6f742ec
commit
c52b7866c8
5 changed files with 210 additions and 0 deletions
1
main.py
1
main.py
|
@ -36,6 +36,7 @@ from matrix.colors import Formatted
|
||||||
from matrix.utf import utf8_decode
|
from matrix.utf import utf8_decode
|
||||||
from matrix.http import HttpResponse
|
from matrix.http import HttpResponse
|
||||||
from matrix.api import MatrixSendMessage
|
from matrix.api import MatrixSendMessage
|
||||||
|
from matrix.encryption import matrix_olm_command_cb
|
||||||
|
|
||||||
# Weechat searches for the registered callbacks in the scope of the main script
|
# Weechat searches for the registered callbacks in the scope of the main script
|
||||||
# file, import the callbacks here so weechat can find them.
|
# file, import the callbacks here so weechat can find them.
|
||||||
|
|
|
@ -32,6 +32,7 @@ from matrix.utils import key_from_value, tags_from_line_data
|
||||||
from matrix.plugin_options import DebugType
|
from matrix.plugin_options import DebugType
|
||||||
from matrix.server import MatrixServer
|
from matrix.server import MatrixServer
|
||||||
from matrix.colors import Formatted
|
from matrix.colors import Formatted
|
||||||
|
from matrix.encryption import matrix_hook_olm_command
|
||||||
|
|
||||||
|
|
||||||
def hook_commands():
|
def hook_commands():
|
||||||
|
@ -98,6 +99,8 @@ def hook_commands():
|
||||||
"matrix_me_command_cb",
|
"matrix_me_command_cb",
|
||||||
"")
|
"")
|
||||||
|
|
||||||
|
matrix_hook_olm_command()
|
||||||
|
|
||||||
W.hook_command_run('/topic', 'matrix_command_topic_cb', '')
|
W.hook_command_run('/topic', 'matrix_command_topic_cb', '')
|
||||||
W.hook_command_run('/buffer clear', 'matrix_command_buf_clear_cb', '')
|
W.hook_command_run('/buffer clear', 'matrix_command_buf_clear_cb', '')
|
||||||
W.hook_command_run('/join', 'matrix_command_join_cb', '')
|
W.hook_command_run('/join', 'matrix_command_join_cb', '')
|
||||||
|
|
176
matrix/encryption.py
Normal file
176
matrix/encryption.py
Normal file
|
@ -0,0 +1,176 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Weechat Matrix Protocol Script
|
||||||
|
# Copyright © 2018 Damir Jelić <poljar@termina.org.uk>
|
||||||
|
#
|
||||||
|
# Permission to use, copy, modify, and/or 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.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||||
|
# SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
|
||||||
|
# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
|
||||||
|
# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||||
|
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
# pylint: disable=redefined-builtin
|
||||||
|
from builtins import str
|
||||||
|
|
||||||
|
from functools import wraps
|
||||||
|
from future.moves.itertools import zip_longest
|
||||||
|
|
||||||
|
import matrix.globals
|
||||||
|
|
||||||
|
try:
|
||||||
|
from olm.account import Account, OlmAccountError
|
||||||
|
except ImportError:
|
||||||
|
matrix.globals.ENCRYPTION = False
|
||||||
|
|
||||||
|
from matrix.globals import W, SERVERS
|
||||||
|
from matrix.utf import utf8_decode
|
||||||
|
|
||||||
|
|
||||||
|
def own_buffer(f):
|
||||||
|
|
||||||
|
@wraps(f)
|
||||||
|
def wrapper(data, buffer, *args, **kwargs):
|
||||||
|
|
||||||
|
for server in SERVERS.values():
|
||||||
|
if buffer in server.buffers.values():
|
||||||
|
return f(server.name, buffer, *args, **kwargs)
|
||||||
|
elif buffer == server.server_buffer:
|
||||||
|
return f(server.name, buffer, *args, **kwargs)
|
||||||
|
|
||||||
|
return W.WEECHAT_RC_OK
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
def encrypt_enabled(f):
|
||||||
|
|
||||||
|
@wraps(f)
|
||||||
|
def wrapper(*args, **kwds):
|
||||||
|
if matrix.globals.ENCRYPTION:
|
||||||
|
return f(*args, **kwds)
|
||||||
|
return None
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
@encrypt_enabled
|
||||||
|
def matrix_hook_olm_command():
|
||||||
|
W.hook_command(
|
||||||
|
# Command name and short description
|
||||||
|
"olm",
|
||||||
|
"Matrix olm encryption command",
|
||||||
|
# Synopsis
|
||||||
|
("info all|blacklisted|private|unverified|verified <filter>||"
|
||||||
|
"blacklist <device-id> ||"
|
||||||
|
"unverify <device-id> ||"
|
||||||
|
"verify <device-id>"),
|
||||||
|
# Description
|
||||||
|
(" info: show info about known devices and their keys\n"
|
||||||
|
"blacklist: blacklist a device\n"
|
||||||
|
" unverify: unverify a device\n"
|
||||||
|
" verify: verify a device\n\n"
|
||||||
|
"Examples:\n"),
|
||||||
|
# Completions
|
||||||
|
('info all|blacklisted|private|unverified|verified ||'
|
||||||
|
'blacklist %(device_ids) ||'
|
||||||
|
'unverify %(device_ids) ||'
|
||||||
|
'verify %(device_ids)'),
|
||||||
|
# Function name
|
||||||
|
'matrix_olm_command_cb',
|
||||||
|
'')
|
||||||
|
|
||||||
|
|
||||||
|
def olm_cmd_parse_args(args):
|
||||||
|
split_args = args.split()
|
||||||
|
|
||||||
|
command = split_args.pop(0) if split_args else "info"
|
||||||
|
|
||||||
|
rest_args = split_args if split_args else []
|
||||||
|
|
||||||
|
return command, rest_args
|
||||||
|
|
||||||
|
|
||||||
|
def grouper(iterable, n, fillvalue=None):
|
||||||
|
"Collect data into fixed-length chunks or blocks"
|
||||||
|
# grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx"
|
||||||
|
args = [iter(iterable)] * n
|
||||||
|
return zip_longest(*args, fillvalue=fillvalue)
|
||||||
|
|
||||||
|
|
||||||
|
def partition_key(key):
|
||||||
|
groups = grouper(key, 4, " ")
|
||||||
|
return ' '.join(''.join(g) for g in groups)
|
||||||
|
|
||||||
|
|
||||||
|
@own_buffer
|
||||||
|
@utf8_decode
|
||||||
|
def matrix_olm_command_cb(server_name, buffer, args):
|
||||||
|
server = SERVERS[server_name]
|
||||||
|
command, args = olm_cmd_parse_args(args)
|
||||||
|
|
||||||
|
if not command or command == "info":
|
||||||
|
olm = server.olm
|
||||||
|
device_msg = (" - Device ID: {}\n".format(server.device_id)
|
||||||
|
if server.device_id else "")
|
||||||
|
id_key = partition_key(olm.account.identity_keys()["curve25519"])
|
||||||
|
fp_key = partition_key(olm.account.identity_keys()["ed25519"])
|
||||||
|
message = ("{prefix}matrix: Identity keys:\n"
|
||||||
|
" - User: {user}\n"
|
||||||
|
"{device_msg}"
|
||||||
|
" - Identity key: {id_key}\n"
|
||||||
|
" - Fingerprint key: {fp_key}\n").format(
|
||||||
|
prefix=W.prefix("network"),
|
||||||
|
user=server.user,
|
||||||
|
device_msg=device_msg,
|
||||||
|
id_key=id_key,
|
||||||
|
fp_key=fp_key)
|
||||||
|
W.prnt(server.server_buffer, message)
|
||||||
|
else:
|
||||||
|
message = ("{prefix}matrix: Command not implemented.".format(
|
||||||
|
prefix=W.prefix("error")))
|
||||||
|
W.prnt(server.server_buffer, message)
|
||||||
|
|
||||||
|
return W.WEECHAT_RC_OK
|
||||||
|
|
||||||
|
|
||||||
|
class EncryptionError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Olm():
|
||||||
|
|
||||||
|
@encrypt_enabled
|
||||||
|
def __init__(self, server, account=None):
|
||||||
|
# type: (Server, Account) -> None
|
||||||
|
self.server = server
|
||||||
|
if account:
|
||||||
|
self.account = account
|
||||||
|
else:
|
||||||
|
self.account = Account()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
@encrypt_enabled
|
||||||
|
def from_session_dir(cls, server):
|
||||||
|
# type: (Server) -> Olm
|
||||||
|
account_file_name = "{}_{}.account".format(server.user,
|
||||||
|
server.device_id)
|
||||||
|
session_path = server.get_session_path()
|
||||||
|
path = os.path.join(session_path, account_file_name)
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(path, "rb") as f:
|
||||||
|
pickle = f.read()
|
||||||
|
account = Account.from_pickle(pickle)
|
||||||
|
return cls(server, account)
|
||||||
|
except OlmAccountError as error:
|
||||||
|
raise EncryptionError(error)
|
|
@ -31,3 +31,4 @@ except ImportError:
|
||||||
OPTIONS = PluginOptions() # type: PluginOptions
|
OPTIONS = PluginOptions() # type: PluginOptions
|
||||||
SERVERS = dict() # type: Dict[str, MatrixServer]
|
SERVERS = dict() # type: Dict[str, MatrixServer]
|
||||||
CONFIG = None # type: weechat.config
|
CONFIG = None # type: weechat.config
|
||||||
|
ENCRYPTION = True # type: bool
|
||||||
|
|
|
@ -35,6 +35,13 @@ from matrix.globals import W, SERVERS, OPTIONS
|
||||||
import matrix.api as API
|
import matrix.api as API
|
||||||
from matrix.api import MatrixClient, MatrixSyncMessage, MatrixLoginMessage
|
from matrix.api import MatrixClient, MatrixSyncMessage, MatrixLoginMessage
|
||||||
|
|
||||||
|
from matrix.encryption import Olm, EncryptionError
|
||||||
|
|
||||||
|
try:
|
||||||
|
FileNotFoundError
|
||||||
|
except NameError:
|
||||||
|
FileNotFoundError = IOError
|
||||||
|
|
||||||
|
|
||||||
class MatrixServer:
|
class MatrixServer:
|
||||||
# pylint: disable=too-many-instance-attributes
|
# pylint: disable=too-many-instance-attributes
|
||||||
|
@ -48,6 +55,7 @@ class MatrixServer:
|
||||||
self.options = dict() # type: Dict[str, weechat.config]
|
self.options = dict() # type: Dict[str, weechat.config]
|
||||||
self.device_name = "Weechat Matrix" # type: str
|
self.device_name = "Weechat Matrix" # type: str
|
||||||
self.device_id = "" # type: str
|
self.device_id = "" # type: str
|
||||||
|
self.olm = None # type: Olm
|
||||||
|
|
||||||
self.user = "" # type: str
|
self.user = "" # type: str
|
||||||
self.password = "" # type: str
|
self.password = "" # type: str
|
||||||
|
@ -126,6 +134,24 @@ class MatrixServer:
|
||||||
with open(path, 'w') as f:
|
with open(path, 'w') as f:
|
||||||
f.write(self.device_id)
|
f.write(self.device_id)
|
||||||
|
|
||||||
|
def _load_olm(self):
|
||||||
|
try:
|
||||||
|
self.olm = Olm.from_session_dir(self)
|
||||||
|
except FileNotFoundError:
|
||||||
|
message = ("{prefix}matrix: Creating new Olm identity for {user}"
|
||||||
|
" on {server} for device {device}.").format(
|
||||||
|
prefix=W.prefix("network"),
|
||||||
|
user=self.user,
|
||||||
|
server=self.name,
|
||||||
|
device=self.device_id)
|
||||||
|
W.prnt("", message)
|
||||||
|
self.olm = Olm(self)
|
||||||
|
except EncryptionError as error:
|
||||||
|
message = ("{prefix}matrix: Error loading Olm"
|
||||||
|
"account: {error}.").format(
|
||||||
|
prefix=W.prefix("error"), error=error)
|
||||||
|
W.prnt("", message)
|
||||||
|
|
||||||
def _create_options(self, config_file):
|
def _create_options(self, config_file):
|
||||||
options = [
|
options = [
|
||||||
Option('autoconnect', 'boolean', '', 0, 0, 'off',
|
Option('autoconnect', 'boolean', '', 0, 0, 'off',
|
||||||
|
@ -200,6 +226,9 @@ class MatrixServer:
|
||||||
|
|
||||||
self._load_device_id()
|
self._load_device_id()
|
||||||
|
|
||||||
|
if self.device_id:
|
||||||
|
self._load_olm()
|
||||||
|
|
||||||
elif option_name == "password":
|
elif option_name == "password":
|
||||||
value = W.config_string(option)
|
value = W.config_string(option)
|
||||||
self.password = W.string_eval_expression(value, {}, {}, {})
|
self.password = W.string_eval_expression(value, {}, {}, {})
|
||||||
|
|
Loading…
Add table
Reference in a new issue