package NetStumbler::Stumbler; use strict; use warnings; use Carp qw(cluck carp croak); require NetStumbler::Wap; require Exporter; our @ISA = qw(Exporter); # # We do not Export anything # our $VERSION = '0.07'; our $wapinfo = NetStumbler::Wap->new(); =head1 Object Methods =head2 new() Returns a new Stumbler object. =cut sub new { my $proto = shift; my $class = ref($proto) || $proto; my $self = {}; bless ($self, $class); $wapinfo->initialize(); return $self; } =head2 parseNSSummaryLine($line) Params: -string A line from a summary file Returns: an array of seperated values corresponding to output of a NetStumbler summary export **NOTE**
  • Conversion of the verbose GPS data to doubles in standard GPS format
  • Blank SSID will be set to "Hidden"
  • The time data will have GMT stripped off
  • If the line is not correctly formed return an empty list
  • Example: my @line = $obj->parseNSSummaryLine($line); print "Line [@line]\n"; =cut sub parseNSSummaryLine { my $self = shift; my $line = shift; my($d1,$lat,$d2,$lon,$ssid,$type,$mac,$time,$snr,$sig,$noise,$name,$flags,$chanbits,$bcninterval,$datarate,$lastchannel); my ($nlat,$nlon); if($line =~ /^#/) { return []; } if($line =~ /(\w).(\d+\.\d+)\t(\w).(\d+\.\d+)\t\(.(.*).\)\t(\S+)\t\(.(\w+\:\w+\:\w+\:\w+\:\w+\:\w+).\)\t(.*)\[.(\w+).(\w+).(\w+).\]\t\#.\((.*)\)\t(\d+)\t(\d+)\t(\d+)\t(\d+)\t(\d+)/) { $d1 = $1; $lat = $2; $d2 = $3; $lon = $4; $ssid = $5; $type = $6; $mac = $7; $time = $8; if($time) { $time =~ s/\(GMT\)//; } chomp($time); if($mac) { $mac =~ s/\://g; } $snr = $9; $sig = $10; $noise = $11; $name = $12; unless($name) { $name = "";}; $flags = hex($13); $chanbits = hex($14); $bcninterval = $15; unless($16) { $datarate = $16/10; }; $lastchannel = $17; if(!$ssid){ $ssid = "Hidden";} if(!$lat){ $lat = 0.00; } if(!$lon){ $lon = 0.00; } if(!$d1){ $d1 = "N";} if(!$d2){ $d2 = "W";} if($d1 =~ /[sS]/){$lat = "-$lat";} if($d2 =~ /[wW]/){$lon = "-$lon";} return ($lat,$lon,$ssid,$type,$mac,$time,$snr,$sig,$noise,$flags,$chanbits,$datarate,$lastchannel); } else { return []; } } =head2 isSummary($file) Params: -string fully qualified filename Returns: true if the file is in NetStumbler Summary format Example: if($obj->isSummary($file)) { # do something here } =cut sub isSummary { my $self = shift; my $file = shift; open(FD,$file) or cluck "Failed to open input file $!\n"; my $found = 0; while() { if(/^#/) { if(/wi-scan summary with extensions/) { $found = 1; } } if($found) { last; } } close(FD); return $found; } =head2 isNS1($file) Params: -string fully qualified filename Returns: true if the file is in NetStumbler NS1 file Example: if($obj->isNS1($file)) { # do something here } =cut sub isNS1 { my $self = shift; my $file = shift; open(FD,$file) or cluck "Failed to open input file $!\n"; my $magic; binmode(FD); read(FD,$magic,4); if($magic eq 'NetS') { return 1; } else { return 0; } } =head2 isKismetCSV($file) Params: -string fully qualified filename Returns: true if the file is in Kismet CSV file Example: if($obj->isKismetCSV($file)) { # do something here } =cut sub isKismetCSV { my $self = shift; my $file = shift; open(FD,$file) or cluck "Failed to open input file $!\n"; my $magic; binmode(FD); read(FD,$magic,7); if($magic eq 'Network') { return 1; } else { return 0; } } =head2 parseKismetCSV($file) Params: -string fully qualified filename Returns: list of lists each item in the sublist corresponds to a list from kismet summary file Example: $ref = $obj->parseKismetCSV($file); # The list is as follows 0 Network 1 NetType 2 ESSID 3 BSSID 4 Info 5 Channel 6 Cloaked 7 WEP 8 Decrypted 9 MaxRate 10 MaxSeenRate 11 Beacon 12 LLC 13 Data 14 Crypt 15 Weak 16 Total 17 Carrier 18 Encoding 19 FirstTime 20 LastTime 21 BestQuality 22 BestSignal 23 BestNoise 24 GPSMinLat 25 GPSMinLon 26 GPSMinAlt 27 GPSMinSpd 28 GPSMaxLat 29 GPSMaxLon 30 GPSMaxAlt 31 GPSMaxSpd 32 GPSBestLat 33 GPSBestLon 34 GPSBestAlt 35 DataSize 36 IPType 37 IP # =cut sub parseKismetCSV { my $self = shift; my $file = shift; my $fh; open(FH,$file); $fh = \*FH; my $line; <$fh>; $line = <$fh>; my @list; while(<$fh>) { $line = $_; push(@list,[split(/;/,$line)]); } return @list; } =head2 parseNS1($file) Params: -string fully qualified filename Returns: list of lists each item in the sublist corresponds to a list from parseNSSummary Example: $ref = $obj->parseNS1($file); =cut sub parseNS1 { my $self = shift; my $file = shift; my $fh; open(FH,$file); $fh = \*FH; my ($sig,$ver,$apCount); my $line; read($fh,$line,12); ($sig,$ver,$apCount) = unpack("A4LL",$line); unless($sig =~ /NetS/) { return []; } unless($ver > 6) { carp "Version $ver not supported!\n"; return []; } my @list; for(my $i=0;$i<$apCount;$i++) { push(@list, [ readAPInfo($fh,$ver) ]); } return @list; } =head1 Private Methods =head2 readAPInfo($fileHandle,$fileVersion) Params: reference - Filehandle reference number - NS1 Version Returns: list - smae format as parseNSSummary =cut sub readAPInfo { my @apData; my $fh = shift; my $ver = shift; my $sl = readUint8($fh); my $sid = readChars($fh,$sl); my $mac; my @ml; for(my $ms=0;$ms<6;$ms++) { push(@ml,readUint8($fh)); } $mac = sprintf("%02x:%02x:%02x:%02x:%02x:%02x",@ml); my($mSig,$mNoi,$mSnr,$flags,$beacon,$fs,$ls,$blat,$blon,$dCount); $mSig = readint32($fh); $mNoi = readint32($fh); $mSnr = readint32($fh); $flags = readUint32($fh); $beacon = readUint32($fh); $fs = readint64($fh); $ls = readint64($fh); $blat = readDouble($fh); $blon = readDouble($fh); $dCount = readUint32($fh); push(@apData,$blat); push(@apData,$blon); push(@apData,$sid); if($wapinfo->isInfrastructure($flags)) { push(@apData,"BSS"); } else { push(@apData,"Ad-Hoc"); } push(@apData,$mac); push(@apData,$fs); push(@apData,$mSnr); push(@apData,$mSig); push(@apData,$mNoi); for(my $xl=0;$xl<$dCount;$xl++) { my $rc = readAPData($fh,$ver); } my $nl = readUint8($fh); my $name = readChars($fh,$nl); push(@apData,$flags); push(@apData,$name); if($ver > 6) { my ($channels,$lchan,$ip,$min,$maxNoise,$dr,$ipsub,$ipmask,$pflags,$ieLength); $channels = readint64($fh); $lchan = readUint32($fh); $ip = readUint8($fh); $ip .= "." . readUint8($fh); $ip .= "." . readUint8($fh); $ip .= "." . readUint8($fh); $min = readint32($fh); $maxNoise = readint32($fh); $dr = readUint32($fh); $ipsub = readUint8($fh); $ipsub .= "." . readUint8($fh); $ipsub .= "." . readUint8($fh); $ipsub .= "." . readUint8($fh); push(@apData,$channels); push(@apData,$beacon); push(@apData,$dr); push(@apData,$lchan); if($ver > 8) { $ipmask = readUint8($fh); $ipmask .= "." . readUint8($fh); $ipmask .= "." . readUint8($fh); $ipmask .= "." . readUint8($fh); } if($ver > 11) { $pflags = readUint32($fh); $ieLength = readUint32($fh); if($ieLength > 0) { for(my $iel=0;$iel < $ieLength;$iel++) { readUint8($fh); } } } } return @apData; } =head2 readAPData($fileHandle,$fileVersion) Params: reference - Filehandle reference number - NS1 Version Returns: nothing TODO: Add a return value to this method to build graphs =cut sub readAPData { my $fh = shift; my $ver = shift; my ($time,$sig,$noise,$loc); $time = readint64($fh); $sig = readint32($fh); $noise = readint32($fh); $loc = readint32($fh); if($loc > 0) { readGPSData($fh); } } =head2 readGPSData($fileHandle) Params: reference - Filehandle reference Returns: nothing TODO: Add a return value to this method to build graphs =cut sub readGPSData { my $fh = shift; my ($lat,$lon,$alt,$numSat,$speed,$track,$magVar,$hdop); $lat = readDouble($fh); $lon = readDouble($fh); $alt = readDouble($fh); $numSat = readUint32($fh); $speed = readDouble($fh); $track = readDouble($fh); $magVar = readDouble($fh); $hdop = readDouble($fh); } =head2 readint64($fileHandle) Params: reference - Filehandle reference Returns: a 64bit number =cut sub readint64 { my $fh = shift; #my $l; #my ($p,$t); #$p = tell($fh); #my $r = read($fh,$l,8); #$t = tell($fh); #ensurePos($fh,$p,$t,8); #if($r != 8){die "Failed to read int64 $r $!\n";} #return unpack("L2",$l); return (readint32($fh) << 32) + readint32($fh); } =head2 readDouble($fileHandle) Params: reference - Filehandle reference Returns: a double =cut sub readDouble { my $fh = shift; my $l; my ($p,$t); $p = tell($fh); my $r = read($fh,$l,8); $t = tell($fh); ensurePos($fh,$p,$t,8); if($r != 8){die "Failed to read double $r $!\n";} return unpack("d",$l); } =head2 readint32($fileHandle) Params: reference - Filehandle reference Returns: a 32bit number =cut sub readint32 { my $fh = shift; my $l; my ($p,$t); $p = tell($fh); my $r = read($fh,$l,4); $t = tell($fh); ensurePos($fh,$p,$t,4); if($r != 4){die "Failed to read int32 $r $!\n";} return unpack("l",$l); } =head2 readUint32($fileHandle) Params: reference - Filehandle reference Returns: an unsigned 32bit number =cut sub readUint32 { my $fh = shift; my $l; my ($p,$t); $p = tell($fh); my $r = read($fh,$l,4); $t = tell($fh); ensurePos($fh,$p,$t,4); if($r != 4){die "Failed to read Uint32 $r $!\n";} return unpack("L",$l); } =head2 readUint8($fileHandle) Params: reference - Filehandle reference Returns: an unsigned 8bit number =cut sub readUint8 { my $fh = shift; my $l; my ($p,$t); $p = tell($fh); my $r = read($fh,$l,1); $t = tell($fh); ensurePos($fh,$p,$t,1); if($r != 1){die "Failed to read Uint8 $r $!\n";} return unpack("C",$l); } =head2 readChars($fileHandle,$length) Params: reference - Filehandle reference length - number of bytes to read Returns: a string =cut sub readChars { my $fh = shift; my $length = shift; my $l; my ($p,$t); $p = tell($fh); my $r = read($fh,$l,$length); $t = tell($fh); ensurePos($fh,$p,$t,$length); if($r != $length) { die "Failed to read $length ($r) $!\n";} return unpack("A*",$l); } =head2 ensurePos($fileHandle,$prePosition,$postPosition,$amountNeeded) This method was aadded due to an odd behavior with Perl5.8 read would sometimes put the file pointer 1 byte beyond where it was supposed to be. This method fixes that issue Params: reference - Filehandle reference number - Pre read position of the file number - Post position of the file number - Correct amount to data that was supposed to be read =cut sub ensurePos { my ($fh,$prePos,$postPos,$amt) = @_; my $diff = ($postPos-$prePos); if($diff != $amt) { $diff -= $amt; $postPos -= $diff; seek($fh,$postPos,0); } } 1; __END__ # Below is stub documentation for your module. You'd better edit it! =head1 NAME NetStumbler::Stumbler - Module to parse netstumbler data =head1 SYNOPSIS use NetStumbler::Stumbler; my $lin = NetStumbler::Stumbler->new(); $lin->isSummary($file); $lin->isNS1($file); $lin->parseNS1($file); =head1 DESCRIPTION This class has several methods to parse NetStumbler data file TODO: add Kismet and iStumbler support =head2 EXPORT None by default. =head1 SEE ALSO http://www.netstumbler.org Net Stumbler http://stumbler.net/ns1files.html NS1 Information =head1 AUTHOR Salvatore E. ScottoDiLuziowashu@olypmus.net =head1 COPYRIGHT AND LICENSE Copyright (C) 2004 by Salvatore ScottoDiLuzio This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.3 or, at your option, any later version of Perl 5 you may have available. =cut