#!/usr/bin/perl
# A POE::Component::Server::FTP daemon that talks to its console counterpart

use strict;
#use warnings FATAL => "all";

use lib qw(/projects/lib);
use Filesys::Virtual;
use POE qw( Filter::Reference Component::Server::FTP Component::Server::TCP Component::TSTP Wheel::Run);
use YAML qw( Dump );


POE::Component::TSTP->create(
	PreSuspend => sub { print "I' being suspended! Quick, put me in the background!\n"; },
	PostSuspend => sub { print "Ahhhh, that's better!\n"; }
);

# ftp connection tracker
my %conn;
# console connection tracker
my %clients;
my @log;

my %ftpd_settings = (
#	Alias           => 'ftpd',						# ftpd is default
	ListenPort      => 21,							# port to listen on
	Domain			=> 'blah.net',					# domain shown on connection
	Version			=> 'ftpd '.$POE::Component::Server::FTP::VERSION,	# shown on connection, you can mimic...
	AnonymousLogin	=> 'deny',						# deny, allow
	FilesystemClass => 'Filesys::Virtual::Plain',	# Currently the only one available
	FilesystemArgs  => {
		'root_path' => '/',							# This is actual root for all paths
		'cwd'       => '/',							# Initial current working dir
		'home_path' => '/home',						# Home directory for '~'
	},
	# use 0 to disable these Limits
#	DownloadLimit	=> (50 * 1024),					# 50 kb/s per ip/connection (use LimitSceme to configure)
#	UploadLimit		=> (100 * 1024),				# 100 kb/s per ip/connection (use LimitSceme to configure)
	DownloadLimit	=> 0,							# 50 kb/s per ip/connection (use LimitSceme to configure)
	UploadLimit		=> 0,							# 100 kb/s per ip/connection (use LimitSceme to configure)
	LimitSceme		=> 'ip',						# ip or per (connection)
	LogLevel		=> 4,							# 4=debug, 3=less info, 2=quiet, 1=really quiet
	TimeOut			=> 120,							# Connection Timeout
	# NOT FINISHED, don't worry about it, its going to be a postprocesser instead
	VirusCheckerName => 'F-Prot',
	VirusCheckerCmd	=> '/usr/local/f-prot/f-prot',
	VirusCheckerParams => [qw( -list )],						# array ref of params
	VirusCheckerRegex => '//',
	VirusCheckerExitCodes => {
		0 => {
			op		=> 0, # do nothing
			text	=> 'Normal exit.  Nothing found, nothing done',
		},
		1 => {
			op		=> 1, # log it
			text	=> 'Unrecoverable error (for example, missing SIGN.DEF)',
		},
		2 => {
			op		=> 1, # log it
			text	=> 'Selftest failed (program has been modified)',
		},
		3 => {
			op		=> 2, # ???
			text	=> 'At least one virus-infected object was found',
		},
		4 => {
			op		=> 1, # log it
			text	=> 'Reserved, not currently in use',
		},
		5 => {
			op		=> 1, # log it
			text	=> 'Abnormal termination (scanning did not finish)',
		},
		6 => {
			op		=> 2, # ???
			text	=> 'At least one virus was removed',
		},
		7 => {
			op		=> 1, # log it
			text	=> 'Error, out of memory (should never happen, but well...)',
		},
		8 => {
			op		=> 2, # log it
			text	=> 'Something suspicious was found, but no recognized virus',
		},
	},
);

POE::Component::Server::FTP->spawn(%ftpd_settings);

POE::Component::Server::TCP->new(
	Alias => "cs",
	Address      => "127.0.0.1",
	Port         => 2021,
	ClientFilter => "POE::Filter::Reference", # Handles perl data structures
	ClientConnected => sub {
		my ($kernel, $heap, $session) = @_[KERNEL, HEAP, SESSION];
		print "Client connected from $heap->{remote_ip}\n";
		
		$clients{$session->ID} = { remote_ip => $heap->{remote_ip} };
		
		$kernel->call($session->ID => send => { log => \@log });
		$kernel->call($session->ID => send => { conn => \%conn });
	},
	ClientInput => sub {
		my ( $kernel, $heap, $session, $data ) = @_[ KERNEL, HEAP, SESSION, ARG0 ];
		my %response;
		if ( ref($data) eq 'HASH' ) {
			#require Data::Dumper;
			#print Data::Dumper->Dump([$data],['data']);
			if (exists($data->{event}) && $data->{event} eq 'chat_msg') {
				foreach my $c (keys %clients) {
					next if ($c == $session->ID);
					$kernel->call($c => send => $data);
				}
			}	
        } else {
			$response{status} = "Error: Bad request type: " . ref($data);
        }

		if (keys %response) {
			$kernel->call($session->ID => send => \%response);
		}
	},
	ClientDisconnected => sub {
		my ($kernel, $session) = $_[KERNEL, SESSION];
		print "Client Disconnected\n";
		
		if (ref($session)) {
			delete $clients{$session->ID};
		}
	},
	InlineStates => {
		send => sub {
			my ($kernel, $heap, $data) = @_[KERNEL, HEAP, ARG0];

			if ($heap->{client}) {
				#if (exists($data->{event}) && $data->{event} ne 'ftpd_write_log') {
				#	print Dump($data);
				#}
				$heap->{client}->put($data);
			}
			
		},
	},
);

POE::Session->create(
	inline_states => {
		_start => sub {
			my ($kernel, $heap) = @_[KERNEL, HEAP, ARG0];
			
			$kernel->post(ftpd => 'register');
	    },
		_default => sub {
			my ($kernel, $heap, $event, $arg) = @_[KERNEL, HEAP, ARG0, ARG1];
			return 0 if ($event =~ m/^_/);
			if (ref($arg->[0]) eq 'HASH') {
				my $o = $arg->[0];
				$o->{event} = $event;
			
				# collapse session references to just an id
				foreach (keys %{$o}) {
					if (ref($o->{$_}) eq 'POE::Session') {
						$o->{$_} = $o->{$_}->ID;
					}
				}
				
				# keep some of the logs
				if ($event eq 'ftpd_write_log') {
					$o->{datetime} = localtime();
					if ($#log > 1000) {
						pop(@log);
					}
					push(@log, $o);
				}

				if ($event eq 'ftpd_connected') {
					$conn{$o->{session}} = $o;
				}
				if ($event eq 'ftpd_login') {
					foreach my $z (qw( username password uid gid home)) {
						$conn{$o->{session}}->{$z} = $o->{$z};
					}
				}
				if ($event eq 'ftpd_disconnected') {
					delete $conn{$o->{session}};
				}
				
				# send the data to all connected consoles
				foreach my $c (keys %clients) {
					$kernel->call($c => send => $o);
				}
			} else {
				#print Dump($arg);
				print "unhandled event $event";
			}
			return 0;
		},
		ftpd_registered => sub {
			my ($kernel, $heap) = @_[KERNEL, HEAP];
			# put start code here, or use a call in _start like this: $kernel->call(ftpd => 'register');
			# before using ftpd
		},
		ftpd_accept => sub {
			my ($kernel, $heap, $data) = @_[KERNEL, HEAP, ARG0];

			# allow this ip address?
			if ($data->{peer_addr} eq '10.0.2.1') {
				#print "Connection denied from $data->{peer_addr}:$data->{peer_port} on $data->{local_ip}:$data->{local_port}";
				# disallow
				return 0;
			} else {
				#print "Connection accepted from $data->{peer_addr}:$data->{peer_port} on $data->{local_ip}:$data->{local_port}";
				# allow
				return 1;
			}
			
		},
	},
);

$poe_kernel->run();

