1package App::Netdisco::Util::Nbtstat; 2 3use Dancer qw/:syntax :script/; 4use Dancer::Plugin::DBIC 'schema'; 5 6use App::Netdisco::Util::Node 'check_mac'; 7use App::Netdisco::AnyEvent::Nbtstat; 8use Encode; 9 10use base 'Exporter'; 11our @EXPORT = (); 12our @EXPORT_OK = qw/ nbtstat_resolve_async store_nbt /; 13our %EXPORT_TAGS = (all => \@EXPORT_OK); 14 15=head1 NAME 16 17App::Netdisco::Util::Nbtstat 18 19=head1 DESCRIPTION 20 21Helper subroutines to support parts of the Netdisco application. 22 23There are no default exports, however the C<:all> tag will export all 24subroutines. 25 26=head1 EXPORT_OK 27 28=head2 nbtstat_resolve_async( $ips ) 29 30This method uses an asynchronous AnyEvent NetBIOS node status requester 31C<App::Netdisco::AnyEvent::Nbtstat>. 32 33Given a reference to an array of hashes will connects to the C<IPv4> of a 34node and gets NetBIOS node status information. 35 36Returns the supplied reference to an array of hashes with MAC address, 37NetBIOS name, NetBIOS domain/workgroup, NetBIOS user, and NetBIOS server 38service status for addresses which responded. 39 40=cut 41 42sub nbtstat_resolve_async { 43 my $ips = shift; 44 45 my $timeout = (setting('nbtstat_response_timeout') 46 || setting('nbtstat_timeout') || 1); 47 my $interval = setting('nbtstat_interval') || 0.02; 48 49 my $stater = App::Netdisco::AnyEvent::Nbtstat->new( 50 timeout => $timeout, 51 interval => $interval 52 ); 53 54 # Set up the condvar 55 my $cv = AE::cv; 56 $cv->begin( sub { shift->send } ); 57 58 foreach my $hash_ref (@$ips) { 59 my $ip = $hash_ref->{'ip'}; 60 $cv->begin; 61 $stater->nbtstat( 62 $ip, 63 sub { 64 my $res = shift; 65 _filter_nbname( $ip, $hash_ref, $res ); 66 $cv->end; 67 } 68 ); 69 } 70 71 # Decrement the cv counter to cancel out the send declaration 72 $cv->end; 73 74 # Wait for the resolver to perform all resolutions 75 $cv->recv; 76 77 # Close sockets 78 undef $stater; 79 80 return $ips; 81} 82 83# filter nbt names / information 84sub _filter_nbname { 85 my $ip = shift; 86 my $hash_ref = shift; 87 my $node_status = shift; 88 89 my $server = 0; 90 my $nbname = ''; 91 my $domain = ''; 92 my $nbuser = ''; 93 94 for my $rr ( @{$node_status->{'names'}} ) { 95 my $suffix = defined $rr->{'suffix'} ? $rr->{'suffix'} : -1; 96 my $G = defined $rr->{'G'} ? $rr->{'G'} : ''; 97 my $name = defined $rr->{'name'} ? $rr->{'name'} : ''; 98 99 if ( $suffix == 0 and $G eq "GROUP" ) { 100 $domain = $name; 101 } 102 if ( $suffix == 3 and $G eq "UNIQUE" ) { 103 $nbuser = $name; 104 } 105 if ( $suffix == 0 and $G eq "UNIQUE" ) { 106 $nbname = $name unless $name =~ /^IS~/; 107 } 108 if ( $suffix == 32 and $G eq "UNIQUE" ) { 109 $server = 1; 110 } 111 } 112 113 unless ($nbname) { 114 debug sprintf ' nbtstat no computer name found for %s', $ip; 115 return; 116 } 117 118 my $mac = $node_status->{'mac_address'} || ''; 119 120 unless ( check_mac( $mac, $ip ) ) { 121 122 # Just assume it's the last MAC we saw this IP at. 123 my $node_ip = schema('netdisco')->resultset('NodeIp') 124 ->single( { ip => $ip, -bool => 'active' } ); 125 126 if ( !defined $node_ip ) { 127 debug sprintf ' no MAC for %s returned by nbtstat or in DB', $ip; 128 return; 129 } 130 $mac = $node_ip->mac; 131 } 132 133 $hash_ref->{'ip'} = $ip; 134 $hash_ref->{'mac'} = $mac; 135 $hash_ref->{'nbname'} = Encode::decode('UTF-8', $nbname); 136 $hash_ref->{'domain'} = Encode::decode('UTF-8', $domain); 137 $hash_ref->{'server'} = $server; 138 $hash_ref->{'nbuser'} = Encode::decode('UTF-8', $nbuser); 139 140 return; 141} 142 143=head2 store_nbt($nb_hash_ref, $now?) 144 145Stores entries in C<node_nbt> table from the provided hash reference; MAC 146C<mac>, IP C<ip>, Unique NetBIOS Node Name C<nbname>, NetBIOS Domain or 147Workgroup C<domain>, whether the Server Service is running C<server>, 148and the current NetBIOS user C<nbuser>. 149 150Adds new entry or time stamps matching one. 151 152Optionally a literal string can be passed in the second argument for the 153C<time_last> timestamp, otherwise the current timestamp (C<now()>) is used. 154 155=cut 156 157sub store_nbt { 158 my ( $hash_ref, $now ) = @_; 159 $now ||= 'now()'; 160 161 schema('netdisco')->resultset('NodeNbt')->update_or_create( 162 { mac => $hash_ref->{'mac'}, 163 ip => $hash_ref->{'ip'}, 164 nbname => $hash_ref->{'nbname'}, 165 domain => $hash_ref->{'domain'}, 166 server => $hash_ref->{'server'}, 167 nbuser => $hash_ref->{'nbuser'}, 168 active => \'true', 169 time_last => \$now, 170 }, 171 { key => 'primary', 172 for => 'update', 173 } 174 ); 175 176 return; 177} 178 1791; 180