hsume2-localtunnel 0.3.beta

Sign up to get free protection for your applications and to get access to all the features.
data/Manifest ADDED
@@ -0,0 +1,9 @@
1
+ Manifest
2
+ README.rdoc
3
+ Rakefile
4
+ bin/localtunnel
5
+ lib/localtunnel.rb
6
+ lib/localtunnel/net_ssh_gateway_patch.rb
7
+ lib/localtunnel/tunnel.rb
8
+ lib/localtunnel/tunnel_app.rb
9
+ server.py
data/README.rdoc ADDED
@@ -0,0 +1,60 @@
1
+ = localtunnel -- instant public tunnel to your local web server
2
+
3
+ == Install
4
+
5
+ To get the dependencies if you don't have them, type:
6
+
7
+ sudo apt-get install ruby ruby1.8-dev rubygems1.8 libopenssl-ruby
8
+
9
+ Now you can install localtunnel with RubyGems:
10
+
11
+ sudo gem install localtunnel
12
+
13
+ or to get the source:
14
+
15
+ git clone http://github.com/progrium/localtunnel.git
16
+
17
+ == Usage
18
+
19
+ localtunnel [options] <localport>
20
+ -k, --key FILE upload a public key for authentication
21
+
22
+ Localtunnel is a client to a free and open source reverse tunneling
23
+ service made specifically for web traffic. It's intended to be used to
24
+ temporarily expose local web servers to the greater Internet for
25
+ debugging, unit tests, demos, etc.
26
+
27
+ This is how you make your local port 8080 public:
28
+
29
+ $ localtunnel 8080
30
+ Port 8080 is now publicly accessible from http://8bv2.localtunnel.com ...
31
+
32
+ Using localtunnel is comparable to using SSH reverse/remote port
33
+ forwarding on a remote host that has GatewayPorts enabled, but without
34
+ all the configuration or the need of a host. The localtunnel command
35
+ works with a server component that is running on localtunnel.com,
36
+ which is provided as a free service.
37
+
38
+ If you have never run localtunnel before, you'll need to upload a public
39
+ key to authenticate. You do this once:
40
+
41
+ $ localtunnel -k ~/.ssh/id_rsa.pub 8080
42
+
43
+ After that, you shouldn't have to use -k again.
44
+
45
+ Localtunnel can be started before or after the local web server. It
46
+ tunnels through to the url given in that status message "publicly
47
+ accessible from..." for as long as the command is running. The tunnel
48
+ is closed if the command exits.
49
+
50
+ == Contributors
51
+
52
+ andyl (andy@r210.com)
53
+ Charles Merriam (charles.merriam@gmail.com)
54
+ Hunter Gillane (hunter.gillane@gmail.com)
55
+ Michael Sofaer (msofaer@pivotallabs.com)
56
+ Jeff Lindsay (progrium@gmail.com)
57
+
58
+ == License
59
+
60
+ MIT
data/Rakefile ADDED
@@ -0,0 +1,17 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'echoe'
4
+
5
+ Echoe.new('hsume2-localtunnel', '0.3.beta') do |p|
6
+ p.description = "instant public tunnel to your local web server"
7
+ p.url = "http://github.com/hsume2/localtunnel"
8
+ p.author = "Jeff Lindsay"
9
+ p.email = "jeff.lindsay@twilio.com"
10
+ p.has_rdoc = false
11
+ p.rdoc_pattern = //
12
+ p.rdoc_options = []
13
+ p.ignore_pattern = ["tmp/*", "script/*"]
14
+ p.executable_pattern = ["bin/*"]
15
+ p.runtime_dependencies = ["json >=1.2.4", "net-ssh >=2.0.22", "net-ssh-gateway >=1.0.1", "sinatra >=1.3.2", "POpen4 >=0.1.4"]
16
+ p.development_dependencies = []
17
+ end
data/bin/localtunnel ADDED
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env ruby
2
+ # Copyright (c) 2010 Jeff Lindsay
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person
5
+ # obtaining a copy of this software and associated documentation
6
+ # files (the "Software"), to deal in the Software without
7
+ # restriction, including without limitation the rights to use,
8
+ # copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the
10
+ # Software is furnished to do so, subject to the following
11
+ # conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be
14
+ # included in all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18
+ # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20
+ # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21
+ # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22
+ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23
+ # OTHER DEALINGS IN THE SOFTWARE.
24
+
25
+ require 'rubygems'
26
+ require 'optparse'
27
+ require 'localtunnel'
28
+
29
+ key = nil
30
+ options = OptionParser.new do |o|
31
+ o.banner = "Usage: localtunnel [options] <localport>"
32
+ o.on("-k", "--key FILE", "upload a public key for authentication") do |k|
33
+ key = File.exist?(k.to_s) ? File.open(k).read : nil
34
+ end
35
+ o.on('-h', "--help", "show this help") { puts o; exit }
36
+ end
37
+
38
+ args = options.parse!
39
+ local_port = args[0]
40
+ unless local_port.to_i.between?(1, 65535)
41
+ puts options
42
+ exit
43
+ end
44
+
45
+ t = LocalTunnel::Tunnel.new(local_port, key)
46
+ t.register_tunnel
47
+ t.start_tunnel
@@ -0,0 +1,45 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{hsume2-localtunnel}
5
+ s.version = "0.3.beta"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Jeff Lindsay"]
9
+ s.date = %q{2012-01-25}
10
+ s.default_executable = %q{localtunnel}
11
+ s.description = %q{instant public tunnel to your local web server}
12
+ s.email = %q{jeff.lindsay@twilio.com}
13
+ s.executables = ["localtunnel"]
14
+ s.extra_rdoc_files = ["README.rdoc", "Rakefile", "bin/localtunnel", "lib/localtunnel.rb", "lib/localtunnel/net_ssh_gateway_patch.rb", "lib/localtunnel/tunnel.rb", "lib/localtunnel/tunnel_app.rb", "server.py", "hsume2-localtunnel.gemspec"]
15
+ s.files = ["Manifest", "README.rdoc", "Rakefile", "bin/localtunnel", "lib/localtunnel.rb", "lib/localtunnel/net_ssh_gateway_patch.rb", "lib/localtunnel/tunnel.rb", "lib/localtunnel/tunnel_app.rb", "server.py", "hsume2-localtunnel.gemspec"]
16
+ s.homepage = %q{http://github.com/hsume2/localtunnel}
17
+ s.require_paths = ["lib"]
18
+ s.rubyforge_project = %q{hsume2-localtunnel}
19
+ s.rubygems_version = %q{1.5.3}
20
+ s.summary = %q{instant public tunnel to your local web server}
21
+
22
+ if s.respond_to? :specification_version then
23
+ s.specification_version = 3
24
+
25
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
26
+ s.add_runtime_dependency(%q<json>, [">= 1.2.4"])
27
+ s.add_runtime_dependency(%q<net-ssh>, [">= 2.0.22"])
28
+ s.add_runtime_dependency(%q<net-ssh-gateway>, [">= 1.0.1"])
29
+ s.add_runtime_dependency(%q<sinatra>, [">= 1.3.2"])
30
+ s.add_runtime_dependency(%q<POpen4>, [">= 0.1.4"])
31
+ else
32
+ s.add_dependency(%q<json>, [">= 1.2.4"])
33
+ s.add_dependency(%q<net-ssh>, [">= 2.0.22"])
34
+ s.add_dependency(%q<net-ssh-gateway>, [">= 1.0.1"])
35
+ s.add_dependency(%q<sinatra>, [">= 1.3.2"])
36
+ s.add_dependency(%q<POpen4>, [">= 0.1.4"])
37
+ end
38
+ else
39
+ s.add_dependency(%q<json>, [">= 1.2.4"])
40
+ s.add_dependency(%q<net-ssh>, [">= 2.0.22"])
41
+ s.add_dependency(%q<net-ssh-gateway>, [">= 1.0.1"])
42
+ s.add_dependency(%q<sinatra>, [">= 1.3.2"])
43
+ s.add_dependency(%q<POpen4>, [">= 0.1.4"])
44
+ end
45
+ end
@@ -0,0 +1,39 @@
1
+ require 'rubygems'
2
+ require 'net/ssh'
3
+ require 'net/ssh/gateway'
4
+
5
+ # http://groups.google.com/group/capistrano/browse_thread/thread/455c0c8a6faa9cc8?pli=1
6
+ class Net::SSH::Gateway
7
+ # Opens a SSH tunnel from a port on a remote host to a given host and port
8
+ # on the local side
9
+ # (equivalent to openssh -R parameter)
10
+ def open_remote(port, host, remote_port, remote_host = "127.0.0.1")
11
+ ensure_open!
12
+
13
+ @session_mutex.synchronize do
14
+ @session.forward.remote(port, host, remote_port, remote_host)
15
+ end
16
+
17
+ if block_given?
18
+ begin
19
+ yield [remote_port, remote_host]
20
+ ensure
21
+ close_remote(remote_port, remote_host)
22
+ end
23
+ else
24
+ return [remote_port, remote_host]
25
+ end
26
+ rescue Errno::EADDRINUSE
27
+ retry
28
+ end
29
+
30
+ # Cancels port-forwarding over an open port that was previously opened via
31
+ # #open_remote.
32
+ def close_remote(port, host = "127.0.0.1")
33
+ ensure_open!
34
+
35
+ @session_mutex.synchronize do
36
+ @session.forward.cancel_remote(port, host)
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,89 @@
1
+ require 'rubygems'
2
+ require 'net/ssh'
3
+ require 'net/ssh/gateway'
4
+ require 'net/http'
5
+ require 'uri'
6
+ require 'json'
7
+ require 'open4'
8
+
9
+ require 'localtunnel/net_ssh_gateway_patch'
10
+
11
+ module LocalTunnel; end
12
+
13
+ require 'localtunnel/tunnel_app'
14
+
15
+ class LocalTunnel::Tunnel
16
+
17
+ SHELL_HOOK_FILE = "./.localtunnel_callback"
18
+
19
+ attr_accessor :port, :key, :host
20
+
21
+ def initialize(port, key)
22
+ @port = port
23
+ @key = key
24
+ @host = ""
25
+ end
26
+
27
+ def register_tunnel(key=@key)
28
+ url = URI.parse("http://open.localtunnel.com/")
29
+ if key
30
+ resp = JSON.parse(Net::HTTP.post_form(url, {"key" => key}).body)
31
+ else
32
+ resp = JSON.parse(Net::HTTP.get(url))
33
+ end
34
+ if resp.has_key? 'error'
35
+ puts " [Error] #{resp['error']}"
36
+ exit
37
+ end
38
+ @host = resp['host'].split(':').first
39
+ @tunnel = resp
40
+ return resp
41
+ rescue
42
+ puts " [Error] Unable to register tunnel. Perhaps service is down?"
43
+ exit
44
+ end
45
+
46
+ def start_tunnel
47
+ port = @port
48
+ tunnel = @tunnel
49
+
50
+ TunnelApp.tunnel = tunnel['host']
51
+ tunnel_pid = fork { TunnelApp.run! }
52
+
53
+ at_exit do
54
+ Process.kill('KILL', tunnel_pid)
55
+ Process.waitpid(tunnel_pid)
56
+ end
57
+
58
+ error = ''
59
+
60
+ status = Open4::popen4("ssh -nNT -g -R *:#{tunnel['through_port']}:0.0.0.0:#{port} #{tunnel['user']}@#{@host} -o PasswordAuthentication=no") do |pid, i, o, e|
61
+ puts " " << tunnel['banner'] if tunnel.has_key? 'banner'
62
+ if File.exists?(File.expand_path(SHELL_HOOK_FILE))
63
+ system "#{SHELL_HOOK_FILE} ""#{tunnel['host']}""" if File.exists?(File.expand_path(SHELL_HOOK_FILE))
64
+ if !$?.success?
65
+ puts " An error occurred executing the callback hook #{SHELL_HOOK_FILE}"
66
+ puts " (Make sure it is executable)"
67
+ end
68
+ end
69
+ puts " Port #{port} is now publicly accessible from http://#{tunnel['host']} ..."
70
+
71
+ begin
72
+ while str = e.gets
73
+ error += str
74
+ puts str
75
+ end
76
+ rescue Interrupt
77
+ exit
78
+ end
79
+ end
80
+
81
+ if error =~ /Permission denied/
82
+ possible_key = Dir[File.expand_path('~/.ssh/*.pub')].first
83
+ puts " Failed to authenticate. If this is your first tunnel, you need to"
84
+ puts " upload a public key using the -k option. Try this:\n\n"
85
+ puts " localtunnel -k #{possible_key ? possible_key : '~/path/to/key'} #{port}"
86
+ exit
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,15 @@
1
+ require 'sinatra/base'
2
+
3
+ class LocalTunnel::Tunnel
4
+ class TunnelApp < Sinatra::Base
5
+ class << self
6
+ attr_accessor :tunnel
7
+ end
8
+
9
+ set :port, (ENV['LOCAL_TUNNEL_PORT'] || 9000)
10
+
11
+ get '/' do
12
+ TunnelApp.tunnel
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,2 @@
1
+ $: << File.expand_path(File.dirname(__FILE__))
2
+ require 'localtunnel/tunnel'
data/server.py ADDED
@@ -0,0 +1,106 @@
1
+ try:
2
+ from twisted.internet import pollreactor
3
+ pollreactor.install()
4
+ except: pass
5
+ from twisted.internet import protocol, reactor, defer, task
6
+ from twisted.web import http, proxy, resource, server
7
+ from twisted.python import log
8
+ import sys, time
9
+ import urlparse
10
+ import socket
11
+ import simplejson
12
+ import re
13
+
14
+ SSH_USER = 'localtunnel'
15
+ AUTHORIZED_KEYS = '/home/localtunnel/.ssh/authorized_keys'
16
+ PORT_RANGE = [32000, 64000]
17
+ BANNER = "This localtunnel service is brought to you by Twilio."
18
+ SSH_OPTIONS = 'command="/bin/echo Shell access denied",no-agent-forwarding,no-pty,no-user-rc,no-X11-forwarding '
19
+ KEY_REGEX = re.compile(r'^ssh-(\w{3}) [^\n]+$')
20
+
21
+ def port_available(port):
22
+ try:
23
+ socket.create_connection(['127.0.0.1', port]).close()
24
+ return False
25
+ except socket.error:
26
+ return True
27
+
28
+ def baseN(num,b=32,numerals="23456789abcdefghijkmnpqrstuvwxyz"):
29
+ return ((num == 0) and "0" ) or (baseN(num // b, b).lstrip("0") + numerals[num % b])
30
+
31
+ class LocalTunnelReverseProxy(proxy.ReverseProxyResource):
32
+ isLeaf = True
33
+
34
+ def __init__(self, user, host='127.0.0.1'):
35
+ self.user = user
36
+ self.tunnels = {}
37
+ proxy.ReverseProxyResource.__init__(self, host, None, None)
38
+
39
+ def find_tunnel_name(self):
40
+ name = baseN(abs(hash(time.time())))[0:4]
41
+ if (name in self.tunnels and not port_available(self.tunnels[name])) or name == 'open':
42
+ time.sleep(0.1)
43
+ return self.find_tunnel_name()
44
+ return name
45
+
46
+ def find_tunnel_port(self):
47
+ port = PORT_RANGE[0]
48
+ start_time = time.time()
49
+ while not port_available(port):
50
+ if time.time()-start_time > 3:
51
+ raise Exception("No port available")
52
+ port += 1
53
+ if port >= PORT_RANGE[1]: port = PORT_RANGE[0]
54
+ return port
55
+
56
+ def garbage_collect(self):
57
+ for name in self.tunnels:
58
+ if port_available(self.tunnels[name]):
59
+ del self.tunnels[name]
60
+
61
+ def install_key(self, key):
62
+ if not KEY_REGEX.match(key.strip()):
63
+ return False
64
+ key = ''.join([SSH_OPTIONS, key.strip(), "\n"])
65
+ fr = open(AUTHORIZED_KEYS, 'r')
66
+ if not key in fr.readlines():
67
+ fa = open(AUTHORIZED_KEYS, 'a')
68
+ fa.write(key)
69
+ fa.close()
70
+ fr.close()
71
+ return True
72
+
73
+ def register_tunnel(self, superhost, key=None):
74
+ if key and not self.install_key(key): return simplejson.dumps(dict(error="Invalid key."))
75
+ name = self.find_tunnel_name()
76
+ port = self.find_tunnel_port()
77
+ self.tunnels[name] = port
78
+ return simplejson.dumps(
79
+ dict(through_port=port, user=self.user, host='%s.%s' % (name, superhost), banner=BANNER))
80
+
81
+ def render(self, request):
82
+ host = request.getHeader('host')
83
+ name, superhost = host.split('.', 1)
84
+ if host.startswith('open.'):
85
+ request.setHeader('Content-Type', 'application/json')
86
+ return self.register_tunnel(superhost, request.args.get('key', [None])[0])
87
+ else:
88
+ if not name in self.tunnels: return "Not found"
89
+
90
+ request.content.seek(0, 0)
91
+ clientFactory = self.proxyClientFactoryClass(
92
+ request.method, request.uri, request.clientproto,
93
+ request.getAllHeaders(), request.content.read(), request)
94
+ self.reactor.connectTCP(self.host, self.tunnels[name], clientFactory)
95
+ return server.NOT_DONE_YET
96
+
97
+ #if 'location' in request.responseHeaders and host in request.responseHeaders['location']:
98
+ # # Strip out the port they think they need
99
+ # p = re.compile(r'%s\:\d+' % host)
100
+ # location = p.sub(host, request.responseHeaders['location'])
101
+ # request.responseHeaders['location'] = location
102
+
103
+
104
+ log.startLogging(sys.stdout)
105
+ reactor.listenTCP(80, server.Site(LocalTunnelReverseProxy(SSH_USER)))
106
+ reactor.run()
metadata ADDED
@@ -0,0 +1,164 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hsume2-localtunnel
3
+ version: !ruby/object:Gem::Version
4
+ hash: 31098141
5
+ prerelease: 4
6
+ segments:
7
+ - 0
8
+ - 3
9
+ - beta
10
+ version: 0.3.beta
11
+ platform: ruby
12
+ authors:
13
+ - Jeff Lindsay
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-01-25 00:00:00 -08:00
19
+ default_executable: localtunnel
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: json
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 23
30
+ segments:
31
+ - 1
32
+ - 2
33
+ - 4
34
+ version: 1.2.4
35
+ type: :runtime
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: net-ssh
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ hash: 35
46
+ segments:
47
+ - 2
48
+ - 0
49
+ - 22
50
+ version: 2.0.22
51
+ type: :runtime
52
+ version_requirements: *id002
53
+ - !ruby/object:Gem::Dependency
54
+ name: net-ssh-gateway
55
+ prerelease: false
56
+ requirement: &id003 !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ hash: 21
62
+ segments:
63
+ - 1
64
+ - 0
65
+ - 1
66
+ version: 1.0.1
67
+ type: :runtime
68
+ version_requirements: *id003
69
+ - !ruby/object:Gem::Dependency
70
+ name: sinatra
71
+ prerelease: false
72
+ requirement: &id004 !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ hash: 31
78
+ segments:
79
+ - 1
80
+ - 3
81
+ - 2
82
+ version: 1.3.2
83
+ type: :runtime
84
+ version_requirements: *id004
85
+ - !ruby/object:Gem::Dependency
86
+ name: POpen4
87
+ prerelease: false
88
+ requirement: &id005 !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ hash: 19
94
+ segments:
95
+ - 0
96
+ - 1
97
+ - 4
98
+ version: 0.1.4
99
+ type: :runtime
100
+ version_requirements: *id005
101
+ description: instant public tunnel to your local web server
102
+ email: jeff.lindsay@twilio.com
103
+ executables:
104
+ - localtunnel
105
+ extensions: []
106
+
107
+ extra_rdoc_files:
108
+ - README.rdoc
109
+ - Rakefile
110
+ - bin/localtunnel
111
+ - lib/localtunnel.rb
112
+ - lib/localtunnel/net_ssh_gateway_patch.rb
113
+ - lib/localtunnel/tunnel.rb
114
+ - lib/localtunnel/tunnel_app.rb
115
+ - server.py
116
+ - hsume2-localtunnel.gemspec
117
+ files:
118
+ - Manifest
119
+ - README.rdoc
120
+ - Rakefile
121
+ - bin/localtunnel
122
+ - lib/localtunnel.rb
123
+ - lib/localtunnel/net_ssh_gateway_patch.rb
124
+ - lib/localtunnel/tunnel.rb
125
+ - lib/localtunnel/tunnel_app.rb
126
+ - server.py
127
+ - hsume2-localtunnel.gemspec
128
+ has_rdoc: true
129
+ homepage: http://github.com/hsume2/localtunnel
130
+ licenses: []
131
+
132
+ post_install_message:
133
+ rdoc_options: []
134
+
135
+ require_paths:
136
+ - lib
137
+ required_ruby_version: !ruby/object:Gem::Requirement
138
+ none: false
139
+ requirements:
140
+ - - ">="
141
+ - !ruby/object:Gem::Version
142
+ hash: 3
143
+ segments:
144
+ - 0
145
+ version: "0"
146
+ required_rubygems_version: !ruby/object:Gem::Requirement
147
+ none: false
148
+ requirements:
149
+ - - ">="
150
+ - !ruby/object:Gem::Version
151
+ hash: 11
152
+ segments:
153
+ - 1
154
+ - 2
155
+ version: "1.2"
156
+ requirements: []
157
+
158
+ rubyforge_project: hsume2-localtunnel
159
+ rubygems_version: 1.5.3
160
+ signing_key:
161
+ specification_version: 3
162
+ summary: instant public tunnel to your local web server
163
+ test_files: []
164
+