diff options
Diffstat (limited to 'features/scripts')
-rwxr-xr-x | features/scripts/otr-bot.py | 206 | ||||
-rwxr-xr-x | features/scripts/vm-execute | 52 |
2 files changed, 258 insertions, 0 deletions
diff --git a/features/scripts/otr-bot.py b/features/scripts/otr-bot.py new file mode 100755 index 00000000..0afd15a4 --- /dev/null +++ b/features/scripts/otr-bot.py @@ -0,0 +1,206 @@ +#!/usr/bin/python +import sys +import jabberbot +import xmpp +import potr +import logging +from argparse import ArgumentParser + +class OtrContext(potr.context.Context): + + def __init__(self, account, peer): + super(OtrContext, self).__init__(account, peer) + + def getPolicy(self, key): + return True + + def inject(self, msg, appdata = None): + mess = appdata["base_reply"] + mess.setBody(msg) + appdata["send_raw_message_fn"](mess) + + +class BotAccount(potr.context.Account): + + def __init__(self, jid, keyFilePath): + protocol = 'xmpp' + max_message_size = 10*1024 + super(BotAccount, self).__init__(jid, protocol, max_message_size) + self.keyFilePath = keyFilePath + + def loadPrivkey(self): + with open(self.keyFilePath, 'rb') as keyFile: + return potr.crypt.PK.parsePrivateKey(keyFile.read())[0] + + +class OtrContextManager: + + def __init__(self, jid, keyFilePath): + self.account = BotAccount(jid, keyFilePath) + self.contexts = {} + + def start_context(self, other): + if not other in self.contexts: + self.contexts[other] = OtrContext(self.account, other) + return self.contexts[other] + + def get_context_for_user(self, other): + return self.start_context(other) + + +class OtrBot(jabberbot.JabberBot): + + PING_FREQUENCY = 60 + + def __init__(self, account, password, otr_key_path, + connect_server = None, log_file = None): + self.__connect_server = connect_server + self.__password = password + self.__log_file = log_file + super(OtrBot, self).__init__(account, password) + self.__otr_manager = OtrContextManager(account, otr_key_path) + self.send_raw_message_fn = super(OtrBot, self).send_message + self.__default_otr_appdata = { + "send_raw_message_fn": self.send_raw_message_fn + } + + def __otr_appdata_for_mess(self, mess): + appdata = self.__default_otr_appdata.copy() + appdata["base_reply"] = mess + return appdata + + # Unfortunately Jabberbot's connect() is not very friendly to + # overriding in subclasses so we have to re-implement it + # completely (copy-paste mostly) in order to add support for using + # an XMPP "Connect Server". + def connect(self): + logging.basicConfig(filename = self.__log_file, + level = logging.DEBUG) + if not self.conn: + conn = xmpp.Client(self.jid.getDomain(), debug=[]) + if self.__connect_server: + try: + conn_server, conn_port = self.__connect_server.split(":", 1) + except ValueError: + conn_server = self.__connect_server + conn_port = 5222 + conres = conn.connect((conn_server, int(conn_port))) + else: + conres = conn.connect() + if not conres: + return None + authres = conn.auth(self.jid.getNode(), self.__password, self.res) + if not authres: + return None + self.conn = conn + self.conn.sendInitPresence() + self.roster = self.conn.Roster.getRoster() + for (handler, callback) in self.handlers: + self.conn.RegisterHandler(handler, callback) + return self.conn + + # Wrap OTR encryption around Jabberbot's most low-level method for + # sending messages. + def send_message(self, mess): + body = str(mess.getBody()) + user = str(mess.getTo().getStripped()) + otrctx = self.__otr_manager.get_context_for_user(user) + if otrctx.state == potr.context.STATE_ENCRYPTED: + otrctx.sendMessage(potr.context.FRAGMENT_SEND_ALL, body, + appdata = self.__otr_appdata_for_mess(mess)) + else: + self.send_raw_message_fn(mess) + + # Wrap OTR decryption around Jabberbot's callback mechanism. + def callback_message(self, conn, mess): + body = str(mess.getBody()) + user = str(mess.getFrom().getStripped()) + otrctx = self.__otr_manager.get_context_for_user(user) + if mess.getType() == "chat": + try: + appdata = self.__otr_appdata_for_mess(mess.buildReply()) + decrypted_body, tlvs = otrctx.receiveMessage(body, + appdata = appdata) + otrctx.processTLVs(tlvs) + except potr.context.NotEncryptedError: + otrctx.authStartV2(appdata = appdata) + return + except (potr.context.UnencryptedMessage, potr.context.NotOTRMessage): + decrypted_body = body + else: + decrypted_body = body + if decrypted_body == None: + return + if mess.getType() == "groupchat": + bot_prefix = self.jid.getNode() + ": " + if decrypted_body.startswith(bot_prefix): + decrypted_body = decrypted_body[len(bot_prefix):] + else: + return + mess.setBody(decrypted_body) + super(OtrBot, self).callback_message(conn, mess) + + # Override Jabberbot quitting on keep alive failure. + def on_ping_timeout(self): + self.__lastping = None + + @jabberbot.botcmd + def ping(self, mess, args): + """Why not just test it?""" + return "pong" + + @jabberbot.botcmd + def say(self, mess, args): + """Unleash my inner parrot""" + return args + + @jabberbot.botcmd + def clear_say(self, mess, args): + """Make me speak in the clear even if we're in an OTR chat""" + self.send_raw_message_fn(mess.buildReply(args)) + return "" + + @jabberbot.botcmd + def start_otr(self, mess, args): + """Make me *initiate* (but not refresh) an OTR session""" + if mess.getType() == "groupchat": + return + return "?OTRv2?" + + @jabberbot.botcmd + def end_otr(self, mess, args): + """Make me gracefully end the OTR session if there is one""" + if mess.getType() == "groupchat": + return + user = str(mess.getFrom().getStripped()) + self.__otr_manager.get_context_for_user(user).disconnect(appdata = + self.__otr_appdata_for_mess(mess.buildReply())) + return "" + +if __name__ == '__main__': + parser = ArgumentParser() + parser.add_argument("account", + help = "the user account, given as user@domain") + parser.add_argument("password", + help = "the user account's password") + parser.add_argument("otr_key_path", + help = "the path to the account's OTR key file") + parser.add_argument("-c", "--connect-server", metavar = 'ADDRESS', + help = "use a Connect Server, given as host[:port] " + + "(port defaults to 5222)") + parser.add_argument("-j", "--auto-join", nargs = '+', metavar = 'ROOMS', + help = "auto-join multi-user chatrooms on start") + parser.add_argument("-l", "--log-file", metavar = 'LOGFILE', + help = "Log to file instead of stderr") + args = parser.parse_args() + otr_bot_opt_args = dict() + if args.connect_server: + otr_bot_opt_args["connect_server"] = args.connect_server + if args.log_file: + otr_bot_opt_args["log_file"] = args.log_file + otr_bot = OtrBot(args.account, args.password, args.otr_key_path, + **otr_bot_opt_args) + if args.auto_join: + for room in args.auto_join: + otr_bot.join_room(room) + otr_bot.serve_forever() diff --git a/features/scripts/vm-execute b/features/scripts/vm-execute new file mode 100755 index 00000000..fc1bf459 --- /dev/null +++ b/features/scripts/vm-execute @@ -0,0 +1,52 @@ +#!/usr/bin/env ruby + +require 'optparse' +begin + require "#{`git rev-parse --show-toplevel`.chomp}/features/support/helpers/exec_helper.rb" +rescue LoadError => e + raise "This script must be run from within Tails' Git directory." +end +$config = Hash.new + +def debug_log(*args) ; end + +class FakeVM + def get_remote_shell_port + 1337 + end +end + +cmd_opts = { + :spawn => false, + :user => "root" +} + +opt_parser = OptionParser.new do |opts| + opts.banner = "Usage: features/scripts/vm-execute [opts] COMMAND" + opts.separator "" + opts.separator "Runs commands in the VM guest being tested. This script " \ + "must be run from within Tails' Git directory." + opts.separator "" + opts.separator "Options:" + + opts.on("-h", "--help", "Show this message") do + puts opts + exit + end + + opts.on("-u", "--user USER", "Run command as USER") do |user| + cmd_opts[:user] = user + end + + opts.on("-s", "--spawn", + "Run command in non-blocking mode") do |type| + cmd_opts[:spawn] = true + end +end +opt_parser.parse!(ARGV) +cmd = ARGV.join(" ") +c = VMCommand.new(FakeVM.new, cmd, cmd_opts) +puts "Return status: #{c.returncode}" +puts "STDOUT:\n#{c.stdout}" +puts "STDERR:\n#{c.stderr}" +exit c.returncode |