After reading the "Packet Logging Through Syslog" section of the pf FAQ 
I decided to try a different approach. Now that it's working (for my 
system and needs) I'm wondering 1) Is it (relatively) safe? 2) Is it 
useful to others? and 3) Did I re-invent something already available I 
missed?

Here's a quick description.

#!/usr/bin/perl -WT
use strict;
#-------------------------------------------------------------------------------
#
# pflogger logs to syslog(3), IN REAL TIME, packets logged by pf(4).
# This is done by by invoking tcpdump(8) on the pflog(4) interface
# and piping the output to logger(1).
#
# In effect pflogger is really just the shell command:
#
# (tcpdump -lent -i pflog0 2>&1 | \
#    logger -p user.info -t pflogger) >/dev/null 2>&1 &
#
# but with both tcpdump and logger running as daemon processes, logger 
running
# unprivileged, and no shell process providing glue.
# Once setup even the perl interpreter process running this script is gone.
#
#------------------------------------------------------------------------------

See attachment for full script.

Thanks,

-- 
      _               _                   _
   __| | __ _ _ __   | |__   __ _ ___ ___| | ___ _ __
  / _` |/ _` | '_ \  | '_ \ / _` / __/ __| |/ _ \ '__|
 | (_| | (_| | | | | | | | | (_| \__ \__ \ |  __/ |
  \__,_|\__,_|_| |_| |_| |_|\__,_|___/___/_|\___|_|

 [EMAIL PROTECTED]
#!/usr/bin/perl -WT
use strict;
#-------------------------------------------------------------------------------
#
# pflogger logs to syslog(3), IN REAL TIME, packets logged by pf(4).
# This is done by by invoking tcpdump(8) on the pflog(4) interface
# and piping the output to logger(1).
#
# In effect pflogger is really just the shell command:
#
# (tcpdump -lent -i pflog0 2>&1 | \
#       logger -p user.info -t pflogger) >/dev/null 2>&1 &
#
# but with both tcpdump and logger running as daemon processes, logger running
# unprivileged, and no shell process providing glue.
# Once setup even the perl interpreter process running this script is gone.
#
#-------------------------------------------------------------------------------
use constant LOGGRP => '_syslogd';                      # unprivileged group
use constant LOGUSR => '_syslogd';                      # unprivileged user
use constant TCPUSR => '_tcpdump';                      # tcpdump unprivileged
use constant PIDFILE => '/var/run/pflogger.pid';        # daemon's pidfile
use constant SETSID => 147;                             # from <sys/syscall.h>
use constant MAXFD => 1023;                             # open files hard limit
use constant CDDIR => '/var/empty';                     # daemon's directory
use constant DMASK => 0177;                             # daemon's umask

#use ABSOLUTELY_NOTHING_ELSE                            # a design goal
#require ABSOLUTELY_NOTHING                             # a design goal

$ENV{PATH} = '/usr/sbin:/usr/bin';                      # taint safe path

if (scalar @ARGV) { die "USE: pflogger\nNo arguments!\n"; }     # USE
if ( $< ) { die "pflogger: got root?\n"; }                      # need root!

my $lgid;
if ( ( $lgid = getgrnam(LOGGRP) ) < 0 ) { die "pflogger: getgrnam: $!\n"; }
my $luid;
if ( ( $luid = getpwnam(LOGUSR) ) < 0 ) { die "pflogger: getpwnam: $!\n"; }

if ( -e PIDFILE ) {

        &check_pidfile() or die "pflogger: check_pidfile error\n";
}

&daemonize() or die "pflogger: daemonize error\n";

my $child = open( STDOUT, "|-" );       # open pipe: parent|child

if ( $child > 0 ) {                     # parent: exec tcpdump

        open(STDERR, ">&STDOUT");       # STDERR to child - like shell's 2>&1

        # CAUTION: tcpdump needs to run as root to read pflog0.
        # Choose tcpdump(8) flags carefully please!
        # These flags are not ARGV or config file options by design.
        # 
        exec( 'tcpdump', '-lent', '-i', 'pflog0' ) or die "$!\n";

} elsif ( $child == 0 ) {               # child: exec logger (unprivileged)

        # drop privilege - uid last - no more changes after that
        # set then check - perl silently ignores set error.
        #
        $) = $lgid; if ( $) != $lgid ) { exit($!); }    # egid
        $( = $lgid; if ( $( != $lgid ) { exit($!); }    # gid
        $> = $luid; if ( $> != $luid ) { exit($!); }    # euid
        $< = $luid; if ( $< != $luid ) { exit($!); }    # uid

        exec( 'logger', '-p', 'user.info', '-t', 'pflogger' ) or exit($!);

} elsif  ( $child < 0 ) { die "open pipe failed: $!\n"; }

# /* NOTREACHED */

#-------------------------------------------------------------------------------
#                       SUBROUTINE DEFINITIONS BELOW
#-------------------------------------------------------------------------------
sub check_pidfile()                     # check for and kill existing process
{
        my $rv = 0;                             # 0 for error, 1 for success
        my $err = "";                           # diagnostic messages

        LCB: {
                if ( open(IN, '<', PIDFILE) == 0 ) {            # open

                        $err = "open PIDFILE";
                        last LCB;
                }

                my $opid;       
                if ( ! ($opid = <IN>) ) {                       # read

                        $err = "read PIDFILE";
                        last LCB;
                }

                if ( close(IN) == 0 ) {                         # close

                        $err = "close PIDFILE";
                        last LCB;
                }

                if ( $opid =~ /^(\d+)$/ ) { $opid = $1; }       # untaint
                else {
        
                        $err = "tainted PIDFILE: $opid";
                        last LCB;
                }

                my $tuid;
                if ( ( $tuid = getpwnam( TCPUSR ) ) < 0 ) {

                        $err = "getpwnam: $!\n";
                        last LCB;
                }

                $> = $tuid;                                     # euid _tcpdump
                if ( $> != $tuid ) {                            # verify

                        $err = "priv drop:$!\n";
                        last LCB;
                }

                if ( kill 0 => $opid ) {                        # alive?

                        warn "restart - terminating $opid\n";

                        if ( ! kill TERM => $opid ) {           # terminate

                                warn "TERM failed $!\n using KILL\n";

                                if ( ! kill KILL => $opid ) {   # forcefully

                                        $err =  "restart failed: $!";
                                }
                        }
                }

                $> = $<;                                        # euid restored
                if ( $> != $< ) { $err = "priv restore:$!\n" }  # verify
        }

        if ($err) { warn "check_pidfile(): $err\n"; }
        else {
                unlink( PIDFILE ) or warn "unlink PIDFILE failed\n";
                $rv = 1;
        }

        return($rv);
}

#-------------------------------------------------------------------------------
sub daemonize()                         # caller becomes a daemon process
{
        my $rv = 0;                             # 0 for failure, 1 for success
        my $err;                                # diagnostic messages

        LCB: {
                local $SIG{HUP} = 'IGNORE';     # local = only within LCB

                my $pid = fork;
                if ( $pid > 0 ) { exit(0); }
                elsif ( $pid < 0 ) {

                        $err = "fork1: $!";
                        last LCB;
                }

                if ( syscall( SETSID ) == 0 ) {

                        $err = "syscall SETSID: $!";
                        last LCB;
                }

                if ( ($pid = fork) > 0 ) { exit(0); }
                elsif ( $pid < 0 ) {
        
                        $err = "fork2: $!";
                        last LCB;
                }

                if ( chdir( CDDIR ) == 0 ) {
        
                        $err = "chdir: $!";
                        last LCB;
                }

                umask DMASK;

                #
                # call pidfile() here before STDERR becomes /dev/null
                # want to see diagnostic messages if anything goes wrong.
                #
                if ( ! &pidfile() ) {

                        $err = "pidfile failed.";
                        last LCB;
                }

                #
                # NOTE: re-opening causes implicit close
                # should be fd's 0,1, and 2 unless changed before here.
                # STDERR last - then diagnostics from warn/die dissappear.
                #
                if ( open( STDIN,  "</dev/null" ) == 0 ) {

                        $err="STDIN: $!";
                        last LCB;
                }
                if ( open( STDOUT, ">/dev/null" ) == 0 ) {
        
                        $err="STDOUT: $!";
                        last LCB;
                }
                if ( open( STDERR, ">/dev/null" ) == 0 ) {
        
                        $err="STDERR: $!";
                        last LCB;
                }

                for (MAXFD..3) { close($_); }   # ok to ignore errors here
        }

        if ($err) { warn "daemonize(): $err\n"; }
        else { $rv = 1; }

        return($rv);
}

#-------------------------------------------------------------------------------
sub pidfile()                                   # create PIDFILE
{
        my $rv = 0;                             # 0 for failure, 1 for success
        my $err = "";                           # diagnostic messages

        my $saved = umask 0133;                 # want -rw-r--r--

        if ( open(OUT, '>', PIDFILE ) == 0 ) { $err = "open: $!"; }
        elsif ( (print OUT "$$\n") == 0 ) { $err = "print $$: $!"; }
        elsif ( close(OUT) == 0 ) { $err = "close: $!"; }
        else { $rv = 1; }

        umask $saved;                           # restore original mask

        if ($err) {

                warn "pidfile(): $err\n";
                unlink( PIDFILE );
        }

        return($rv);
}

#-------------------------------------------------------------------------------
__END__

Reply via email to