tlspretense 0.6.1
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/.document +6 -0
- data/.gitignore +7 -0
- data/.rspec +1 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +41 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +231 -0
- data/Rakefile +44 -0
- data/bin/makeder.sh +6 -0
- data/bin/tlspretense +7 -0
- data/bin/view.sh +3 -0
- data/doc/general_setup.rdoc +288 -0
- data/doc/linux_setup.rdoc +64 -0
- data/lib/certmaker.rb +61 -0
- data/lib/certmaker/certificate_factory.rb +106 -0
- data/lib/certmaker/certificate_suite_generator.rb +120 -0
- data/lib/certmaker/ext_core/hash_indifferent_fetch.rb +12 -0
- data/lib/certmaker/runner.rb +27 -0
- data/lib/certmaker/tasks.rb +20 -0
- data/lib/packetthief.rb +167 -0
- data/lib/packetthief/handlers.rb +14 -0
- data/lib/packetthief/handlers/abstract_ssl_handler.rb +249 -0
- data/lib/packetthief/handlers/proxy_redirector.rb +26 -0
- data/lib/packetthief/handlers/ssl_client.rb +87 -0
- data/lib/packetthief/handlers/ssl_server.rb +174 -0
- data/lib/packetthief/handlers/ssl_smart_proxy.rb +143 -0
- data/lib/packetthief/handlers/ssl_transparent_proxy.rb +225 -0
- data/lib/packetthief/handlers/transparent_proxy.rb +183 -0
- data/lib/packetthief/impl.rb +11 -0
- data/lib/packetthief/impl/ipfw.rb +140 -0
- data/lib/packetthief/impl/manual.rb +54 -0
- data/lib/packetthief/impl/netfilter.rb +109 -0
- data/lib/packetthief/impl/pf_divert.rb +168 -0
- data/lib/packetthief/impl/pf_rdr.rb +192 -0
- data/lib/packetthief/logging.rb +49 -0
- data/lib/packetthief/redirect_rule.rb +29 -0
- data/lib/packetthief/util.rb +36 -0
- data/lib/ssl_test.rb +21 -0
- data/lib/ssl_test/app_context.rb +17 -0
- data/lib/ssl_test/certificate_manager.rb +33 -0
- data/lib/ssl_test/config.rb +79 -0
- data/lib/ssl_test/ext_core/io_raw_input.rb +31 -0
- data/lib/ssl_test/input_handler.rb +35 -0
- data/lib/ssl_test/runner.rb +110 -0
- data/lib/ssl_test/runner_options.rb +68 -0
- data/lib/ssl_test/ssl_test_case.rb +46 -0
- data/lib/ssl_test/ssl_test_report.rb +24 -0
- data/lib/ssl_test/ssl_test_result.rb +30 -0
- data/lib/ssl_test/test_listener.rb +140 -0
- data/lib/ssl_test/test_manager.rb +116 -0
- data/lib/tlspretense.rb +13 -0
- data/lib/tlspretense/app.rb +52 -0
- data/lib/tlspretense/init_runner.rb +115 -0
- data/lib/tlspretense/skel/ca/goodcacert.pem +19 -0
- data/lib/tlspretense/skel/ca/goodcakey.pem +27 -0
- data/lib/tlspretense/skel/config.yml +523 -0
- data/lib/tlspretense/version.rb +3 -0
- data/packetthief_examples/em_ssl_test.rb +73 -0
- data/packetthief_examples/redirector.rb +29 -0
- data/packetthief_examples/setup_iptables.sh +24 -0
- data/packetthief_examples/ssl_client_simple.rb +27 -0
- data/packetthief_examples/ssl_server_simple.rb +44 -0
- data/packetthief_examples/ssl_smart_proxy.rb +115 -0
- data/packetthief_examples/ssl_transparent_proxy.rb +97 -0
- data/packetthief_examples/transparent_proxy.rb +56 -0
- data/spec/packetthief/impl/ipfw_spec.rb +98 -0
- data/spec/packetthief/impl/manual_spec.rb +65 -0
- data/spec/packetthief/impl/netfilter_spec.rb +66 -0
- data/spec/packetthief/impl/pf_divert_spec.rb +82 -0
- data/spec/packetthief/impl/pf_rdr_spec.rb +133 -0
- data/spec/packetthief/logging_spec.rb +78 -0
- data/spec/packetthief_spec.rb +47 -0
- data/spec/spec_helper.rb +53 -0
- data/spec/ssl_test/certificate_manager_spec.rb +222 -0
- data/spec/ssl_test/config_spec.rb +76 -0
- data/spec/ssl_test/runner_spec.rb +360 -0
- data/spec/ssl_test/ssl_test_case_spec.rb +113 -0
- data/spec/ssl_test/test_listener_spec.rb +199 -0
- data/spec/ssl_test/test_manager_spec.rb +324 -0
- data/tlspretense.gemspec +35 -0
- metadata +262 -0
@@ -0,0 +1,143 @@
|
|
1
|
+
module PacketThief
|
2
|
+
module Handlers
|
3
|
+
|
4
|
+
# This SSL proxy needs a CA certificate (or chain) and private key. It then
|
5
|
+
# uses that information to automatically generate host certificates. It
|
6
|
+
# creates a host certificate by performing a pre-flight request to the
|
7
|
+
# original destination to get its certificate. It then doctors that
|
8
|
+
# certificate so that it's keypair is the supplied keypair, and so that it
|
9
|
+
# appears to be issued by the CA or last CA in the chain. Note that the
|
10
|
+
# supplied key must correspond to the last signing certificate, and that
|
11
|
+
# the key will be used as the doctored certificate's new key.
|
12
|
+
class SSLSmartProxy < SSLTransparentProxy
|
13
|
+
|
14
|
+
# How long to wait before giving up on a preflight request. Defaults to 5
|
15
|
+
# seconds.
|
16
|
+
attr_accessor :preflight_timeout
|
17
|
+
|
18
|
+
def initialize(tcpsocket, ca_chain, key, logger=nil)
|
19
|
+
super(tcpsocket, logger)
|
20
|
+
|
21
|
+
@preflight_timeout = 5
|
22
|
+
if ca_chain.kind_of? Array
|
23
|
+
@ca_chain = ca_chain
|
24
|
+
else
|
25
|
+
@ca_chain = [ca_chain]
|
26
|
+
end
|
27
|
+
@key = key
|
28
|
+
|
29
|
+
begin
|
30
|
+
# preflight the original destination.
|
31
|
+
@ctx.cert = lookup_cert
|
32
|
+
@ctx.extra_chain_cert = @ca_chain
|
33
|
+
@ctx.key = @key
|
34
|
+
rescue OpenSSL::SSL::SSLError, Errno::ECONNREFUSED, Errno::ETIMEDOUT => e
|
35
|
+
logerror "initialize: Failed to look up cert", :error => e
|
36
|
+
close_connection
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# def client_connected
|
41
|
+
# # don't try to connect to dest here.
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
|
45
|
+
def servername_cb(sslsock, hostname)
|
46
|
+
super
|
47
|
+
newctx = sslsock.context.dup
|
48
|
+
newctx.cert = lookup_cert(hostname)
|
49
|
+
newctx.extra_chain_cert = @ca_chain
|
50
|
+
newctx.key = @key
|
51
|
+
newctx
|
52
|
+
end
|
53
|
+
|
54
|
+
# Requests a certificate from the original destination.
|
55
|
+
def preflight_for_cert(hostname=nil)
|
56
|
+
logdebug "prefilight for: #{hostname}"
|
57
|
+
begin
|
58
|
+
pfctx = OpenSSL::SSL::SSLContext.new
|
59
|
+
pfctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
60
|
+
# Try to use a non-blocking Socket to create a short timeout.
|
61
|
+
pfs = Socket.new(:AF_INET, :SOCK_STREAM)
|
62
|
+
begin
|
63
|
+
pfs.connect_nonblock(Socket.sockaddr_in(dest_port, dest_host))
|
64
|
+
rescue Errno::EINPROGRESS
|
65
|
+
IO.select(nil, [pfs], nil, preflight_timeout) or raise Errno::ETIMEDOUT, "Connection to #{dest_host}:#{dest_port} timed out after #{preflight_timeout} seconds"
|
66
|
+
end
|
67
|
+
logdebug "preflight tcp socket connected"
|
68
|
+
pfssl = OpenSSL::SSL::SSLSocket.new(pfs, pfctx)
|
69
|
+
pfssl.hostname = hostname if hostname and pfssl.respond_to? :hostname
|
70
|
+
begin
|
71
|
+
pfssl.connect_nonblock
|
72
|
+
rescue IO::WaitReadable, IO::WaitWritable
|
73
|
+
IO.select([pfssl],[pfssl],nil,preflight_timeout) or raise Errno::ETIMEDOUT, "SSL handshake to #{dest_host}:#{dest_port} timed out #{preflight_timeout} seconds"
|
74
|
+
retry
|
75
|
+
end
|
76
|
+
logdebug "preflight complete"
|
77
|
+
return pfssl.peer_cert
|
78
|
+
rescue OpenSSL::SSL::SSLError, Errno::ECONNREFUSED, Errno::ETIMEDOUT => e
|
79
|
+
logerror "Error during preflight SSL connection: #{e} (#{e.class})"
|
80
|
+
raise
|
81
|
+
ensure
|
82
|
+
pfssl.close if pfssl
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
# Replace the issuer, the public key, and the signature.
|
88
|
+
def doctor_cert(oldcert, cacert, key)
|
89
|
+
newcert = oldcert.dup
|
90
|
+
newcert.issuer = cacert.subject
|
91
|
+
newcert.public_key = key.public_key
|
92
|
+
|
93
|
+
exts = newcert.extensions.dup
|
94
|
+
exts.each_index do |i|
|
95
|
+
if exts[i].oid == "authorityKeyIdentifier"
|
96
|
+
ef = OpenSSL::X509::ExtensionFactory.new
|
97
|
+
ef.subject_certificate = newcert
|
98
|
+
ef.issuer_certificate = cacert
|
99
|
+
newe = ef.create_ext_from_string("authorityKeyIdentifier=keyid:always")
|
100
|
+
exts[i] = newe
|
101
|
+
end
|
102
|
+
end
|
103
|
+
newcert.extensions = exts
|
104
|
+
|
105
|
+
sigalg = case newcert.signature_algorithm
|
106
|
+
when /MD5/i
|
107
|
+
OpenSSL::Digest::MD5.new
|
108
|
+
when /SHA1/i
|
109
|
+
OpenSSL::Digest::SHA1.new
|
110
|
+
when /SHA256/i
|
111
|
+
OpenSSL::Digest::SHA256.new
|
112
|
+
else
|
113
|
+
raise "Unsupported signing algorithm: #{@ctx.cert.signing_algorithm}"
|
114
|
+
end
|
115
|
+
newcert.sign(key, sigalg)
|
116
|
+
return newcert
|
117
|
+
end
|
118
|
+
|
119
|
+
# Check a class-level cache for an existing doctored certificate that
|
120
|
+
# corresponds to the requested hostname (IP address for non-SNI or
|
121
|
+
# pre-SNI lookup, and hostnames from SNI lookup).
|
122
|
+
def lookup_cert(hostname=nil)
|
123
|
+
@@certcache ||= {}
|
124
|
+
|
125
|
+
if hostname
|
126
|
+
cachekey = "#{hostname}:#{dest_port}"
|
127
|
+
else
|
128
|
+
cachekey = "#{dest_host}:#{dest_port}"
|
129
|
+
end
|
130
|
+
|
131
|
+
unless @@certcache.has_key? cachekey
|
132
|
+
logdebug "lookup_cert: cache miss, looking up and doctoring actual cert", :dest => cachekey
|
133
|
+
@@certcache[cachekey] = doctor_cert(preflight_for_cert(hostname), @ca_chain[0], @key)
|
134
|
+
else
|
135
|
+
logdebug "lookup_cert: cache hit", :dest => cachekey
|
136
|
+
end
|
137
|
+
logdebug "lookup_cert: returning", :subject => @@certcache[cachekey].subject
|
138
|
+
@@certcache[cachekey]
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,225 @@
|
|
1
|
+
module PacketThief
|
2
|
+
module Handlers
|
3
|
+
|
4
|
+
# Provides a transparent proxy for any TCP connection.
|
5
|
+
class SSLTransparentProxy < SSLServer
|
6
|
+
|
7
|
+
# Represents a connection out to the original destination.
|
8
|
+
class SSLProxyConnection < SSLClient
|
9
|
+
# Boolean that represents whether this handler has started to
|
10
|
+
# close/unbind. Used to ensure there is no unbind-loop between the two
|
11
|
+
# connections that make up the proxy.
|
12
|
+
attr_accessor :closed
|
13
|
+
|
14
|
+
# Boolean that represents whether the connection has connected yet.
|
15
|
+
attr_accessor :connected
|
16
|
+
|
17
|
+
# Sets up references to the client proxy connection handler that created
|
18
|
+
# this handler.
|
19
|
+
def initialize(tcpsocket, client_conn, ctx)
|
20
|
+
super(tcpsocket)
|
21
|
+
@client = client_conn
|
22
|
+
@ctx = ctx
|
23
|
+
|
24
|
+
@connected = false
|
25
|
+
@closed = false
|
26
|
+
sni_hostname = @client.dest_hostname if @client.dest_hostname
|
27
|
+
end
|
28
|
+
|
29
|
+
# send on successful handshake instead of on post_init.
|
30
|
+
def tls_successful_handshake
|
31
|
+
@client.dest_connected
|
32
|
+
@client._send_buffer
|
33
|
+
end
|
34
|
+
|
35
|
+
def tls_failed_handshake(e)
|
36
|
+
@client.dest_handshake_failed(e)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Transmit data sent by the destinaton to the client.
|
40
|
+
def receive_data(data)
|
41
|
+
@client.dest_recv(data)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Start the closing process and close the other connection if it is not
|
45
|
+
# already closing.
|
46
|
+
def unbind
|
47
|
+
@client.dest_closed
|
48
|
+
self.closed = true
|
49
|
+
@client.dest = nil
|
50
|
+
@client.close_connection_after_writing if @client and not @client.closed
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
# This holds a reference to the connection to the destination. If it is
|
56
|
+
# null, it hasn't been created yet.
|
57
|
+
attr_accessor :dest
|
58
|
+
|
59
|
+
# This holds a reference to the connection to the client. It is actually
|
60
|
+
# self.
|
61
|
+
attr_accessor :client
|
62
|
+
|
63
|
+
attr_reader :client_host, :client_port
|
64
|
+
|
65
|
+
# Override these before connecting to dest to change the dest connection.
|
66
|
+
attr_accessor :dest_host, :dest_port
|
67
|
+
|
68
|
+
# An internal buffer of packet data received from the client. It will grow until
|
69
|
+
# the destination connection connects.
|
70
|
+
attr_accessor :buffer
|
71
|
+
|
72
|
+
# Boolean that represents whether this handler has started to
|
73
|
+
# close/unbind. Used to ensure there is no unbind-loop between the two
|
74
|
+
# connections that make up the proxy.
|
75
|
+
attr_accessor :closed
|
76
|
+
|
77
|
+
# If a client specifies a TLS hostname extension (SNI) as the hostname,
|
78
|
+
# then we can forward that fact on to the real server. We can also use it
|
79
|
+
# to choose a certificate to present.
|
80
|
+
attr_accessor :dest_hostname
|
81
|
+
|
82
|
+
# The SSLContext that will be used on the connection to the destination.
|
83
|
+
# Initially, its verify_mode is set to OpenSSL::SSL::VERIFY_NONE.
|
84
|
+
attr_accessor :dest_ctx
|
85
|
+
|
86
|
+
def initialize(tcpsocket, logger=nil)
|
87
|
+
super
|
88
|
+
@closed = false
|
89
|
+
|
90
|
+
@client = self
|
91
|
+
@dest = nil
|
92
|
+
|
93
|
+
@buffer = []
|
94
|
+
@@activeconns ||= {}
|
95
|
+
|
96
|
+
@client_port, @client_host = Socket.unpack_sockaddr_in(get_peername)
|
97
|
+
@dest_port, @dest_host = PacketThief.original_dest(self)
|
98
|
+
|
99
|
+
if @@activeconns.has_key? "#{client_host}:#{client_port}"
|
100
|
+
logwarn "loop detected! Stopping the loop."
|
101
|
+
close_connection
|
102
|
+
return
|
103
|
+
end
|
104
|
+
|
105
|
+
@dest_ctx = OpenSSL::SSL::SSLContext.new
|
106
|
+
@dest_ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
107
|
+
|
108
|
+
end
|
109
|
+
|
110
|
+
# Just calls client_connected to keep things straightforward.
|
111
|
+
def tls_successful_handshake
|
112
|
+
client_connected
|
113
|
+
end
|
114
|
+
|
115
|
+
def receive_data(data)
|
116
|
+
client_recv data
|
117
|
+
end
|
118
|
+
|
119
|
+
# Start the closing process and close the other connection if it is not
|
120
|
+
# already closing.
|
121
|
+
def unbind
|
122
|
+
client_closed
|
123
|
+
@@activeconns.delete "#{client_host}:#{client_port}"
|
124
|
+
self.closed = true
|
125
|
+
# @dest.client = nil if @dest
|
126
|
+
@dest.close_connection_after_writing if @dest and not @dest.closed
|
127
|
+
end
|
128
|
+
|
129
|
+
# Initiate the connection to @dest_host:@dest_port.
|
130
|
+
def connect_to_dest
|
131
|
+
return if @dest
|
132
|
+
@dest = SSLProxyConnection.connect(@dest_host, @dest_port, self, @dest_ctx)
|
133
|
+
newport, newhost = Socket::unpack_sockaddr_in(@dest.get_sockname)
|
134
|
+
# Add the new connection to the list to prevent loops.
|
135
|
+
@@activeconns["#{newhost}:#{newport}"] = "#{dest_host}:#{dest_port}"
|
136
|
+
end
|
137
|
+
|
138
|
+
def _send_buffer
|
139
|
+
@buffer.each do |pkt|
|
140
|
+
@dest.send_data pkt
|
141
|
+
end
|
142
|
+
@buffer = []
|
143
|
+
end
|
144
|
+
|
145
|
+
|
146
|
+
# Queues up data to send to the remote host, only sending it if the
|
147
|
+
# connection to the remote host exists.
|
148
|
+
def send_to_dest(data)
|
149
|
+
@buffer << data
|
150
|
+
_send_buffer if @dest
|
151
|
+
end
|
152
|
+
|
153
|
+
# Sends data back to the client
|
154
|
+
def send_to_client(data)
|
155
|
+
send_data data
|
156
|
+
end
|
157
|
+
|
158
|
+
# Returns the certificate chain for the destination, or nil if the
|
159
|
+
# destination connection does not exist yet.
|
160
|
+
def dest_cert_chain
|
161
|
+
return @dest.sslsocket.peer_cert_chain if @dest
|
162
|
+
nil
|
163
|
+
end
|
164
|
+
|
165
|
+
#### Callbacks
|
166
|
+
|
167
|
+
# Set _dest_hostname_ in addition to the default behavior.
|
168
|
+
def servername_cb(sslsock, hostname)
|
169
|
+
@dest_hostname = hostname
|
170
|
+
|
171
|
+
super(sslsock, hostname)
|
172
|
+
end
|
173
|
+
|
174
|
+
# This method is called when a client connects, and the TLS handhsake has
|
175
|
+
# completed. The default behavior is to begin initating the connection to
|
176
|
+
# the original destination. Override this method to change its behavior.
|
177
|
+
def client_connected
|
178
|
+
connect_to_dest
|
179
|
+
end
|
180
|
+
|
181
|
+
# This method is called when the TLS handshake between the client and the
|
182
|
+
# proxy fails. It does nothing by default.
|
183
|
+
def client_handshake_failed
|
184
|
+
end
|
185
|
+
|
186
|
+
# This method is called when the proxy receives data from the client
|
187
|
+
# connection. The default behavior is to call send_to_dest(data) in order
|
188
|
+
# to foward the data on to the original destination. Override this method
|
189
|
+
# to analyze the data, or modify it before sending it on.
|
190
|
+
def client_recv(data)
|
191
|
+
send_to_dest data
|
192
|
+
end
|
193
|
+
|
194
|
+
# Called when the client connection closes. At present, it only provides
|
195
|
+
# informational utility.
|
196
|
+
def client_closed
|
197
|
+
end
|
198
|
+
|
199
|
+
# Called when the connection to and the TLS handshake between the proxy
|
200
|
+
# and the destination succeeds. The default behavior does nothing.
|
201
|
+
def dest_connected
|
202
|
+
end
|
203
|
+
|
204
|
+
# Called when the TLS handshake between the proxy and the destination
|
205
|
+
# fails.
|
206
|
+
def dest_handshake_failed(e)
|
207
|
+
end
|
208
|
+
|
209
|
+
# Called when the proxy receives data from the destination connection.
|
210
|
+
# The default behavior calls #dest_recv() to send the data to the client.
|
211
|
+
#
|
212
|
+
# Override it to analyze or modify the data.
|
213
|
+
def dest_recv(data)
|
214
|
+
send_to_client data
|
215
|
+
end
|
216
|
+
|
217
|
+
# Called when the original destination connection closes. At present, it only provides
|
218
|
+
# informational utility.
|
219
|
+
def dest_closed
|
220
|
+
end
|
221
|
+
|
222
|
+
end
|
223
|
+
|
224
|
+
end
|
225
|
+
end
|
@@ -0,0 +1,183 @@
|
|
1
|
+
module PacketThief
|
2
|
+
module Handlers
|
3
|
+
|
4
|
+
# Provides a transparent proxy for any TCP connection.
|
5
|
+
class TransparentProxy < ::EM::Connection
|
6
|
+
include Logging
|
7
|
+
|
8
|
+
# Represents a connection out to the original destination.
|
9
|
+
module ProxyConnection
|
10
|
+
# Boolean that represents whether this handler has started to
|
11
|
+
# close/unbind. Used to ensure there is no unbind-loop between the two
|
12
|
+
# connections that make up the proxy.
|
13
|
+
attr_accessor :closing
|
14
|
+
|
15
|
+
# Boolean that represents whether the connection has connected yet.
|
16
|
+
attr_accessor :connected
|
17
|
+
|
18
|
+
# Sets up references to the client proxy connection handler that created
|
19
|
+
# this handler.
|
20
|
+
def initialize(client_conn)
|
21
|
+
@client = client_conn
|
22
|
+
|
23
|
+
@connected = false
|
24
|
+
@closing = false
|
25
|
+
end
|
26
|
+
|
27
|
+
def post_init
|
28
|
+
@client._send_buffer
|
29
|
+
end
|
30
|
+
|
31
|
+
# Transmit data sent by the destinaton to the client.
|
32
|
+
def receive_data(data)
|
33
|
+
@client.dest_recv(data)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Start the closing process and close the other connection if it is not
|
37
|
+
# already closing.
|
38
|
+
def unbind
|
39
|
+
@client.dest_closed
|
40
|
+
self.closing = true
|
41
|
+
@client.close_connection_after_writing if @client and not @client.closing
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
# This holds a reference to the connection to the destination. If it is
|
47
|
+
# null, it hasn't been created yet.
|
48
|
+
attr_accessor :dest
|
49
|
+
|
50
|
+
# This holds a reference to the connection to the client. It is actually
|
51
|
+
# self.
|
52
|
+
attr_accessor :client
|
53
|
+
|
54
|
+
attr_reader :client_host, :client_port
|
55
|
+
|
56
|
+
# Override these before connecting to dest to change the dest connection.
|
57
|
+
attr_accessor :dest_host, :dest_port
|
58
|
+
|
59
|
+
# An internal buffer of packet data received from the client. It will grow until
|
60
|
+
# the destination connection connects.
|
61
|
+
attr_accessor :buffer
|
62
|
+
|
63
|
+
# Boolean that represents whether this handler has started to
|
64
|
+
# close/unbind. Used to ensure there is no unbind-loop between the two
|
65
|
+
# connections that make up the proxy.
|
66
|
+
attr_accessor :closing
|
67
|
+
|
68
|
+
# When the proxy should connect to a destination.
|
69
|
+
attr_accessor :when_to_connect_to_dest
|
70
|
+
|
71
|
+
def initialize(log=nil)
|
72
|
+
@logger = log
|
73
|
+
end
|
74
|
+
|
75
|
+
def post_init
|
76
|
+
@closing = false
|
77
|
+
|
78
|
+
@client = self
|
79
|
+
@dest = nil
|
80
|
+
|
81
|
+
@buffer = []
|
82
|
+
@@activeconns ||= {}
|
83
|
+
|
84
|
+
@client_port, @client_host = Socket.unpack_sockaddr_in(get_peername)
|
85
|
+
@dest_port, @dest_host = PacketThief.original_dest(self)
|
86
|
+
|
87
|
+
logdebug "Client connected", :client_host => client_host, :client => "#{@client_host}:#{@client_port}", :orig_dest => "#{@dest_host}:#{@dest_port}"
|
88
|
+
|
89
|
+
if @@activeconns.has_key? "#{client_host}:#{client_port}"
|
90
|
+
puts "Warning: loop detected! Stopping the loop."
|
91
|
+
close_connection
|
92
|
+
return
|
93
|
+
end
|
94
|
+
|
95
|
+
client_connected
|
96
|
+
end
|
97
|
+
|
98
|
+
def receive_data(data)
|
99
|
+
client_recv data
|
100
|
+
end
|
101
|
+
|
102
|
+
# Start the closing process and close the other connection if it is not
|
103
|
+
# already closing.
|
104
|
+
def unbind
|
105
|
+
client_closed
|
106
|
+
@@activeconns.delete "#{client_host}:#{client_port}"
|
107
|
+
self.closing = true
|
108
|
+
@dest.close_connection_after_writing if @dest and not @dest.closing
|
109
|
+
end
|
110
|
+
|
111
|
+
|
112
|
+
# Initiate the connection to @dest_host:@dest_port.
|
113
|
+
def connect_to_dest
|
114
|
+
logdebug "Connecting to #{@dest_host}:#{@dest_port}"
|
115
|
+
@dest = ::EM.connect(@dest_host, @dest_port, ProxyConnection, self)
|
116
|
+
newport, newhost = Socket::unpack_sockaddr_in(@dest.get_sockname)
|
117
|
+
# Add the new connection to the list to prevent loops.
|
118
|
+
@@activeconns["#{newhost}:#{newport}"] = "#{dest_host}:#{dest_port}"
|
119
|
+
end
|
120
|
+
|
121
|
+
def _send_buffer
|
122
|
+
@buffer.each do |pkt|
|
123
|
+
@dest.send_data pkt
|
124
|
+
end
|
125
|
+
@buffer = []
|
126
|
+
end
|
127
|
+
|
128
|
+
|
129
|
+
# Queues up data to send to the remote host, only sending it if the
|
130
|
+
# connection to the remote host exists.
|
131
|
+
def send_to_dest(data)
|
132
|
+
logdebug "sending to dest", :data => data
|
133
|
+
@buffer << data
|
134
|
+
_send_buffer if @dest
|
135
|
+
end
|
136
|
+
|
137
|
+
# Sends data back to the client
|
138
|
+
def send_to_client(data)
|
139
|
+
send_data data
|
140
|
+
end
|
141
|
+
|
142
|
+
|
143
|
+
# This method is called when a client connects. The default behavior is
|
144
|
+
# to begin initating the connection to the original destination. Override
|
145
|
+
# this method to change its behavior.
|
146
|
+
def client_connected
|
147
|
+
connect_to_dest
|
148
|
+
end
|
149
|
+
|
150
|
+
# This method is called when the proxy receives data from the client
|
151
|
+
# connection. The default behavior is to call send_to_dest(data) in order
|
152
|
+
# to foward the data on to the original destination. Override this method
|
153
|
+
# to analyze the data, or modify it before sending it on.
|
154
|
+
def client_recv(data)
|
155
|
+
logdebug("received from client", :data => data)
|
156
|
+
send_to_dest data
|
157
|
+
end
|
158
|
+
|
159
|
+
# Called when the proxy receives data from the destination connection.
|
160
|
+
# The default behavior calls #dest_recv() to send the data to the client.
|
161
|
+
#
|
162
|
+
# Override it to analyze or modify the data.
|
163
|
+
def dest_recv(data)
|
164
|
+
logdebug("received from dest", :data => data)
|
165
|
+
send_to_client data
|
166
|
+
end
|
167
|
+
|
168
|
+
# Called when the client connection closes. At present, it only provides
|
169
|
+
# informational utility.
|
170
|
+
def client_closed
|
171
|
+
logdebug("client closed connection")
|
172
|
+
end
|
173
|
+
|
174
|
+
# Called when the original destination connection closes. At present, it only provides
|
175
|
+
# informational utility.
|
176
|
+
def dest_closed
|
177
|
+
logdebug("dest closed connection")
|
178
|
+
end
|
179
|
+
|
180
|
+
end
|
181
|
+
|
182
|
+
end
|
183
|
+
end
|