# -*- Mode: Python; tab-width: 4 -*-

# A 'forwarding' FTP proxy.  In combination with PASV mode, allows you
# pipe the control connection through a separate host/process.
                           
#    +--------+         high bandwidth        +----------------------+
#    | client |<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<| connected FTP server |
#    +--------+                               +----------------------+
#              \                             /
#               \                           / 
#                \                         /  
#                 \                       /   
#                  \                     /    
#                   \                   /     
#                    \                 /      
#     low bandwidth ->\               /<- low bandwidth
#                      \             /   
#                       \           /    
#                        \         /  
#                         \       /   
#                         +--------+  
#                         | medusa |  
#                         +--------+

import asynchat
import asyncore
import socket
import sys
import time

# this is the client that connects to the 'real' ftp server.
class forwarding_proxy (asynchat.async_chat):
	def __init__ (self, channel):
		asynchat.async_chat.__init__ (self)
		self.channel = channel
		self.set_terminator ('\r\n')
		self.create_socket (socket.AF_INET, socket.SOCK_STREAM)
		self.connect (channel.server.proxy_addr)
		self.data = ''

	def handle_connect (self):
		print 'connected to proxy server'

	def collect_incoming_data (self, data):
		self.data = self.data + data

	def found_terminator (self):
		line = self.data
		print '<==',line
		self.data = ''
		self.channel.push (line+'\r\n')

	def handle_close (self):
		print 'proxy closed'
		self.channel.close()
		self.close()

# this is the server channel that redirects through the proxy
class forwarding_ftp_channel (asynchat.async_chat):
	def __init__ (self, server, conn, addr):
		asynchat.async_chat.__init__ (self, conn)
		self.server = server
		self.set_terminator ('\r\n')
		self.addr = addr
		self.proxy = forwarding_proxy (self)
		self.data = ''

	def collect_incoming_data (self, data):
		self.data = self.data + data

	def found_terminator (self):
		line = self.data
		print '==>',line
		self.data = ''
		self.proxy.push (line+'\r\n')

	def handle_close (self):
		print 'channel closed'
		self.proxy.close()
		self.close()

class forwarding_ftp_server (asyncore.dispatcher):
	channel_class = forwarding_ftp_channel

	def __init__ (self, local_addr, proxy_addr):
		asyncore.dispatcher.__init__ (self)
		self.create_socket (socket.AF_INET, socket.SOCK_STREAM)
		self.bind (local_addr)
		self.listen (5)
		print 'Forwarding FTP server started at %s\n\tLocal Address:%s\n\tRemote Address:%s\n' % (
			time.ctime(time.time()),
			local_addr,
			proxy_addr
			)
		self.proxy_addr = proxy_addr

	def handle_accept (self):
		conn, addr = self.accept()
		print 'incoming connection from %s' % repr(addr)
		sys.stdout.flush()
		self.channel_class (self, conn, addr)

if __name__ == '__main__':
	import sys
	import string
	if len(sys.argv) == 1:
		print 'Usage: %s <local_ip> <local_port> <remote_host>' % sys.argv[0]
	else:
		fs = forwarding_ftp_server (
			(sys.argv[1], string.atoi (sys.argv[2])),
			(sys.argv[3], 21)
			)
		asyncore.loop()