#!/usr/bin/perl -w
use Net::Pcap;
use NetPacket::Ethernet;
use NetPacket::IP;
use NetPacket::UDP;
use Crypt::CBC;
use MIME::Base64;
use Getopt::Std;
use Sys::Syslog;
use Sys::Syslog qw(:DEFAULT setlogsock);
use Sys::Hostname;
use POSIX qw(setsid);

use strict;

our ( %opt, %Prefs );

# process command line options and read the config file
sub getOptions {
    chomp( my $home = `pwd` );
    my $opt_string = 'dhc:';
    getopts( "$opt_string", \%opt ) or usage();

    usage() if $opt{h};

    # the config file should be found in the same path as the daemon
    $0 =~ /^(\.+|\/\S*)(\/\S+?)$/;
  CASE: {
        ( $1 eq '.' ) && do {
            $opt{c} = $home . "/$opt{c}";
            last CASE;
        };
        ( $1 eq '..' ) && do {
            $opt{c} = $home . "/" . $1 . "/$opt{c}";
            last CASE;
        };
        $opt{c} = $1 . "/$opt{c}";
    }

    if ($>) { die "Only root can execute this program.\n"; }
}

# read the config file
sub readConfig {
    if ( -f $opt{c} && -T $opt{c} ) {
        print "Reading config: $opt{c} \n";
        open( CONFIG, "< $opt{c}" ) or die "Can't open $opt{c} : $!";
    } else {
        die "Bad or missing config file $opt{c}";
    }

    while (<CONFIG>) {
        chomp;       # remove newline
        s/#.*//;     # skip  comments
        s/^\s+//;    # remove leading  whitespace
        s/\s+$//;    # remove trailing whitaspace
        next unless length;
        my ( $var, $value ) = split( /\s*=\s*/, $_, 2 );
        $Prefs{$var} = $value;
    }
    close CONFIG;
}

# print some usage hints
sub usage() {
    system "clear";
    print STDERR << "EOF";

   +-----------------------------------------------------------+
   |                       = B O U N C E R =                   |
   |                       jcb 2004  (c) LNM                   |
   +-----------------------------------------------------------+

    usage: $0 [ -h ] [ -d ] -c file  
     
     -h        : this help message
     -d        : log debugging infos
     -c <file> : config file name
     -f [0..99]: remote function code
     

    example: $0 -d -c ./bouncer.cfg 
    
    You have to specify the name of a configuration file which
    should reside in the same directory as the daemon itself. 
    If you don\'t define an interface in your config file, the 
    program tries to detect an appropriate interface for you. 
    
    
    
EOF

    exit;
}

# Passwort generator
sub generatePW {
    my $delta = 0;
    if ( scalar(@_) ) { $delta = shift }
    syslog( 'debug', "Time delta: $delta" );
    my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) =
      localtime( time() );
    my $seedval = ( $yday * 24 + $hour ) * 60 + $min + $delta;
    srand($seedval);
    my @KeyElem = ( ( 'a' .. 'z' ), ( 'A' .. 'Z' ), ( 0 .. 9 ) );
    my $pw      = '';

    for ( 1 .. $Prefs{pwlen} ) {
        my $i = int( rand(61) );
        $pw .= $KeyElem[$i];
    }
    if ( $opt{d} ) {
        syslog( 'debug', "seed: $seedval password: $pw" );
    }
    return $pw;
}

# Redirect standard file descriptors from and to /dev/null
# Fork a child process, kill the parent
# Create a new session with the calling process as the
# leader of the session and the process group
# Detach the controlling terminal
sub daemonize {
    chdir '/' or die "Can't chdir to /: $!";
    open STDIN,  '/dev/null'  or die "Can't read /dev/null: $!";
    open STDOUT, '>/dev/null' or die "Can't write /dev/null: $!";
    defined( my $pid = fork ) or die "Can't fork: $!";
    exit if $pid;
    setsid or die "Can't start a new session: $!";
    syslog( 'notice', 'bouncer daemon started, PID: ' . $$ );
    umask 0;
    open STDERR, '>&STDOUT' or die "Can't dup stdout: $!";
}

# callback function
sub analyzePackets {
    my ( $user_data, $header, $packet ) = @_;

    #   Strip ethernet encapsulation of captured packet
    my $ether_data = NetPacket::Ethernet::strip($packet);

    #   Decode contents of TCP/IP packet contained within
    #   captured ethernet packet and then the UDP datagram
    #   contained within the ip packet
    my $ip  = NetPacket::IP->decode($ether_data);
    my $udp = NetPacket::UDP->decode( $ip->{'data'} );

    #   Print all out where its coming from and where its
    #   going to!
    syslog( 'debug',
            "REVEIVED: "
          . $ip->{'src_ip'} . ":"
          . $udp->{'src_port'} . " -> "
          . $ip->{'dest_ip'} . ":"
          . $udp->{'dest_port'} );

    my $dat = $udp->{'data'};
    for ( my $i = $Prefs{delta} * -1 ; $i <= $Prefs{delta} ; $i++ ) {
        my $pw = generatePW($i);
        syslog( 'debug', "Received data: $dat" );
        syslog( 'debug', "Estimated  pw: $pw" );
        my $cipher = Crypt::CBC->new( $Prefs{deskey}, "Crypt::DES" );
        my $tmpkey = $cipher->decrypt( decode_base64($dat) );
        syslog( 'debug', "Decrypted  pw: $tmpkey" );
        my $key = substr( $tmpkey, 0, $Prefs{pwlen} );
        my $fnc = substr( $tmpkey, $Prefs{pwlen} + 1, 2 );

        if ( $key eq $pw ) {
            syslog( 'alert', "ACCESS GRANTED - function $fnc" );
	    system $Prefs{$fnc};
            last;
        } else {
            syslog( 'alert', "ACCESS  DENIED -  wrong password: " . $dat );
        }
    }
}

#
#          ==-> M A I N <-==
#

getOptions();
readConfig();

# some hard coded preferences
my $snaplen = 256;    # read the first 256 byte per packet
my $promisc = 0;      # no promiscuous mode
my $to_ms   = -1;     # 0: capture packets until an error
                      #-1: capture packets indefinitely.
my $err;              # pcap error messages
my $filter = 'udp and dst port ' . $Prefs{watch_port};
my $filter_c;         # compiled paket filter

# Initialize syslog
openlog( 'bouncer daemon', 'cons,pid,ndelay', 'daemon' );
setlogsock('unix');

# Use network device passed in the config file or if no
# argument is passed, try to determine an appropriate
# interface by lookupdev()
my $dev = $Prefs{iface};
unless ( defined $dev ) {
    $dev = Net::Pcap::lookupdev( \$err );
    if ( defined $err ) {
        die 'Unable to determine network device for monitoring - ', $err;
    }
}

# Check on bogus network device arguments that may be
# passed to the program as an entry in the configuration file
my ( $address, $netmask );
if ( Net::Pcap::lookupnet( $dev, \$address, \$netmask, \$err ) ) {
    die 'Unable to look up device information for ', $dev, ' - ', $err;
}

# Create a packet capture object on the device
my $object = Net::Pcap::open_live( $dev, $snaplen, $promisc, $to_ms, \$err );

unless ( defined $object ) {
    die 'Unable to create packet capture object on device ', $dev, ' - ', $err;
}

# Compile and set packet filter for packet capture object.
Net::Pcap::compile( $object, \$filter_c, $filter, 0, $netmask )
  && die 'Unable to compile packet capture filter_c';

Net::Pcap::setfilter( $object, $filter_c )
  && die 'Unable to set packet capture filter_c';

#
#          ==-> D A E M O N I Z E <-==
#

&daemonize;

use POSIX;
sigaction SIGTERM, new POSIX::SigAction sub {
    Net::Pcap::close($object);
    syslog( 'alert', 'Bye, bouncer daemon is exiting' );
    closelog;
    exit(1);
};

sigaction SIGHUP, new POSIX::SigAction sub {
    syslog( 'alert', 'Reloading the config file' );
    readConfig;
};

Net::Pcap::loop( $object, -1, \&analyzePackets, '' )
  || die 'Unable to perform packet capture';


