No.2 | | 6603 bytes |
| 
Jason Balicki wrote:
Hello,
Hello,
I have several locations with a hardware VPN device and
I've set them up to log to a central logging server.
Some of the managers of these facilities would like to
have a report showing who's using the VPN and when.
I've written a script that does that. It works, but as
I'm pretty new at Perl (this is only my second real
life application) I'm sure it could be improved
(significantly).
Would some kind soul like to take a look at this
script, make suggestions or otherwise completely
destroy my ego? :)
, you asked for it.
Thanks in advance to anyone who takes this up
The script can be found at:
And some fake (sanitized) data to run it against (you'll
have to make changes to the script accordingly):
(60k text file, some
gunk has been pre-filtered)
Thank you very much for providing a working program and data to test it with.
#!/usr/bin/perl -w
#
# FIXME: change the tempfile to something sane (randomly generated)
# FIXME: delete the tempfile when we're done
If you really need a temp file you should use the File::Temp module.
perldoc File::Temp
#
use warnings;
You don't need to use both the '-w' switch and the 'warnings' pragma, remove
the '-w' switch.
use strict;
my $inputfile = '/var/log/messages';
my $tmp_file = '/tmp/vpntmp';
my $month = `date +%b`;
my $day = `date +%-d`;
#my $day =140;
my $year = `date +%Y`;
There is no need to call an external program to get the date as perl provides
functions for this:
my ( $month, $day, $year ) = localtime =~ /(\w{3})\s+(\d+)\s+\S+\s+(\d{4})/;
my @pppd_pids;
my $pppd_pid_index=0;
chop ($month, $day, $year);
You should use chomp() instead of chop() (which you rarely need) and you won't
need either if you use the code above.
open(FILE,$inputfile);
You should *ALWAYS* verify that the file opened correctly.
# generate temp file
open (TEMPFILE,">$tmp_file");
You don't really need a temp file as one days data should fit easily into
memory.
print "VPN usage report for $day $month $year\n\n";
# populate temp file and get pids
while (<FILE>) {
#because of the split()s I need to convert all
#multiple spaces to single spaces.
tr/ //s;
If you used split() correctly then you wouldn't need to do this.
# populate temp file, if it's a pppd line from the 550
if ((/$month $day/ && /$year/ && /SG550/ && /pppd/) || (/$month $day/ && /$year/ && /SG550/ && /pptpd/)) {
You can simplify that a bit:
if ( /$month $day/ && /$year/ && /SG550/ && /ppt?pd/ ) {
print TEMPFILE $_;
}
# if it's a connection note the pid for later processing
#if (/$month $day/ && /$year/ && /pppd/ && /peer authentication succeeded/){
if (/$month $day/ && /$year/ && /pppd/ && /2.3.8 started by/){
You are including the pppd version number in the regular expression but that
won't work if you upgrade pppd. Also, the period is special in regular
expressions, it matches any character.
#my $converted_string = $_;
#$converted_string =~ tr/ //s;
my @fields = split(/ /,$_);
You are using split() to split on a single space character. If you used the
default split with no arguments it will work better:
my @fields = split;
my $pppd_pid = get_pid($fields[5]);
$pppd_pids[$pppd_pid_index]=$pppd_pid;
$pppd_pid_index++;
Instead of keeping track of the array index just use push():
push @pppd_pids, $pppd_pid;
}
}
close ( TEMPFILE );
close ( FILE );
Instead of critiquing the rest of your code I'll show you how I would code it:
#!/usr/bin/perl
use warnings;
use strict;
my $inputfile = '/var/log/messages';
my ( $month, $day, $year ) = localtime =~ /(\w{3})\s+(\d+)\s+\S+\s+(\d+)/;
open FILE, '<', $inputfile or die "Cannot open $inputfile: $!";
print "VPN usage report for $day $month $year\n\n";
my ( $remote, %pids );
while ( <FILE) {
next unless /^$month +$day +([\d:]+).+?SG550 +ppt?pd\[(\d+)].+?\($year/o;
my ( $time, $pid ) = ( $1, $2 );
if ( /CTRL: Client ([\d.]+) control connection started/ ) {
$remote = $1;
}
elsif ( /pppd [\d.]+ started by/ ) {
# Instead of guessing the PID difference between pppd and pptpd
# this assumes that the pppd 'started by' line comes right after
# the pptpd 'connection started' line.
@{ $pids{ $pid } }{ qw/start remote/ } = ( $time, $remote );
}
elsif ( /peer authentication succeeded for (.+)/ ) {
$pids{ $pid }{ username } = $1;
}
elsif ( /remote IP address ([\d.]+)/ ) {
$pids{ $pid }{ assigned } = $1;
}
elsif ( /Sent (\d+) bytes, received (\d+) bytes/ ) {
@{ $pids{ $pid } }{ qw/sent rcvd/ } = ( $1, $2 );
}
elsif ( /Connection terminated/ ) {
$pids{ $pid }{ end } = $time;
}
elsif ( /Connect time (.+)/ ) {
$pids{ $pid }{ connect } = $1;
}
}
close FILE;
my $counter;
for my $pid ( sort { $a <=$b } keys %pids ) {
# set empty fields to the string 'n/a'
$_ ||= 'n/a' for @{ $pids{ $pid } }{ qw/ username remote assigned sent
rcvd start end connect / };
$counter++;
print <<STATUS;
Connection $counter
PID: $pid
Username: $pids{$pid}{username}
Remote IP Address: $pids{$pid}{remote}
Assigned IP Address: $pids{$pid}{assigned}
Bytes Sent: $pids{$pid}{sent}
Bytes Received: $pids{$pid}{rcvd}
Start Time: $pids{$pid}{start}
End Time: $pids{$pid}{end}
Duration: $pids{$pid}{connect}
STATUS
}
print <<'MESG';
Please note that any "n/a" entries in the bytes or time columns
may mean that the connection is still up, or that the connection dropped
with an error (such as if the vpn server is restarted.) If there is a "n/a"
in the assigned address field then the connection probably failed. If you can
ping the assigned address then the connection is still up.
Also note that an empty file means there have been no connections today,
you may wish to run this report by hand and specify another date, or you
may wish to view the logs directly.
MESG
__END__
John