stubby 0.0.10 → 0.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +48 -10
- data/lib/stubby.rb +2 -0
- data/lib/stubby/cli/application.rb +36 -1
- data/lib/stubby/extensions/default.rb +66 -60
- data/lib/stubby/extensions/dns.rb +106 -101
- data/lib/stubby/extensions/dns/osx.rb +61 -41
- data/lib/stubby/extensions/http.rb +186 -181
- data/lib/stubby/extensions/reload.rb +38 -32
- data/lib/stubby/extensions/smtp.rb +47 -41
- data/lib/stubby/master.rb +34 -14
- data/lib/stubby/registry.rb +0 -1
- metadata +54 -16
- checksums.yaml +0 -7
@@ -1,58 +1,78 @@
|
|
1
1
|
# This module extracts the OS level DNS configuration dependency for OSX.
|
2
|
-
module
|
3
|
-
module
|
4
|
-
module
|
5
|
-
|
6
|
-
|
7
|
-
|
2
|
+
module Stubby
|
3
|
+
module Extensions
|
4
|
+
module DNS
|
5
|
+
module OSX
|
6
|
+
private
|
7
|
+
def servers_for(interface)
|
8
|
+
servers = `networksetup -getdnsservers '#{interface}'`
|
8
9
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
10
|
+
if servers =~ /There aren't any DNS Servers/
|
11
|
+
return ["empty"]
|
12
|
+
else
|
13
|
+
return servers.split("\n")
|
14
|
+
end
|
13
15
|
end
|
14
|
-
end
|
15
16
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
17
|
+
def setup_reference(interface)
|
18
|
+
return if interface.include? "*"
|
19
|
+
puts "[INFO] #{interface} configured with Stubby DNS. Will restore to #{@network_interfaces[interface]}"
|
20
|
+
`networksetup -setdnsservers '#{interface}' #{STUBBY_MASTER}`
|
21
|
+
end
|
22
|
+
|
23
|
+
def teardown_reference(interface, servers)
|
24
|
+
`networksetup -setdnsservers '#{interface}' #{servers.join(" ")}`
|
25
|
+
puts "[INFO] #{interface} original DNS settings restored #{servers.join(" ")}"
|
26
|
+
rescue => e
|
27
|
+
puts e.inspect
|
28
|
+
end
|
22
29
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
rescue => e
|
27
|
-
puts e.inspect
|
28
|
-
end
|
30
|
+
def setup_references
|
31
|
+
# TODO: if we connect to a new network, we'd like that to use us, too
|
32
|
+
return if @network_interfaces
|
29
33
|
|
30
|
-
|
31
|
-
|
32
|
-
|
34
|
+
network_interfaces.each do |interface, servers|
|
35
|
+
setup_reference(interface)
|
36
|
+
end
|
33
37
|
|
34
|
-
|
35
|
-
`networksetup listallnetworkservices`.split("\n").each do |interface|
|
36
|
-
next if interface.include? '*'
|
37
|
-
setup_reference(interface)
|
38
|
+
flush
|
38
39
|
end
|
39
40
|
|
40
|
-
|
41
|
-
|
41
|
+
def network_interfaces
|
42
|
+
# TODO: if we connect to a new network, we'd like that to use us, too
|
43
|
+
return @network_interfaces if @network_interfaces
|
42
44
|
|
43
|
-
|
44
|
-
|
45
|
+
@network_interfaces = {}
|
46
|
+
`networksetup listallnetworkservices`.split("\n").each do |interface|
|
47
|
+
next if interface.include? '*'
|
48
|
+
@network_interfaces[interface] = servers_for(interface)
|
49
|
+
end
|
50
|
+
|
51
|
+
@network_interfaces
|
52
|
+
end
|
45
53
|
|
46
|
-
|
47
|
-
|
54
|
+
# This is less of a complete restoration and actually
|
55
|
+
# a return to no defaults
|
56
|
+
def restore_references
|
57
|
+
network_interfaces.each do |interface, servers|
|
58
|
+
teardown_reference(interface, ["empty"])
|
59
|
+
end
|
48
60
|
end
|
49
61
|
|
50
|
-
|
51
|
-
|
62
|
+
def teardown_references
|
63
|
+
interfaces, @network_interfaces = @network_interfaces, nil
|
52
64
|
|
53
|
-
|
54
|
-
|
55
|
-
|
65
|
+
(interfaces || []).each do |interface, servers|
|
66
|
+
teardown_reference(interface, servers)
|
67
|
+
end
|
68
|
+
|
69
|
+
flush
|
70
|
+
end
|
71
|
+
|
72
|
+
def flush
|
73
|
+
`dscacheutil -flushcache`
|
74
|
+
end
|
75
|
+
end
|
56
76
|
end
|
57
77
|
end
|
58
78
|
end
|
@@ -4,237 +4,242 @@ require 'liquid'
|
|
4
4
|
require 'httpi'
|
5
5
|
require 'rack/ssl'
|
6
6
|
|
7
|
-
module
|
8
|
-
module
|
9
|
-
|
10
|
-
|
7
|
+
module Stubby
|
8
|
+
module Extensions
|
9
|
+
module HTTP
|
10
|
+
class NotFoundException < Exception
|
11
|
+
end
|
11
12
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
13
|
+
class HTTPApp < Sinatra::Base
|
14
|
+
class << self
|
15
|
+
def port
|
16
|
+
80
|
17
|
+
end
|
17
18
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
19
|
+
def run!(session, server_settings={})
|
20
|
+
puts self.inspect + ": " + port.to_s
|
21
|
+
|
22
|
+
set :bind, STUBBY_MASTER
|
23
|
+
set :port, port
|
24
|
+
set :stubby_session, session
|
25
|
+
set :server, 'thin'
|
25
26
|
|
26
|
-
|
27
|
-
|
27
|
+
super(:server_settings => server_settings)
|
28
|
+
end
|
28
29
|
|
29
|
-
|
30
|
-
|
31
|
-
|
30
|
+
def adapter(*names, &block)
|
31
|
+
names.each do |name|
|
32
|
+
adapters[name] = block
|
33
|
+
end
|
32
34
|
end
|
33
|
-
end
|
34
35
|
|
35
|
-
|
36
|
-
|
36
|
+
def adapters
|
37
|
+
@@adapters ||= {}
|
38
|
+
end
|
37
39
|
end
|
38
|
-
end
|
39
40
|
|
40
|
-
|
41
|
-
|
41
|
+
set :run, false
|
42
|
+
set :static, false
|
42
43
|
|
43
|
-
|
44
|
-
|
45
|
-
|
44
|
+
adapter "http-redirect" do
|
45
|
+
r = URI.parse(instruction_params["to"])
|
46
|
+
r.path = request.path if r.path.blank?
|
46
47
|
|
47
|
-
|
48
|
-
|
48
|
+
redirect to(r.to_s)
|
49
|
+
end
|
49
50
|
|
50
|
-
|
51
|
-
|
51
|
+
adapter "file" do
|
52
|
+
paths = []
|
52
53
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
54
|
+
if url.host == "-"
|
55
|
+
paths << File.expand_path(File.join("~/.stubby/#{request.host}", request.path))
|
56
|
+
paths << File.expand_path(File.join("/usr/local/stubby/#{request.host}", request.path))
|
57
|
+
else
|
58
|
+
paths << File.expand_path(File.join(url.path, request.path))
|
59
|
+
end
|
59
60
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
61
|
+
paths.each do |path|
|
62
|
+
next if path.index(url.path) != 0
|
63
|
+
|
64
|
+
p = [path, File.join(path, "index.html")].select { |path|
|
65
|
+
File.exists?(path) and !File.directory?(path)
|
66
|
+
}.first
|
66
67
|
|
67
|
-
|
68
|
-
|
68
|
+
send_file(p) and break unless p.nil?
|
69
|
+
end
|
69
70
|
|
70
|
-
|
71
|
-
|
71
|
+
not_found(paths.join(",\n"))
|
72
|
+
end
|
72
73
|
|
73
|
-
|
74
|
-
|
75
|
-
|
74
|
+
adapter "http-proxy" do
|
75
|
+
run_proxy("http")
|
76
|
+
end
|
76
77
|
|
77
|
-
|
78
|
-
|
79
|
-
|
78
|
+
adapter "https-proxy" do
|
79
|
+
run_proxy("https")
|
80
|
+
end
|
80
81
|
|
81
|
-
|
82
|
-
|
83
|
-
|
82
|
+
%w(get post put patch delete options link unlink).each do |method|
|
83
|
+
send(method, //) do
|
84
|
+
run_handler
|
85
|
+
end
|
84
86
|
end
|
85
|
-
end
|
86
87
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
88
|
+
private
|
89
|
+
def run_handler
|
90
|
+
if instruction.nil?
|
91
|
+
not_found
|
92
|
+
elsif adapter=self.class.adapters[url.scheme]
|
93
|
+
instance_eval &adapter
|
94
|
+
else
|
95
|
+
instance_eval &self.class.adapters["default"]
|
96
|
+
end
|
95
97
|
end
|
96
|
-
end
|
97
98
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
99
|
+
def run_proxy(scheme)
|
100
|
+
to = url.dup
|
101
|
+
to.scheme = scheme
|
102
|
+
to.path = request.path if to.path.empty?
|
103
|
+
to.query = request.query_string
|
103
104
|
|
104
|
-
|
105
|
+
puts "#{to.to_s} scheme: #{request.scheme}"
|
105
106
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
# TODO: this is a hack, should support streaming the bodies
|
110
|
-
# and handling the headers more systematically (allow
|
111
|
-
# keep-alives and transfer encoding)
|
112
|
-
r.headers.merge! Hash[(env.select { |k,v| v.is_a? String }.collect { |k,v| [k.gsub("HTTP_", "").gsub("_", "-"), v] })]
|
113
|
-
r.headers["HOST"] = request.host
|
114
|
-
r.headers["STUBBY-ENV"] = settings.stubby_session.environment
|
115
|
-
r.headers["STUBBY-KEY"] = settings.stubby_session.key(instruction)
|
116
|
-
r.headers["STUBBY-USER"] = settings.stubby_session.user_key
|
117
|
-
r.headers["X-FORWARDED-PROTO"] = request.scheme
|
118
|
-
r.headers["X-FORWARDED-FOR"] = request.ip
|
119
|
-
r.headers["CONNECTION"] = "close"
|
120
|
-
r.headers.delete "ACCEPT-ENCODING"
|
121
|
-
|
122
|
-
request.body.rewind
|
123
|
-
r.body = request.body.read
|
124
|
-
|
125
|
-
response = HTTPI.request(request.request_method.downcase.to_sym, r)
|
107
|
+
r = HTTPI::Request.new
|
108
|
+
r.url = to.to_s
|
126
109
|
|
127
|
-
|
110
|
+
# TODO: this is a hack, should support streaming the bodies
|
111
|
+
# and handling the headers more systematically (allow
|
112
|
+
# keep-alives and transfer encoding)
|
113
|
+
r.headers.merge! Hash[(env.select { |k,v| v.is_a? String }.collect { |k,v| [k.gsub("HTTP_", "").gsub("_", "-"), v] })]
|
114
|
+
r.headers["HOST"] = request.host
|
115
|
+
r.headers["STUBBY-ENV"] = settings.stubby_session.environment
|
116
|
+
r.headers["STUBBY-KEY"] = settings.stubby_session.key(instruction)
|
117
|
+
r.headers["STUBBY-USER"] = settings.stubby_session.user_key
|
118
|
+
r.headers["X-FORWARDED-PROTO"] = request.scheme
|
119
|
+
r.headers["X-FORWARDED-FOR"] = request.ip
|
120
|
+
r.headers["CONNECTION"] = "close"
|
121
|
+
r.headers.delete "ACCEPT-ENCODING"
|
122
|
+
|
123
|
+
request.body.rewind
|
124
|
+
r.body = request.body.read
|
125
|
+
|
126
|
+
response = HTTPI.request(request.request_method.downcase.to_sym, r)
|
127
|
+
|
128
|
+
response.headers.delete "TRANSFER-ENCODING"
|
128
129
|
|
129
|
-
|
130
|
-
|
130
|
+
status(response.code)
|
131
|
+
puts "response: #{response.headers}"
|
131
132
|
|
132
|
-
|
133
|
-
|
134
|
-
|
133
|
+
headers(response.headers)
|
134
|
+
body(response.body)
|
135
|
+
end
|
135
136
|
|
136
|
-
|
137
|
-
|
138
|
-
|
137
|
+
def forbidden
|
138
|
+
[403, "Forbidden"]
|
139
|
+
end
|
139
140
|
|
140
|
-
|
141
|
-
|
142
|
-
|
141
|
+
def not_found(resource="*unknown*")
|
142
|
+
[404, "Not Found:\n #{resource}"]
|
143
|
+
end
|
143
144
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
145
|
+
def instruction
|
146
|
+
@instruction ||= MultiJson.load(HTTPI.post("http://#{STUBBY_MASTER}:9000/rules/search.json",
|
147
|
+
trigger: "#{request.scheme}://#{request.host}").body)
|
148
|
+
end
|
148
149
|
|
149
|
-
|
150
|
-
|
151
|
-
|
150
|
+
def instruction_params
|
151
|
+
Rack::Utils.parse_nested_query url.query
|
152
|
+
end
|
152
153
|
|
153
|
-
|
154
|
-
|
154
|
+
def url
|
155
|
+
@url ||= URI.parse(instruction)
|
156
|
+
end
|
155
157
|
end
|
156
|
-
end
|
157
158
|
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
class << self
|
163
|
-
def port
|
164
|
-
443
|
165
|
-
end
|
159
|
+
# TODO: get SSL support running. Self signed cert
|
160
|
+
class HTTPSApp < HTTPApp
|
161
|
+
use Rack::SSL
|
166
162
|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
163
|
+
class << self
|
164
|
+
def port
|
165
|
+
443
|
166
|
+
end
|
171
167
|
|
172
|
-
|
173
|
-
:
|
174
|
-
:
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
:
|
179
|
-
|
168
|
+
def run!(session)
|
169
|
+
set :bind, STUBBY_MASTER
|
170
|
+
set :port, port
|
171
|
+
set :stubby_session, session
|
172
|
+
|
173
|
+
Rack::Handler::Thin.run(self, {
|
174
|
+
:Host => STUBBY_MASTER,
|
175
|
+
:Port => 443
|
176
|
+
}) do |server|
|
177
|
+
server.ssl = true
|
178
|
+
server.ssl_options = {
|
179
|
+
:verify_peer => false
|
180
|
+
}
|
181
|
+
end
|
182
|
+
rescue => e
|
183
|
+
puts "#{e.inspect}"
|
180
184
|
end
|
181
|
-
rescue => e
|
182
|
-
puts "#{e.inspect}"
|
183
185
|
end
|
184
186
|
end
|
185
|
-
end
|
186
187
|
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
188
|
+
class Server
|
189
|
+
def initialize
|
190
|
+
@log = Logger.new(STDOUT)
|
191
|
+
end
|
191
192
|
|
192
|
-
|
193
|
-
|
193
|
+
def run!(session, options)
|
194
|
+
return if options[:http] == false
|
194
195
|
|
195
|
-
|
196
|
-
|
197
|
-
|
196
|
+
@session = session
|
197
|
+
HTTPApp.run!(session)
|
198
|
+
end
|
198
199
|
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
200
|
+
# http://blah.com => localhost:3000
|
201
|
+
# =>
|
202
|
+
# http://blah.com => http-proxy://localhost:3000
|
203
|
+
def expand_rule(trigger, instruction, proto='http')
|
204
|
+
u = URI.parse(instruction)
|
205
|
+
|
206
|
+
(if u.scheme.nil?
|
207
|
+
{ trigger => "http-proxy://#{instruction}" }
|
208
|
+
elsif u.scheme == "http"
|
209
|
+
u.scheme = "http-proxy"
|
210
|
+
{ trigger => u.to_s }
|
211
|
+
else
|
212
|
+
{ trigger => instruction }
|
213
|
+
end).merge({
|
214
|
+
"#{trigger.gsub(proto + "://", "dns://")}/a" => "dns-a://#{STUBBY_MASTER}"
|
215
|
+
})
|
216
|
+
end
|
217
|
+
|
218
|
+
def stop!
|
219
|
+
HTTPApp.quit!
|
220
|
+
end
|
216
221
|
|
217
|
-
|
218
|
-
|
222
|
+
def restore!
|
223
|
+
# nil
|
224
|
+
end
|
219
225
|
end
|
220
|
-
end
|
221
226
|
|
222
|
-
|
223
|
-
|
224
|
-
|
227
|
+
class SSLServer < Server
|
228
|
+
def run!(session, options)
|
229
|
+
return if options[:https] == false
|
225
230
|
|
226
|
-
|
227
|
-
|
228
|
-
|
231
|
+
@session = session
|
232
|
+
HTTPSApp.run!(session)
|
233
|
+
end
|
229
234
|
|
230
|
-
|
231
|
-
|
232
|
-
|
235
|
+
def stop!
|
236
|
+
HTTPSApp.quit!
|
237
|
+
end
|
233
238
|
|
234
|
-
|
235
|
-
|
239
|
+
def expand_rule(trigger, instruction)
|
240
|
+
super(trigger, instruction, "https")
|
241
|
+
end
|
236
242
|
end
|
237
243
|
end
|
238
244
|
end
|
239
245
|
end
|
240
|
-
|