123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207 |
- #!/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 = mess.getBody().encode('utf-8')
- user = mess.getTo().getStripped().encode('utf-8')
- 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 = mess.getBody().encode('utf-8')
- user = mess.getFrom().getStripped().encode('utf-8')
- 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 = mess.getFrom().getStripped().encode('utf-8')
- 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()
|