]> code.delx.au - pymsnt/blobdiff - src/misciq.py
Added SVN version number to result from jabber:iq:version requests.
[pymsnt] / src / misciq.py
index 56b5be6158f24b33477f65b97ffbdb723ea2dcb1..64cbe90bc150754eae1e54cff76b2efe21935146 100644 (file)
@@ -2,21 +2,18 @@
 # Licensed for distribution under the GPL version 2, check COPYING for details
 
 import utils
-if(utils.checkTwisted()):
-       from twisted.xish.domish import Element
-       from twisted.words.protocols.jabber import jid
-else:
-       from tlib.domish import Element
-       from tlib.jabber import jid
-from twisted.internet import reactor, task
+from twisted.internet import reactor, task, protocol, error
+from tlib.xmlw import Element, jid
 from debug import LogEvent, INFO, WARN, ERROR
+import svninfo
 import jabw
 import legacy
 import disco
 import config
 import lang
+import ft
 import base64
-import sys
+import sys, urllib
 
 
 class ConnectUsers:
@@ -33,7 +30,7 @@ class ConnectUsers:
                ID = el.getAttribute("id")
                ulang = utils.getLang(el)
 
-               if config.admins.count(jid.JID(to).userhost()) == 0:
+               if config.admins.count(jid.intern(to).userhost()) == 0:
                        self.pytrans.discovery.sendIqError(to=to, fro=config.jid, ID=ID, xmlns=disco.COMMANDS, etype="cancel", condition="not-authorized")
                        return
 
@@ -53,7 +50,7 @@ class ConnectUsers:
                command.attributes["status"] = "completed"
 
                x = command.addElement("x")
-               x.attributes["xmlns"] = "jabber:x:data"
+               x.attributes["xmlns"] = disco.XDATA
                x.attributes["type"] = "result"
 
                title = x.addElement("title")
@@ -97,7 +94,7 @@ class Statistics:
                command.attributes["status"] = "completed"
 
                x = command.addElement("x")
-               x.attributes["xmlns"] = "jabber:x:data"
+               x.attributes["xmlns"] = disco.XDATA
                x.attributes["type"] = "result"
 
                title = x.addElement("title")
@@ -134,7 +131,7 @@ class AdHocCommands:
        def incomingIq(self, el):
                itype = el.getAttribute("type")
                fro = el.getAttribute("from")
-               froj = jid.JID(fro)
+               froj = jid.intern(fro)
                to = el.getAttribute("to")
                ID = el.getAttribute("id")
 
@@ -142,7 +139,7 @@ class AdHocCommands:
 
                node = None
                for child in el.elements():
-                       xmlns = child.defaultUri
+                       xmlns = child.uri
                        node = child.getAttribute("node")
 
                        handled = False
@@ -222,10 +219,11 @@ class VCardFactory:
        def incomingIq(self, el):
                itype = el.getAttribute("type")
                fro = el.getAttribute("from")
-               froj = jid.JID(fro)
+               froj = jid.intern(fro)
                to = el.getAttribute("to")
+               toj = jid.intern(to)
                ID = el.getAttribute("id")
-               if(itype != "get" and itype != "error"):
+               if itype != "get" and itype != "error":
                        self.pytrans.discovery.sendIqError(to=fro, fro=config.jid, ID=ID, xmlns="vcard-temp", etype="cancel", condition="feature-not-implemented")
                        return
 
@@ -233,17 +231,17 @@ class VCardFactory:
 
                toGateway = not (to.find('@') > 0)
 
-               if(not toGateway):
-                       if(not self.pytrans.sessions.has_key(froj.userhost())):
+               if not toGateway:
+                       if not self.pytrans.sessions.has_key(froj.userhost()):
                                self.pytrans.discovery.sendIqError(to=fro, fro=config.jid, ID=ID, xmlns="vcard-temp", etype="auth", condition="not-authorized")
                                return
                        s = self.pytrans.sessions[froj.userhost()]
-                       if(not s.ready):
+                       if not s.ready:
                                self.pytrans.discovery.sendIqError(to=fro, fro=config.jid, ID=ID, xmlns="vcard-temp", etype="auth", condition="not-authorized")
                                return
                
-                       c = s.contactList.findContact(to)
-                       if(not c):
+                       c = s.contactList.findContact(toj.userhost())
+                       if not c:
                                self.pytrans.discovery.sendIqError(to=fro, fro=config.jid, ID=ID, xmlns="vcard-temp", etype="cancel", condition="recipient-unavailable")
                                return
 
@@ -256,18 +254,18 @@ class VCardFactory:
                iq.attributes["type"] = "result"
                vCard = iq.addElement("vCard")
                vCard.attributes["xmlns"] = "vcard-temp"
-               if(toGateway):
+               if toGateway:
                        FN = vCard.addElement("FN")
-                       FN.addContent(legacy.name)
+                       FN.addContent(config.discoName)
                        DESC = vCard.addElement("DESC")
-                       DESC.addContent(legacy.name)
+                       DESC.addContent(config.discoName)
                        URL = vCard.addElement("URL")
                        URL.addContent(legacy.url)
                else:
-                       if(c.nickname):
+                       if c.nickname:
                                NICKNAME = vCard.addElement("NICKNAME")
                                NICKNAME.addContent(c.nickname)
-                       if(c.avatar):
+                       if c.avatar:
                                PHOTO = c.avatar.makePhotoElement()
                                vCard.addChild(PHOTO)
 
@@ -282,8 +280,9 @@ class IqAvatarFactory:
        def incomingIq(self, el):
                itype = el.getAttribute("type")
                fro = el.getAttribute("from")
-               froj = jid.JID(fro)
+               froj = jid.intern(fro)
                to = el.getAttribute("to")
+               toj = jid.intern(to)
                ID = el.getAttribute("id")
 
                if(itype != "get" and itype != "error"):
@@ -300,7 +299,7 @@ class IqAvatarFactory:
                        self.pytrans.discovery.sendIqError(to=fro, fro=config.jid, ID=ID, xmlns=disco.IQAVATAR, etype="auth", condition="not-authorized")
                        return
 
-               c = s.contactList.findContact(to)
+               c = s.contactList.findContact(toj.userhost())
                if(not c):
                        self.pytrans.discovery.sendIqError(to=fro, fro=config.jid, ID=ID, xmlns=disco.IQAVATAR, etype="cancel", condition="recipient-unavailable")
                        return
@@ -433,6 +432,11 @@ class VersionTeller:
                self.pytrans = pytrans
                self.pytrans.discovery.addFeature(disco.IQVERSION, self.incomingIq, config.jid)
                self.pytrans.discovery.addFeature(disco.IQVERSION, self.incomingIq, "USER")
+               try:
+                       self.version = "%s - SVN r%s" % (legacy.version, svninfo.getSVNVersion())
+               except:
+                       self.version = legacy.version
+               self.os = "Python" + ".".join([str(x) for x in sys.version_info[0:3]]) + " - " + sys.platform
        
        def incomingIq(self, el):
                eltype = el.getAttribute("type")
@@ -451,13 +455,231 @@ class VersionTeller:
                query = iq.addElement("query")
                query.attributes["xmlns"] = disco.IQVERSION
                name = query.addElement("name")
-               name.addContent(legacy.name)
+               name.addContent(config.discoName)
                version = query.addElement("version")
-               version.addContent(legacy.version)
+               version.addContent(self.version)
                os = query.addElement("os")
-               os.addContent("Python" + ".".join([str(x) for x in sys.version_info[0:3]]) + " - " + sys.platform)
+               os.addContent(self.os)
                
                self.pytrans.send(iq)
 
 
+class FileTransferOOBSend:
+       def __init__(self, pytrans):
+               self.pytrans = pytrans
+               self.pytrans.discovery.addFeature(disco.IQOOB, self.incomingOOB, "USER")
+       
+       def incomingOOB(self, el):
+               ID = el.getAttribute("id")
+               def errOut():
+                       self.pytrans.discovery.sendIqError(to=el.getAttribute("from"), fro=el.getAttribute("to"), ID=ID, xmlns=disco.IQOOB, etype="cancel", condition="feature-not-implemented")
+
+               if el.attributes["type"] != "set":
+                       return errOut()
+               for child in el.elements():
+                       if child.name == "query":
+                               query = child
+                               break
+               else:
+                       return errOut()
+               for child in query.elements():
+                       if child.name == "url":
+                               url = child.__str__()
+                               break
+               else:
+                       return errOut()
+
+               froj = jid.intern(el.getAttribute("from"))
+               toj = jid.intern(el.getAttribute("to"))
+               session = self.pytrans.sessions.get(froj.userhost(), None)
+               if not session:
+                       return errOut()
+               
+               res = utils.getURLBits(url, "http")
+               if not res:
+                       return errOut()
+               host, port, path, filename = res
+               
+
+               def sendResult():
+                       iq = Element((None, "iq"))
+                       iq.attributes["to"] = froj.full()
+                       iq.attributes["from"] = toj.full()
+                       iq.attributes["type"] = "result"
+                       if ID:
+                               iq.attributes["id"] = ID
+                       iq.addElement("query").attributes["xmlns"] = "jabber:iq:oob"
+                       self.pytrans.send(iq)
+
+               def startTransfer(consumer):
+                       factory = protocol.ClientFactory()
+                       factory.protocol = ft.OOBSendConnector
+                       factory.path = path
+                       factory.host = host
+                       factory.port = port
+                       factory.consumer = consumer
+                       factory.finished = sendResult
+                       reactor.connectTCP(host, port, factory)
+
+               def doSendFile(length):
+                       ft.FTSend(session, toj.userhost(), startTransfer, errOut, filename, length)
+
+               # Make a HEAD request to grab the length of data first
+               factory = protocol.ClientFactory()
+               factory.protocol = ft.OOBHeaderHelper
+               factory.path = path
+               factory.host = host
+               factory.port = port
+               factory.gotLength = doSendFile
+               reactor.connectTCP(host, port, factory)
+
+
+
+class Socks5FileTransfer:
+       def __init__(self, pytrans):
+               self.pytrans = pytrans
+               self.pytrans.discovery.addFeature(disco.SI, self.incomingSI, "USER")
+               self.pytrans.discovery.addFeature(disco.FT, lambda: None, "USER")
+               self.pytrans.discovery.addFeature(disco.S5B, self.incomingS5B, "USER")
+               self.sessions = {}
+       
+       def incomingSI(self, el):
+               ID = el.getAttribute("id")
+               def errOut():
+                       self.pytrans.discovery.sendIqError(to=el.getAttribute("from"), fro=el.getAttribute("to"), ID=ID, xmlns=disco.SI, etype="cancel", condition="bad-request")
+
+               toj = jid.intern(el.getAttribute("to"))
+               froj = jid.intern(el.getAttribute("from"))
+               session = self.pytrans.sessions.get(froj.userhost(), None)
+               if not session:
+                       return errOut()
+
+               si = el.si
+               if not (si and si.getAttribute("profile") == disco.FT):
+                       return errOut()
+               file = si.file
+               if not (file and file.uri == disco.FT):
+                       return errOut()
+               try:
+                       sid = si["id"]
+                       filename = file["name"]
+                       filesize = int(file["size"])
+               except KeyError:
+                       return errOut()
+               except ValueError:
+                       return errOut()
+
+               # Check that we can use socks5 bytestreams
+               feature = si.feature
+               if not (feature and feature.uri == disco.FEATURE_NEG):
+                       return errOut()
+               x = feature.x
+               if not (x and x.uri == disco.XDATA):
+                       return errOut()
+               field = x.field
+               if not (field and field.getAttribute("var") == "stream-method"):
+                       return errOut()
+               for option in field.elements():
+                       value = option.value
+                       if not value:
+                               continue
+                       value = value.__str__()
+                       if value == disco.S5B:
+                               break
+               else:
+                       return errOut() # Socks5 bytestreams not supported :(
+
+
+               def startTransfer(consumer):
+                       iq = Element((None, "iq"))
+                       iq["type"] = "result"
+                       iq["to"] = froj.full()
+                       iq["from"] = toj.full()
+                       iq["id"] = ID
+                       si = iq.addElement("si")
+                       si["xmlns"] = disco.SI
+                       feature = si.addElement("feature")
+                       feature["xmlns"] = disco.FEATURE_NEG
+                       x = feature.addElement("x")
+                       x["xmlns"] = disco.XDATA
+                       x["type"] = "submit"
+                       field = x.addElement("field")
+                       field["var"] = "stream-method"
+                       value = field.addElement("value")
+                       value.addContent(disco.S5B)
+                       self.pytrans.send(iq)
+                       self.sessions[(froj.full(), sid)] = consumer
+
+               ft.FTSend(session, toj.userhost(), startTransfer, errOut, filename, filesize)
+       
+       def incomingS5B(self, el):
+               ID = el.getAttribute("id")
+               def errOut():
+                       self.pytrans.discovery.sendIqError(to=el.getAttribute("from"), fro=el.getAttribute("to"), ID=ID, xmlns=disco.S5B, etype="cancel", condition="item-not-found")
+
+               if el.getAttribute("type") != "set":
+                       return errOut()
+
+               toj = jid.intern(el.getAttribute("to"))
+               froj = jid.intern(el.getAttribute("from"))
+
+               query = el.query
+               if not (query and query.getAttribute("mode", "tcp") == "tcp"):
+                       return errOut()
+               sid = query.getAttribute("sid")
+               consumer = self.sessions.pop((froj.full(), sid), None)
+               if not consumer:
+                       return errOut()
+               streamhosts = []
+               for streamhost in query.elements():
+                       if streamhost.name == "streamhost":
+                               try:
+                                       JID = streamhost["jid"]
+                                       host = streamhost["host"]
+                                       port = int(streamhost["port"])
+                               except ValueError:
+                                       return errOut()
+                               except KeyError:
+                                       continue
+                               streamhosts.append((JID, host, port))
+
+
+               def gotStreamhost(host):
+                       for streamhost in streamhosts:
+                               if streamhost[1] == host:
+                                       jid = streamhost[0]
+                                       break
+                       else:
+                               LogEvent(WARN)
+                               return errOut()
+
+                       for connector in factory.connectors:
+                               # Stop any other connections
+                               try:
+                                       connector.stopConnecting()
+                               except error.NotConnectingError:
+                                       pass
+
+                       iq = Element((None, "iq"))
+                       iq["type"] = "result"
+                       iq["from"] = toj.full()
+                       iq["to"] = froj.full()
+                       iq["id"] = ID
+                       query = iq.addElement("query")
+                       query["xmlns"] = disco.S5B
+                       streamhost = query.addElement("streamhost-used")
+                       streamhost["jid"] = jid
+                       self.pytrans.send(iq)
+
+
+               # Try the streamhosts
+               factory = protocol.ClientFactory()
+               factory.protocol = ft.JEP65ConnectionSend
+               factory.consumer = consumer
+               factory.hash = utils.socks5Hash(sid, froj.full(), toj.full())
+               factory.madeConnection = gotStreamhost
+               factory.connectors = []
+               for streamhost in streamhosts:
+                       factory.connectors.append(reactor.connectTCP(streamhost[1], streamhost[2], factory))
+