#!/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 () { 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 : 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';