#!/usr/bin/perl
#
# Quick and dirty DCCSERVER manager for Linux
#
# $Id: dccserver,v 1.1 2005/01/16 05:49:49 nemesis Exp $
#
# Copyright (C) 2003 Kees Cook
# kees@outflux.net, http://outflux.net/
# 
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
# http://www.gnu.org/copyleft/gpl.html
#
use IO::Socket;
use IO::Handle;
use IO::Pipe;
use IO::Select;
use POSIX;

my $VERSION="0.1.0";

# get the port number from the command line
($NICK,$PATH,$PORT) = @ARGV;

die "Usage: NICK PATH PORT\nNeed nickname to announce as\n" unless (defined($NICK));
die "Usage: NICK PATH PORT\nNeed path for downloaded files\n" unless (defined($PATH));
die "Usage: NICK PATH PORT\nNeed port to listen on\n" unless (defined($PORT));

$master = IO::Socket::INET->new("Proto" => "tcp", "Reuse" => 1,
                                "Listen" => 20, "LocalPort" => $PORT);
die unless (defined($master));

# tell person what port they're going to be running on
print "Port = $PORT\n";

for (;;) {
        $client = $master->accept();
        if (defined($client)) {
                if (($child = fork()) == 0) {
                        # Child
                        undef $master;
                        DCCServerHandle($client);
                        exit(0);
                }
                # Master
                undef $client;
        }
        else {
                last;
        }
}


sub DCCServerHandle
{
        my($sock)=@_;
        my $line=<$sock>;
        chomp($line);

        my ($cmd,$arg)=split(/\s+/,$line,2);

        select($sock); $|=1; select(STDOUT); $|=1;
#        $sock->setvbuf(undef,_IONBF,0);
        
        my $host=$client->peerhost();
        my $port=$client->peerport();
        print "Connect from $host:$port ($line)\n";

        if    ($cmd == 100) {
		# chat with them

                print $sock "101 $NICK\n";

                # I want to SEE everything coming from the server
                # I want send to the server everything I TYPE

		# Do not close this socket across the fork
                fcntl($sock,F_SETFD,0);

		# Create stdin/stdout pair from the socket
                POSIX::dup2($sock->fileno,3) || die "dup2";
                POSIX::fcntl(3,F_SETFD,0);
                POSIX::dup2($sock->fileno,4) || die "dup2";
                POSIX::fcntl(4,F_SETFD,0);

                exec "/usr/bin/X11/xterm", "-e", "./dcc-chat", "3", "4";
                exit(1);
        }
        elsif ($cmd == 110) {
		# access to OUR fserve

		# We don't suppor this yet
                print $sock "150 $NICK\n";
                print "$host:$port Bonged\n";
        }
        elsif ($cmd == 120) {
		# send us a file

                my($clientnick,$bytes,$filename)=split(/\s+/,$arg,3);
                # 1: destroy any part of the fname that is a path
                my @parts = split(/\//,$filename);
                $filename = pop(@parts);
                my $attempt="$PATH/$filename";
                
                # 2: if file exists with same name get size
                my $size=0;
                if (-f $attempt) {
                        $size = -s _;
                }
                
                # 3: if size > sending size, bong
                if ($size > $bytes) {
                        print $sock "151 $NICK\n";
                        print "$filename Bonged (local file too big!)\n";
                }
                else {
                        # 4: open file
                        if (!open(DATA,">>$attempt")) {
                                print $sock "151 $NICK\n";
                                print "$filename Bonged (could not open file '$attempt'): $!\n";
                        }
                        else {
                                # 5: report resume location
                                print $sock "121 $NICK $size\n";

                                # 6: suck down file (reporting status)
                                my $got;
                                my $buffer;
                                my $prevpercent=-1;
                                my $starttime=time;
                                my $startsize=$size;
				# Try to grab 16K chunks at a time.  Make this bigger?
                                while ( $size!=$bytes && ($got = sysread($sock,$buffer,16384))>0) {
                                        if (syswrite(DATA,$buffer,$got)<$got) {
                                                print "$filename: write error: $!\n";
                                                last;
                                        }
                                        $size+=$got;

                                        # calc percent
                                        $percent=0;
                                        if ($bytes>0) {
                                                $percent=int($size*1000/$bytes);
                                        }
                                        if ($percent!=$prevpercent) {
                                                my $mins, $secs, $bps;
                                                my $time, $percent;
                                
                                                $prevpercent=$percent;
                                                
                                                $time=time;
                                                $bps=0;
                                                if ($time!=$starttime) {
                                                        $bps=($size-$startsize)/($time-$starttime);
                                                }
                                                $secs=0;
                                                if ($bps!=0) {
                                                        $secs=int(($bytes-$size)/$bps);
                                                }
                                        
                                                $time+=$secs;
                                                $mins=int($secs/60);
                                                $secs=$secs % 60;
                                                $time=scalar(localtime($time));
                                                
                                                printf("$filename: %dKB/s $size/$bytes %.1f%% ETA: %02d:%02d ($time)\n",
                                                        int($bps/1024),
                                                        ($percent/10),$mins,$secs);
                                        } 
                                }
                                $bps=($size-$startsize)/($time-$starttime);
                                if ($size==$bytes) {
                                        printf("$filename: DONE! (%dKB/s)\n",int($bps/1024));
                                }
                                else {
                                        printf("$filename: ABORTED! (%dKB/s)\n",
                                                int($bps/1024));
                                }
                                close(DATA);
                        }
                }
        }
        elsif ($cmd == 130) {
		# send them a file
	
		# We don't support this yet
                print $sock "150 $NICK\n";
                print "$host:$port Bonged\n";
        }
        else {
		# who knows!

                print $sock "150 $NICK\n";
                print "$host:$port Bonged\n";
        }
        undef $sock;
}
