#!/usr/bin/perl use strict; use warnings; #use Data::Dumper(); use Perl::Tidy; #sub Dumper {perltidy source=>\(Data::Dumper::Dumper@_),destination=>\(my$t);$t} use Data::Dumper; $Data::Dumper::Quotekeys = 0; $Data::Dumper::Sortkeys = 1; print Dumper [ parse_wtmp() ]; sub parse_wtmp { # 'c2ph' was used to generate this, # with help from dump_size_offset() to confirm/fix alignment: # http://paperlined.org/dev/perl/modules/related_modules/more_complex_than_pack.html my ($fnames, $ftypes) = unzip(qw_comments(<<'EOF')); type l # short int type of login (0 through 9); see wtmp(5) pid i # pid_t PID of login process line Z32 # char[UT_LINESIZE] device name of TTY id Z4 # char[4] terminal name suffix, or inittab ID user Z32 # char[UT_NAMESIZE] username host A256 # char[UT_HOSTSIZE] hostname (if remote login) exit l # struct exit_status exit status of a process when ut_type==DEAD_PROCESS session l # long int session ID, used for windowing tv_sec i # struct timeval.tv_sec time this entry was made tv_usec i # struct timeval.tv_usec addr_v6 a16 # int32_t[4] internet address of remote host; IPv4 address uses just ut_addr_v6[0] unused Z20 # char[20] reserved for future use EOF $ftypes = join(" ", @$ftypes); open my $fin, '<', '/var/log/wtmp' or die $!; local $/ = \(length pack $ftypes); # read fixed-length records my @entries; while (<$fin>) { my %ent; @ent{@$fnames} = unpack $ftypes, $_; $ent{tv_human} = ~~localtime $ent{tv_sec}; if (!grep {$_} unpack 'x4L3', $ent{addr_v6}) { # if the last 12 bytes are all-zero, then it's an IPv4 address $ent{addr_human} = join '.', unpack 'C4', $ent{addr_v6}; } else { $ent{addr_human} = join ':', unpack 'H4H4H4H4H4H4H4H4', $ent{addr_v6}; } push @entries, \%ent; } return @entries; } # split [1,'a',2,'b',3,'c'] into [[1,2,3],['a','b','c']] sub unzip {my($i,$j)=(0,0); [grep{++$i%2}@_],[grep{$j++%2}@_]} # Like qw[...], but it allows use of comments (hash symbol). sub qw_comments {local$_=shift;s/\s+#.*//gm;split}