Class: FtpProxy::Session

Inherits:
Object
  • Object
show all
Defined in:
lib/ftpproxy.rb

Instance Method Summary collapse

Constructor Details

#initialize(socket, params = {}) ⇒ Session

Returns a new instance of Session.



51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/ftpproxy.rb', line 51

def initialize socket, params={}
	@client = socket
	@server = nil
	@relay  = nil
	@log    = params[:log]
	@mode   = params[:mode]
	if @proxy = params[:proxy]
		raise ArgumentError, 'malformed proxy' unless @proxy =~ /^(.*)(?::(\d+))?$/
		@proxy = [$1, $2]
	end
rescue
	@log.error $!
	raise
end

Instance Method Details

#cmd_pasv(params) ⇒ Object



179
180
181
182
183
184
185
186
187
188
189
# File 'lib/ftpproxy.rb', line 179

def cmd_pasv params
	socket = TCPServer.open @server.addr[3], 0
	host = socket.addr[3]
	port = socket.addr[1]
	@log.debug "Waiting for passive connection from client on #{host}:#{port}"
	write_client "227 Entering Passive Mode (#{(host.split('.') + port.divmod(256)).join ','})"
	make_relay socket.accept, :passive
rescue
	@log.error $!
	write_client '425 Data connection failed'
end

#cmd_port(params) ⇒ Object



165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/ftpproxy.rb', line 165

def cmd_port params
	nums = params.split(',')
	raise 'invalid parameters' unless nums.length == 6
	host = nums[0, 4].join('.')
	port = nums[4].to_i * 256 + nums[5].to_i
	@log.debug "Opening active connection to client on #{host}:#{port}"
	socket = TCPSocket.open(host, port)
	write_client "200 Connection established (#{port})"
	make_relay socket, :active
rescue
	@log.error $!
	write_client '425 Data connection failed'
end

#cmd_user(user) ⇒ Object



146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/ftpproxy.rb', line 146

def cmd_user user
	raise 'malformed user string' unless user =~ /(.+)@(.+?)(?::(\d+))?$/
	user, host, port = $1, $2, $3
	# this is to double-proxy through an upstream proxy
	if @proxy
		user, pass = "#{user}@#{host}#{':' + port.to_s if port}", pass
		host, port = @proxy
	end
	@log.debug "Connecting to #{host}:#{port || 21}"
	@server = TCPSocket.open host, (port || '21').to_i
	raise 'invalid server response' unless read_server[0] == ?2
	write_server "USER #{user}"
rescue
	@log.error $!
	write_client '530 Not logged in.'
else
	relay_cmd
end

#make_relay(clientdata, mode) ⇒ Object



191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
# File 'lib/ftpproxy.rb', line 191

def make_relay clientdata, mode
	@relay.close if @relay
	case mode = @mode || mode
	when :active
		socket = TCPServer.open @server.addr[3], 0
		host = socket.addr[3]
		port = socket.addr[1]
		@log.debug "Waiting for active connection from server on #{host}:#{port}"
		write_server "PORT #{(host.split('.') + port.divmod(256)).join ','}"
		raise 'invalid server response' unless read_server[0] == ?2
		@relay = ActiveRelay.new socket, clientdata
	when :passive
		write_server 'PASV'
		raise 'invalid server response' unless nums = read_server[/^227[^(]*\(([^)]*)\)/, 1]
		nums = nums.split(',')
		raise 'invalid server response' unless nums.length == 6
		host = nums[0, 4].join('.')
		port = nums[4].to_i * 256 + nums[5].to_i
		@log.debug "Opening passive connection to server on #{host}:#{port}"
		@relay = Relay.new TCPSocket.new(host, port), clientdata
	else
		raise 'unhandled server data channel mode - %p' % mode
	end
end

#proxy(line) ⇒ Object



124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/ftpproxy.rb', line 124

def proxy line
	write_server line
	if %w[125 150].include? relay_cmd[0, 3]
		begin
			# relay data channel
			@relay.relay
		rescue
			@log.error $!
			write_client '425 Data connection failed'
		else
			relay_cmd
		end
	end
end

#read_clientObject



80
81
82
83
84
# File 'lib/ftpproxy.rb', line 80

def read_client
	line = @client.gets
	@log.debug "@CLIENT>> #{line.inspect}"
	line
end

#read_serverObject

handle multiline responses



67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/ftpproxy.rb', line 67

def read_server
	data = @server.readline
	if data[3] == ?-
		code = data[0, 3]
		begin
			line = @server.readline
			data << line
		end until line[0, 3] == code and line[3] != ?-
	end
	@log.debug "@SERVER>> #{data.inspect}"
	data.gsub(/\r\n|\r/, "\n")
end

#relay_cmdObject

relay command channel



140
141
142
143
144
# File 'lib/ftpproxy.rb', line 140

def relay_cmd
	resp = read_server
	resp.each { |line| write_client line.chomp }
	resp
end

#startObject



96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/ftpproxy.rb', line 96

def start
	write_client "220 #{Socket.gethostname} (#{File.basename $0}) #{Time.now.strftime '%a, %d %b %Y %H:%M:%S'}"
	while line = read_client
		line.chomp!
		cmd, params = line.split ' ', 2
		cmd = cmd.downcase
		params = nil if params == ''
		msg = "cmd_#{cmd}"
		if @server and WHITELIST.include?(cmd)
			proxy line
		elsif cmd == 'user' or @server && respond_to?(msg)
			send msg, params
		elsif WHITELIST.include?(cmd) or respond_to?(msg)
			write_client '530 Not logged in.'
		else
			write_client '500 Syntax error, command unrecognized.'
		end
	end
rescue Errno::EINVAL
	# common, when client just aborts
rescue
	@log.warn $!
ensure
	@client.close if !@client.closed?
	@server.close if @server and !@server.closed?
	@relay.close if @relay
end

#write_client(str) ⇒ Object



91
92
93
94
# File 'lib/ftpproxy.rb', line 91

def write_client str
	@log.debug "@CLIENT<< #{str.inspect}"
	@client.write str + "\r\n"
end

#write_server(str) ⇒ Object



86
87
88
89
# File 'lib/ftpproxy.rb', line 86

def write_server str
	@log.debug "@SERVER<< #{str.inspect}"
	@server.write str + "\r\n"
end