"""
License
Copyright (C)
2002-2004 Dave Smith (dizzyd@jabber.org)
2007-2008 Fabio Forno (xmpp:ff@jabber.bluendo.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
$Id: proxy65.py 34 2008-12-14 12:48:21Z fabio.forno@gmail.com $
-------------------------------------------------------------------------
Portions Copyright 2008 Apple, Inc. All rights reserved.
"""
import os
import sys, socket
sys.path.append('/usr/share/proxy65');
from plistlib import readPlist, writePlist
from twisted.internet import protocol, reactor
from twisted.python import usage, log
from twisted.words.protocols.jabber import component,jid
from twisted.application import app, service, internet
try:
from twisted.internet.endpoints import _parse; from twisted.application import strports; strports._parse = _parse
except ImportError:
raise ImportError("Import failed for twisted 10.2 workaround")
import socks5
configPath = '/Library/Preferences/com.apple.Proxy65.plist';
config = {'jid':'',
'secret':'',
'rhost':'',
'rport':'',
'proxyips':''}
JEP65_GET = "/iq[@type='get']/query[@xmlns='http://jabber.org/protocol/bytestreams']"
JEP65_ACTIVATE = "/iq[@type='set']/query[@xmlns='http://jabber.org/protocol/bytestreams']/activate"
DISCO_GET = "/iq[@type='get']/query[@xmlns='http://jabber.org/protocol/disco#info']"
def hashSID(sid, initiator, target):
import sha
return sha.new("%s%s%s" % (sid, initiator, target)).hexdigest()
class JEP65Proxy(socks5.SOCKSv5):
def __init__(self, service):
socks5.SOCKSv5.__init__(self)
self.service = service
self.supportedAuthMechs = [socks5.AUTHMECH_ANON]
self.supportedAddrs = [socks5.ADDR_DOMAINNAME]
self.enabledCommands = [socks5.CMD_CONNECT]
self.addr = ""
def stopProducing(self):
self.transport.loseConnection()
def pauseProducing(self):
self.transport.stopReading()
def resumeProducing(self):
self.transport.startReading()
def connectRequested(self, addr, port):
if addr == "http://jabber.org/protocol/bytestreams":
self.connectCompleted(addr, 0)
self.transport.loseConnection()
return
self.addr = addr
if self.service.isActive(addr):
self.sendErrorReply(socks5.REPLY_CONN_NOT_ALLOWED)
return
if self.service.addConnection(addr, self):
self.connectCompleted(addr, 0)
self.transport.stopReading()
else:
self.sendErrorReply(socks5.REPLY_CONN_REFUSED)
def connectionLost(self, reason):
if self.state == socks5.STATE_CONNECT_PENDING:
self.service.removePendingConnection(self.addr, self)
else:
self.transport.unregisterProducer()
if self.peersock != None:
self.peersock.peersock = None
self.peersock.transport.unregisterProducer()
self.peersock = None
self.service.removeActiveConnection(self.addr)
class Service(component.Service, protocol.Factory):
def __init__(self, config):
self.jid = config["jid"]
self.activeAddresses = []
self.listeners = None
self.pendingConns = {}
self.activeConns = {}
def buildProtocol(self, addr):
return JEP65Proxy(self)
def componentConnected(self, xmlstream):
xmlstream.addObserver(JEP65_GET, self.onGetHostInfo)
xmlstream.addObserver(DISCO_GET, self.onDisco)
xmlstream.addObserver(JEP65_ACTIVATE, self.onActivateStream)
self.listeners.startService()
def componentDisconnected(self):
self.listeners.stopService()
def onGetHostInfo(self, iq):
iq.swapAttributeValues("to", "from")
iq["type"] = "result"
iq.query.children = []
for (ip, port) in self.activeAddresses:
s = iq.query.addElement("streamhost")
s["jid"] = self.jid
s["host"] = ip
s["port"] = str(port)
self.send(iq)
def onDisco(self, iq):
iq.swapAttributeValues("to", "from")
iq["type"] = "result"
iq.query.children = []
i = iq.query.addElement("identity")
i["category"] = "proxy"
i["type"] = "bytestreams"
i["name"] = "SOCKS5 Bytestreams Service"
iq.query.addElement("feature")["var"] = "http://jabber.org/protocol/bytestreams"
self.send(iq)
def onActivateStream(self, iq):
try:
fromJID = jid.internJID(iq["from"]).full()
activateJID = jid.internJID(unicode(iq.query.activate)).full()
sid = hashSID(
iq.query["sid"].encode("utf-8"), fromJID.encode("utf-8"), activateJID.encode("utf-8")
)
log.msg("Activation requested for: ", sid)
olist = self.pendingConns[sid]
del self.pendingConns[sid]
if len(olist) != 2:
log.msg("Activation for %s failed: insufficient participants", sid)
iq.swapAttributeValues("to", "from")
iq["type"] = "error"
iq.query.children = []
e = iq.addElement("error")
e["code"] = "405"
e["type"] = "cancel"
c = e.addElement("not-allowed")
c["xmlns"] = "urn:ietf:params:xml:ns:xmpp-stanzas"
self.send(iq)
for c in olist:
c.transport.loseConnection()
return
iq.swapAttributeValues("to", "from")
iq["type"] = "result"
iq.query.children = []
self.send(iq)
assert sid not in self.activeConns
self.activeConns[sid] = None
log.msg("Activating ", sid)
olist[0].peersock = olist[1]
olist[1].peersock = olist[0]
olist[0].transport.registerProducer(olist[1], 0)
olist[1].transport.registerProducer(olist[0], 0)
except Exception, e:
iq.swapAttributeValues("to", "from")
iq["type"] = "error"
iq.query.children = []
e = iq.addElement("error")
e["code"] = "404"
e["type"] = "cancel"
c = e.addElement("item-not-found")
c["xmlns"] = "urn:ietf:params:xml:ns:xmpp-stanzas"
self.send(iq)
def isActive(self, address):
return address in self.activeConns
def addConnection(self, address, connection):
log.msg("Adding connection: ", address, connection)
olist = self.pendingConns.get(address, [])
if len(olist) <= 1:
olist.append(connection)
self.pendingConns[address] = olist
return True
else:
return False
def removePendingConnection(self, address, connection):
olist = self.pendingConns[address]
if len(olist) == 1:
del self.pendingConns[address]
else:
olist.remove(connection)
self.pendingConns[address] = olist
def removeActiveConnection(self, address):
del self.activeConns[address]
def makeService(config):
try:
int(config["rport"], 10)
except ValueError:
print "Invalid router port (--rport) provided."
sys.exit(-1)
if config["secret"] == None:
print "Component secret (--secret) is a REQUIRED parameter. Configuration aborted."
sys.exit(-1)
if config["proxyips"] == None:
print "Proxy Network Addresses (--proxyips) is a REQUIRED parameter. Configuration aborted."
sys.exit(-1)
addresses = config["proxyips"].split(",")
validAddresses = []
for a in addresses:
try:
ip = None
port = 7777
if ":" in a:
ip, port = a.split(":")
else:
ip = a
socket.inet_pton(socket.AF_INET, ip)
validAddresses.append((ip, int(port)))
except socket.error:
print "Warning! Not using invalid proxy network address: ", a
if len(validAddresses) < 1:
print "0 Proxy Network Addresses (--proxyips) found. Configuration aborted."
sys.exit(-1)
c = component.buildServiceManager(config["jid"], config["secret"],
("tcp:%s:%s" % (config["rhost"], config["rport"])))
proxySvc = Service(config)
proxySvc.setServiceParent(c)
listeners = service.MultiService()
for (ip,port) in validAddresses:
listener = internet.TCPServer(port, proxySvc, interface=ip)
listener.setServiceParent(listeners)
proxySvc.listeners = listeners
proxySvc.activeAddresses = validAddresses
return c
application = service.Application("Proxy65")
try:
configFromFile = readPlist(configPath)
except:
print("Failed to find configuration file: %s" % (configPath))
sys.exit(1)
config.update(configFromFile)
service = makeService(config)
service.setServiceParent(application)