107 lines
3.9 KiB
Python
107 lines
3.9 KiB
Python
try:
|
|
from twisted.internet import pollreactor
|
|
pollreactor.install()
|
|
except: pass
|
|
from twisted.internet import protocol, reactor, defer, task
|
|
from twisted.web import http, proxy, resource, server
|
|
from twisted.python import log
|
|
import sys, time
|
|
import urlparse
|
|
import socket
|
|
import simplejson
|
|
import re
|
|
|
|
SSH_USER = 'localtunnel'
|
|
AUTHORIZED_KEYS = '/home/localtunnel/.ssh/authorized_keys'
|
|
PORT_RANGE = [32000, 64000]
|
|
BANNER = "This localtunnel service is brought to you by Twilio."
|
|
SSH_OPTIONS = 'command="/bin/echo Shell access denied",no-agent-forwarding,no-pty,no-user-rc,no-X11-forwarding '
|
|
KEY_REGEX = re.compile(r'^ssh-(\w{3}) [^\n]+$')
|
|
|
|
def port_available(port):
|
|
try:
|
|
socket.create_connection(['127.0.0.1', port]).close()
|
|
return False
|
|
except socket.error:
|
|
return True
|
|
|
|
def baseN(num,b=32,numerals="23456789abcdefghijkmnpqrstuvwxyz"):
|
|
return ((num == 0) and "0" ) or (baseN(num // b, b).lstrip("0") + numerals[num % b])
|
|
|
|
class LocalTunnelReverseProxy(proxy.ReverseProxyResource):
|
|
isLeaf = True
|
|
|
|
def __init__(self, user, host='127.0.0.1'):
|
|
self.user = user
|
|
self.tunnels = {}
|
|
proxy.ReverseProxyResource.__init__(self, host, None, None)
|
|
|
|
def find_tunnel_name(self):
|
|
name = baseN(abs(hash(time.time())))[0:4]
|
|
if (name in self.tunnels and not port_available(self.tunnels[name])) or name == 'open':
|
|
time.sleep(0.1)
|
|
return self.find_tunnel_name()
|
|
return name
|
|
|
|
def find_tunnel_port(self):
|
|
port = PORT_RANGE[0]
|
|
start_time = time.time()
|
|
while not port_available(port):
|
|
if time.time()-start_time > 3:
|
|
raise Exception("No port available")
|
|
port += 1
|
|
if port >= PORT_RANGE[1]: port = PORT_RANGE[0]
|
|
return port
|
|
|
|
def garbage_collect(self):
|
|
for name in self.tunnels:
|
|
if port_available(self.tunnels[name]):
|
|
del self.tunnels[name]
|
|
|
|
def install_key(self, key):
|
|
if not KEY_REGEX.match(key.strip()):
|
|
return False
|
|
key = ''.join([SSH_OPTIONS, key.strip(), "\n"])
|
|
fr = open(AUTHORIZED_KEYS, 'r')
|
|
if not key in fr.readlines():
|
|
fa = open(AUTHORIZED_KEYS, 'a')
|
|
fa.write(key)
|
|
fa.close()
|
|
fr.close()
|
|
return True
|
|
|
|
def register_tunnel(self, superhost, key=None):
|
|
if key and not self.install_key(key): return simplejson.dumps(dict(error="Invalid key."))
|
|
name = self.find_tunnel_name()
|
|
port = self.find_tunnel_port()
|
|
self.tunnels[name] = port
|
|
return simplejson.dumps(
|
|
dict(through_port=port, user=self.user, host='%s.%s' % (name, superhost), banner=BANNER))
|
|
|
|
def render(self, request):
|
|
host = request.getHeader('host')
|
|
name, superhost = host.split('.', 1)
|
|
if host.startswith('open.'):
|
|
request.setHeader('Content-Type', 'application/json')
|
|
return self.register_tunnel(superhost, request.args.get('key', [None])[0])
|
|
else:
|
|
if not name in self.tunnels: return "Not found"
|
|
|
|
request.content.seek(0, 0)
|
|
clientFactory = self.proxyClientFactoryClass(
|
|
request.method, request.uri, request.clientproto,
|
|
request.getAllHeaders(), request.content.read(), request)
|
|
self.reactor.connectTCP(self.host, self.tunnels[name], clientFactory)
|
|
return server.NOT_DONE_YET
|
|
|
|
#if 'location' in request.responseHeaders and host in request.responseHeaders['location']:
|
|
# # Strip out the port they think they need
|
|
# p = re.compile(r'%s\:\d+' % host)
|
|
# location = p.sub(host, request.responseHeaders['location'])
|
|
# request.responseHeaders['location'] = location
|
|
|
|
|
|
log.startLogging(sys.stdout)
|
|
reactor.listenTCP(80, server.Site(LocalTunnelReverseProxy(SSH_USER)))
|
|
reactor.run()
|