changeset 2740:8fd8ce5a5855

jp (message/send, encryption): encryption handling: - encryption algorithm can now be requested when sending a message (using --encrypt option) - new encryption commands to (de)activate encryption session, check available algorithms, or manage trust.
author Goffi <goffi@goffi.org>
date Wed, 02 Jan 2019 18:50:57 +0100
parents e8dc00f612fb
children 1797671827b9
files CHANGELOG sat_frontends/jp/cmd_encryption.py sat_frontends/jp/cmd_message.py
diffstat 3 files changed, 317 insertions(+), 64 deletions(-) [+]
line wrap: on
line diff
--- a/CHANGELOG	Wed Jan 02 18:50:47 2019 +0100
+++ b/CHANGELOG	Wed Jan 02 18:50:57 2019 +0100
@@ -38,7 +38,6 @@
         - new debug commands, to monitor stream, call bridge method or send fake signals
         - new info/session command, to get data on current session
         - new blog/get command, to retrieve locally XMPP blog
-        - new message/encryption command, to handle encryption sessions
         - new message/mam command, to check MAM archives (this command may be renamed or merged in an other one in the future)
         - new pubsub commands, for low level pubsub manipulations
         - include pubusb/search command for "grepping" pubsub nodes
@@ -52,6 +51,8 @@
         - new forums commands
         - new avatar commands
         - new ping command
+        - new encryption commands, to handle encryption sessions
+        - e2e encryption algorithm can now be requested in jp message/send
         - better handling of arguments for pubsub related commands
         - improved outputs
         - new template output, jp can now act as a static site generator
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sat_frontends/jp/cmd_encryption.py	Wed Jan 02 18:50:57 2019 +0100
@@ -0,0 +1,253 @@
+#!/usr/bin/env python2
+# -*- coding: utf-8 -*-
+
+# jp: a SAT command line tool
+# Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org)
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from sat_frontends.jp import base
+from sat_frontends.jp.constants import Const as C
+from sat.core.i18n import _
+from functools import partial
+from sat.tools.common import data_format
+from sat_frontends.jp import xmlui_manager
+
+__commands__ = ["Encryption"]
+
+
+class EncryptionAlgorithms(base.CommandBase):
+
+    def __init__(self, host):
+        extra_outputs = {"default": self.default_output}
+        super(EncryptionAlgorithms, self).__init__(
+            host, "algorithms",
+            use_output=C.OUTPUT_LIST_DICT,
+            extra_outputs=extra_outputs,
+            use_profile=False,
+            help=_("show available encryption algorithms"))
+        self.need_loop = True
+
+    def add_parser_options(self):
+        pass
+
+    def encryptionPluginsGetCb(self, plugins):
+        self.output(plugins)
+        self.host.quit()
+
+    def default_output(self, plugins):
+        if not plugins:
+            self.disp(_(u"No encryption plugin registered!"))
+            self.host.quit(C.EXIT_NOT_FOUND)
+        else:
+            self.disp(_(u"Following encryption algorithms are available: {algos}").format(
+                algos=', '.join([p['name'] for p in plugins])))
+            self.host.quit()
+
+    def start(self):
+        self.host.bridge.encryptionPluginsGet(
+            callback=self.encryptionPluginsGetCb,
+            errback=partial(
+                self.errback,
+                msg=_(u"can't retrieve plugins: {}"),
+                exit_code=C.EXIT_BRIDGE_ERRBACK,
+            ),
+        )
+
+
+class EncryptionGet(base.CommandBase):
+
+    def __init__(self, host):
+        super(EncryptionGet, self).__init__(
+            host, "get",
+            use_output=C.OUTPUT_DICT,
+            help=_(u"get encryption session data"))
+        self.need_loop = True
+
+    def add_parser_options(self):
+        self.parser.add_argument(
+            "jid", type=base.unicode_decoder,
+            help=_(u"jid of the entity to check")
+        )
+
+    def messageEncryptionGetCb(self, serialised):
+        session_data = data_format.deserialise(serialised)
+        if session_data is None:
+            self.disp(
+                u"No encryption session found, the messages are sent in plain text.")
+            self.host.quit(C.EXIT_NOT_FOUND)
+        self.output(session_data)
+        self.host.quit()
+
+    def start(self):
+        jids = self.host.check_jids([self.args.jid])
+        jid = jids[0]
+        self.host.bridge.messageEncryptionGet(
+            jid, self.profile,
+            callback=self.messageEncryptionGetCb,
+            errback=partial(
+                self.errback,
+                msg=_(u"can't get session: {}"),
+                exit_code=C.EXIT_BRIDGE_ERRBACK,
+            ),
+        )
+
+
+class EncryptionStart(base.CommandBase):
+
+    def __init__(self, host):
+        super(EncryptionStart, self).__init__(
+            host, "start",
+            help=_(u"start encrypted session with an entity"))
+        self.need_loop = True
+
+    def add_parser_options(self):
+        self.parser.add_argument(
+            "--encrypt-noreplace",
+            action="store_true",
+            help=_(u"don't replace encryption algorithm if an other one is already used"))
+        algorithm = self.parser.add_mutually_exclusive_group()
+        algorithm.add_argument(
+            "-n", "--name", help=_(u"algorithm name (DEFAULT: choose automatically)"))
+        algorithm.add_argument(
+            "-N", "--namespace",
+            help=_(u"algorithm namespace (DEFAULT: choose automatically)"))
+        self.parser.add_argument(
+            "jid", type=base.unicode_decoder,
+            help=_(u"jid of the entity to stop encrypted session with")
+        )
+
+    def encryptionNamespaceGetCb(self, namespace):
+        jids = self.host.check_jids([self.args.jid])
+        jid = jids[0]
+        self.host.bridge.messageEncryptionStart(
+            jid, namespace, not self.args.encrypt_noreplace,
+            self.profile,
+            callback=self.host.quit,
+            errback=partial(self.errback,
+                            msg=_(u"Can't start encryption session: {}"),
+                            exit_code=C.EXIT_BRIDGE_ERRBACK,
+                            ))
+
+    def start(self):
+        if self.args.name is not None:
+            self.host.bridge.encryptionNamespaceGet(self.args.name,
+                callback=self.encryptionNamespaceGetCb,
+                errback=partial(self.errback,
+                                msg=_(u"Can't get encryption namespace: {}"),
+                                exit_code=C.EXIT_BRIDGE_ERRBACK,
+                                ))
+        elif self.args.namespace is not None:
+            self.encryptionNamespaceGetCb(self.args.namespace)
+        else:
+            self.encryptionNamespaceGetCb(u"")
+
+
+class EncryptionStop(base.CommandBase):
+
+    def __init__(self, host):
+        super(EncryptionStop, self).__init__(
+            host, "stop",
+            help=_(u"stop encrypted session with an entity"))
+        self.need_loop = True
+
+    def add_parser_options(self):
+        self.parser.add_argument(
+            "jid", type=base.unicode_decoder,
+            help=_(u"jid of the entity to stop encrypted session with")
+        )
+
+    def start(self):
+        jids = self.host.check_jids([self.args.jid])
+        jid = jids[0]
+        self.host.bridge.messageEncryptionStop(
+            jid, self.profile,
+            callback=self.host.quit,
+            errback=partial(
+                self.errback,
+                msg=_(u"can't end encrypted session: {}"),
+                exit_code=C.EXIT_BRIDGE_ERRBACK,
+            ),
+        )
+
+
+class TrustUI(base.CommandBase):
+
+    def __init__(self, host):
+        super(TrustUI, self).__init__(
+            host, "ui",
+            help=_(u"get UI to manage trust"))
+        self.need_loop = True
+
+    def add_parser_options(self):
+        self.parser.add_argument(
+            "jid", type=base.unicode_decoder,
+            help=_(u"jid of the entity to stop encrypted session with")
+        )
+        algorithm = self.parser.add_mutually_exclusive_group()
+        algorithm.add_argument(
+            "-n", "--name", help=_(u"algorithm name (DEFAULT: current algorithm)"))
+        algorithm.add_argument(
+            "-N", "--namespace",
+            help=_(u"algorithm namespace (DEFAULT: current algorithm)"))
+
+    def encryptionTrustUIGetCb(self, xmlui_raw):
+        xmlui = xmlui_manager.create(self.host, xmlui_raw)
+        xmlui.show()
+        xmlui.submitForm()
+
+    def encryptionNamespaceGetCb(self, namespace):
+        jids = self.host.check_jids([self.args.jid])
+        jid = jids[0]
+        self.host.bridge.encryptionTrustUIGet(
+            jid, namespace, self.profile,
+            callback=self.encryptionTrustUIGetCb,
+            errback=partial(
+                self.errback,
+                msg=_(u"can't end encrypted session: {}"),
+                exit_code=C.EXIT_BRIDGE_ERRBACK,
+            ),
+        )
+
+    def start(self):
+        if self.args.name is not None:
+            self.host.bridge.encryptionNamespaceGet(self.args.name,
+                callback=self.encryptionNamespaceGetCb,
+                errback=partial(self.errback,
+                                msg=_(u"Can't get encryption namespace: {}"),
+                                exit_code=C.EXIT_BRIDGE_ERRBACK,
+                                ))
+        elif self.args.namespace is not None:
+            self.encryptionNamespaceGetCb(self.args.namespace)
+        else:
+            self.encryptionNamespaceGetCb(u"")
+
+
+class EncryptionTrust(base.CommandBase):
+    subcommands = (TrustUI,)
+
+    def __init__(self, host):
+        super(EncryptionTrust, self).__init__(
+            host, "trust", use_profile=False, help=_(u"trust manangement")
+        )
+
+
+class Encryption(base.CommandBase):
+    subcommands = (EncryptionAlgorithms, EncryptionGet, EncryptionStart, EncryptionStop,
+                   EncryptionTrust)
+
+    def __init__(self, host):
+        super(Encryption, self).__init__(
+            host, "encryption", use_profile=False, help=_(u"encryption sessions handling")
+        )
--- a/sat_frontends/jp/cmd_message.py	Wed Jan 02 18:50:47 2019 +0100
+++ b/sat_frontends/jp/cmd_message.py	Wed Jan 02 18:50:57 2019 +0100
@@ -33,6 +33,7 @@
 class Send(base.CommandBase):
     def __init__(self, host):
         super(Send, self).__init__(host, "send", help=_("send a message to a contact"))
+        self.need_loop=True
 
     def add_parser_options(self):
         self.parser.add_argument(
@@ -71,6 +72,12 @@
             default=C.MESS_TYPE_AUTO,
             help=_("type of the message"),
         )
+        self.parser.add_argument("-e", "--encrypt", metavar="ALGORITHM",
+                                 help=_(u"encrypt message using given algorithm"))
+        self.parser.add_argument(
+            "--encrypt-noreplace",
+            action="store_true",
+            help=_(u"don't replace encryption algorithm if an other one is already used"))
         syntax = self.parser.add_mutually_exclusive_group()
         syntax.add_argument("-x", "--xhtml", action="store_true", help=_(u"XHTML body"))
         syntax.add_argument("-r", "--rich", action="store_true", help=_(u"rich body"))
@@ -78,17 +85,16 @@
             "jid", type=base.unicode_decoder, help=_(u"the destination jid")
         )
 
-    def start(self):
-        if self.args.xhtml and self.args.separate:
-            self.disp(
-                u"argument -s/--separate is not compatible yet with argument -x/--xhtml",
-                error=True,
-            )
-            self.host.quit(2)
+    def multi_send_cb(self):
+        self.sent += 1
+        if self.sent == self.to_send:
+            self.host.quit(self.errcode)
 
-        jids = self.host.check_jids([self.args.jid])
-        jid = jids[0]
-        self.sendStdin(jid)
+    def multi_send_eb(self, failure_, msg):
+        self.disp(_(u"Can't send message [{msg}]: {reason}").format(
+            msg=msg, reason=failure_))
+        self.errcode = C.EXIT_BRIDGE_ERRBACK
+        self.multi_send_cb()
 
     def sendStdin(self, dest_jid):
         """Send incomming data on stdin to jabber contact
@@ -113,7 +119,12 @@
             stdin_lines = []
 
         if self.args.separate:  # we send stdin in several messages
+            self.to_send = 0
+            self.sent = 0
+            self.errcode = 0
+
             if header:
+                self.to_send += 1
                 self.host.bridge.messageSend(
                     dest_jid,
                     {self.args.lang: header},
@@ -124,6 +135,7 @@
                     errback=lambda ignore: ignore,
                 )
 
+            self.to_send += len(stdin_lines)
             for line in stdin_lines:
                 self.host.bridge.messageSend(
                     dest_jid,
@@ -132,8 +144,8 @@
                     self.args.type,
                     extra,
                     profile_key=self.host.profile,
-                    callback=lambda: None,
-                    errback=lambda ignore: ignore,
+                    callback=self.multi_send_cb,
+                    errback=partial(self.multi_send_eb, msg=line),
                 )
 
         else:
@@ -149,9 +161,44 @@
                 self.args.type,
                 extra,
                 profile_key=self.host.profile,
-                callback=lambda: None,
-                errback=lambda ignore: ignore,
+                callback=self.host.quit,
+                errback=partial(self.errback,
+                                msg=_(u"Can't send message: {}")))
+
+    def encryptionNamespaceGetCb(self, namespace, jid_):
+        self.host.bridge.messageEncryptionStart(
+            jid_, namespace, not self.args.encrypt_noreplace,
+            self.profile,
+            callback=lambda: self.sendStdin(jid_),
+            errback=partial(self.errback,
+                            msg=_(u"Can't start encryption session: {}"),
+                            exit_code=C.EXIT_BRIDGE_ERRBACK,
+                            ))
+
+
+    def start(self):
+        if self.args.xhtml and self.args.separate:
+            self.disp(
+                u"argument -s/--separate is not compatible yet with argument -x/--xhtml",
+                error=True,
             )
+            self.host.quit(2)
+
+        jids = self.host.check_jids([self.args.jid])
+        jid_ = jids[0]
+
+        if self.args.encrypt_noreplace and self.args.encrypt is None:
+            self.parser.error("You need to use --encrypt if you use --encrypt-noreplace")
+
+        if self.args.encrypt is not None:
+            self.host.bridge.encryptionNamespaceGet(self.args.encrypt,
+                callback=partial(self.encryptionNamespaceGetCb, jid_=jid_),
+                errback=partial(self.errback,
+                                msg=_(u"Can't get encryption namespace: {}"),
+                                exit_code=C.EXIT_BRIDGE_ERRBACK,
+                                ))
+        else:
+            self.sendStdin(jid_)
 
 
 class MAM(base.CommandBase):
@@ -216,56 +263,8 @@
             callback=self._MAMGetCb, errback=self.errback)
 
 
-class EncryptionAlgorithms(base.CommandBase):
-
-    def __init__(self, host):
-        extra_outputs = {"default": self.default_output}
-        super(EncryptionAlgorithms, self).__init__(
-            host, "algorithms",
-            use_output=C.OUTPUT_LIST_DICT,
-            extra_outputs=extra_outputs,
-            use_profile=False,
-            help=_("show available encryption algorithms"))
-        self.need_loop = True
-
-    def add_parser_options(self):
-        pass
-
-    def encryptionPluginsGetCb(self, plugins):
-        self.output(plugins)
-        self.host.quit()
-
-    def default_output(self, plugins):
-        if not plugins:
-            self.disp(_(u"No encryption plugin registered!"))
-            self.host.quit(C.EXIT_NOT_FOUND)
-        else:
-            self.disp(_(u"Following encryption algorithms are available: {algos}").format(
-                algos=', '.join([p['name'] for p in plugins])))
-            self.host.quit()
-
-    def start(self):
-        self.host.bridge.encryptionPluginsGet(
-            callback=self.encryptionPluginsGetCb,
-            errback=partial(
-                self.errback,
-                msg=_(u"can't retrieve plugins: {}"),
-                exit_code=C.EXIT_BRIDGE_ERRBACK,
-            ),
-        )
-
-
-class Encryption(base.CommandBase):
-    subcommands = (EncryptionAlgorithms,)
-
-    def __init__(self, host):
-        super(Encryption, self).__init__(
-            host, "encryption", use_profile=False, help=_("encryption sessions handling")
-        )
-
-
 class Message(base.CommandBase):
-    subcommands = (Send, MAM, Encryption)
+    subcommands = (Send, MAM)
 
     def __init__(self, host):
         super(Message, self).__init__(