1 # Clarkway tunnelling service
2 # Copyright (C) 2007 Michael Schutte
4 # This program is free software; you can redistribute it and/or modify it
5 # under the terms of the GNU General Public License as published by the Free
6 # Software Foundation; either version 2 of the License, or (at your option)
9 # This program is distributed in the hope that it will be useful, but WITHOUT
10 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
25 # A master connects to a gateway and allows it to open arbitrary
30 attr_reader :host, :port, :gateway, :reconnect
33 # Create a master connecting to +host+:+port+, expecting the peer
34 # certificate with subject +gateway+, retrying every +reconnect+
37 def initialize(host, port, gateway, reconnect)
43 @reconnect = reconnect
49 # Loop forever, trying to establish a gateway connection. When
50 # connected to a gateway, start regular work.
55 tcps = TCPSocket.new @host, @port
56 ssls = SSL::SSLSocket.new tcps, context
58 ssls.sync_close = true
60 @logger.warn("Gateway not available.")
62 if ssls.peer_cert.subject.to_s != @gateway
63 @logger.warn("Possible spoofing attempt! Gateway " +
64 "identified as #{ssls.peer_cert.subject}, " +
65 "not starting operation.")
67 @logger.info("Gateway connection established.")
71 @logger.info("Terminating gracefully.")
76 ssls.close unless ssls.nil?
78 @logger.info("Retrying in #{@reconnect} seconds.")
82 @logger.info "Terminating gracefully."
91 # Stop master operations.
94 @logger.info("Master terminated.")
99 # Perform control (master - gateway) connection on +ctrl+.
103 if ctrl.gets !~ /^gateway connection ready$/
104 @logger.warn "Gateway protocol mismatch."
109 ctrl.puts "master, how can I help?"
111 while line = ctrl.gets
114 ctrl.puts "always happy to serve"
116 when /^already online$/
120 when /^(\d+): connect me to port (\d+) on (.*)$/
124 register_thread(id) do
125 do_connect ctrl, id, host, port
127 when /^(\d+):\ directly\ connect\ ([^:]*):(\d+)
128 \ to\ port\ (\d+)\ on\ ([^;]*);\ DN\ is\ (.*)$/x
135 register_thread(id) do
136 do_direct ctrl, id, chost, cport, dn, host, port
139 ctrl.puts "#{$1}: sorry"
145 @logger.warn("Gateway connection lost.")
147 @logger.warn("Gateway connection closed.")
152 # The select loop connecting Clarkway client and a TCP socket.
154 def select_loop(client, socket)
156 ios, dummy = select [client, socket]
159 when socket then client
163 data = io.sysread(BLOCKSIZE)
171 break if client.closed? or socket.closed?
176 # Handle a normal connection request.
178 def do_connect(ctrl, id, host, port)
179 @logger.info("Gateway requested connection to #{host}:#{port}.")
181 # Connect to the requested TCP server
183 socket = TCPSocket.new host, port
185 ctrl.puts "#{id}: connection failed"
189 # Connect back to the gateway
191 gateway = TCPSocket.new @host, @port
192 gateway = SSL::SSLSocket.new gateway, context
194 gateway.sync_close = true
196 if gateway.peer_cert.subject.to_s != @gateway
197 @logger.warn("Possible spoofing attempt! While connecting " +
198 "back, gateway identified as " +
199 "#{gateway.peer_cert.subject}.")
204 gateway.puts "this is connection #{id}"
205 @logger.info("Gateway connected to #{host}:#{port}.")
206 select_loop gateway, socket
208 ctrl.puts "#{id}: connection failed"
209 @logger.warn("I/O error between gateway and #{host}:#{port}.")
211 socket.close unless socket.closed?
212 gateway.close unless gateway.closed?
213 @logger.info("Closed connection between gateway and " +
219 # Directly connect back to the client.
221 def do_direct(ctrl, id, chost, cport, dn, host, port)
222 @logger.info("#{chost}:#{cport} requested connection to #{host}:#{port}.")
224 # Connect to the Clarkway client
226 tcpclient = TCPSocket.new chost, cport
227 client = SSL::SSLSocket.new tcpclient, context
229 client.sync_close = true
231 @logger.warn("Error on direct connection: #{e}")
232 ctrl.puts "#{id}: unable to connect"
236 if client.peer_cert.subject.to_s != dn
238 @logger.warn("Possible spoofing attempt! Wrong client ",
239 "identification on direct connection.")
240 ctrl.puts "#{id}: unable to connect"
244 ctrl.puts "#{id}: in progress"
246 # Connect to the requested TCP server
248 socket = TCPSocket.new host, port
250 client.puts "connection failed"
255 @logger.info("Client #{client.peer_cert.subject} " +
256 "from #{chost}:#{cport} connected to " +
259 client.puts "have fun"
260 select_loop client, socket
262 @logger.warn("I/O error between #{chost}:#{cport} " +
263 "and #{host}:#{port}.")
265 socket.close unless socket.closed?
266 client.close unless client.closed?
267 @logger.info("Closed connection to #{chost}:#{cport}.")