1package App::Netdisco::Web::Plugin::Search::Node; 2 3use Dancer ':syntax'; 4use Dancer::Plugin::DBIC; 5use Dancer::Plugin::Auth::Extensible; 6 7use NetAddr::IP::Lite ':lower'; 8use Regexp::Common 'net'; 9use NetAddr::MAC (); 10use POSIX qw/strftime/; 11 12use App::Netdisco::Web::Plugin; 13use App::Netdisco::Util::Web 'sql_match'; 14 15register_search_tab({ 16 tag => 'node', 17 label => 'Node', 18 api_endpoint => 1, 19 api_parameters => [ 20 q => { 21 description => 'MAC Address or IP Address or Hostname (without Domain Suffix) of a Node (supports SQL or "*" wildcards)', 22 required => 1, 23 }, 24 partial => { 25 description => 'Partially match the "q" parameter (wildcard characters not required)', 26 type => 'boolean', 27 default => 'false', 28 }, 29 deviceports => { 30 description => 'MAC Address search will include Device Port MACs', 31 type => 'boolean', 32 default => 'true', 33 }, 34 show_vendor => { 35 description => 'Include interface Vendor in results', 36 type => 'boolean', 37 default => 'false', 38 }, 39 archived => { 40 description => 'Include archived records in results', 41 type => 'boolean', 42 default => 'false', 43 }, 44 daterange => { 45 description => 'Date Range in format "YYYY-MM-DD to YYYY-MM-DD"', 46 default => ('1970-01-01 to '. strftime('%Y-%m-%d', gmtime)), 47 }, 48 age_invert => { 49 description => 'Results should NOT be within daterange', 50 type => 'boolean', 51 default => 'false', 52 }, 53 # mac_format is used only in the template (will be IEEE) in results 54 #mac_format => { 55 #}, 56 # stamps param is used only in the template (they will be included) 57 #stamps => { 58 #}, 59 ], 60}); 61 62# nodes matching the param as an IP or DNS hostname or MAC 63get '/ajax/content/search/node' => require_login sub { 64 my $node = param('q'); 65 send_error('Missing node', 400) unless $node; 66 return unless ($node =~ m/\w/); # need some alphanum at least 67 content_type('text/html'); 68 69 my $agenot = param('age_invert') || '0'; 70 my ( $start, $end ) = param('daterange') =~ m/(\d+-\d+-\d+)/gmx; 71 72 my $mac = NetAddr::MAC->new(mac => ($node || '')); 73 undef $mac if 74 ($mac and $mac->as_ieee 75 and (($mac->as_ieee eq '00:00:00:00:00:00') 76 or ($mac->as_ieee !~ m/$RE{net}{MAC}/))); 77 78 my @active = (param('archived') ? () : (-bool => 'active')); 79 my (@times, @wifitimes, @porttimes); 80 81 if ( $start and $end ) { 82 $start = $start . ' 00:00:00'; 83 $end = $end . ' 23:59:59'; 84 85 if ($agenot) { 86 @times = (-or => [ 87 time_first => [ undef ], 88 time_last => [ { '<', $start }, { '>', $end } ] 89 ]); 90 @wifitimes = (-or => [ 91 time_last => [ undef ], 92 time_last => [ { '<', $start }, { '>', $end } ], 93 ]); 94 @porttimes = (-or => [ 95 creation => [ undef ], 96 creation => [ { '<', $start }, { '>', $end } ] 97 ]); 98 } 99 else { 100 @times = (-or => [ 101 -and => [ 102 time_first => undef, 103 time_last => undef, 104 ], 105 -and => [ 106 time_last => { '>=', $start }, 107 time_last => { '<=', $end }, 108 ], 109 ]); 110 @wifitimes = (-or => [ 111 time_last => undef, 112 -and => [ 113 time_last => { '>=', $start }, 114 time_last => { '<=', $end }, 115 ], 116 ]); 117 @porttimes = (-or => [ 118 creation => undef, 119 -and => [ 120 creation => { '>=', $start }, 121 creation => { '<=', $end }, 122 ], 123 ]); 124 } 125 } 126 127 my ($likeval, $likeclause) = sql_match($node, not param('partial')); 128 my $using_wildcards = (($likeval ne $node) ? 1 : 0); 129 130 my @where_mac = 131 ($using_wildcards ? \['me.mac::text ILIKE ?', $likeval] 132 : ((!defined $mac or $mac->errstr) ? \'0=1' : ('me.mac' => $mac->as_ieee)) ); 133 134 my $sightings = schema('netdisco')->resultset('Node') 135 ->search({-and => [@where_mac, @active, @times]}, { 136 order_by => {'-desc' => 'time_last'}, 137 '+columns' => [ 138 'device.dns', 139 { time_first_stamp => \"to_char(time_first, 'YYYY-MM-DD HH24:MI')" }, 140 { time_last_stamp => \"to_char(time_last, 'YYYY-MM-DD HH24:MI')" }, 141 ], 142 join => 'device', 143 }); 144 145 my $ips = schema('netdisco')->resultset('NodeIp') 146 ->search({-and => [@where_mac, @active, @times]}, { 147 order_by => {'-desc' => 'time_last'}, 148 '+columns' => [ 149 'oui.company', 150 'oui.abbrev', 151 { time_first_stamp => \"to_char(time_first, 'YYYY-MM-DD HH24:MI')" }, 152 { time_last_stamp => \"to_char(time_last, 'YYYY-MM-DD HH24:MI')" }, 153 ], 154 join => 'oui' 155 }); 156 157 my $netbios = schema('netdisco')->resultset('NodeNbt') 158 ->search({-and => [@where_mac, @active, @times]}, { 159 order_by => {'-desc' => 'time_last'}, 160 '+columns' => [ 161 'oui.company', 162 'oui.abbrev', 163 { time_first_stamp => \"to_char(time_first, 'YYYY-MM-DD HH24:MI')" }, 164 { time_last_stamp => \"to_char(time_last, 'YYYY-MM-DD HH24:MI')" }, 165 ], 166 join => 'oui' 167 }); 168 169 my $wireless = schema('netdisco')->resultset('NodeWireless')->search( 170 { -and => [@where_mac, @wifitimes] }, 171 { order_by => { '-desc' => 'time_last' }, 172 '+columns' => [ 173 'oui.company', 174 'oui.abbrev', 175 { 176 time_last_stamp => \"to_char(time_last, 'YYYY-MM-DD HH24:MI')" 177 }], 178 join => 'oui' 179 } 180 ); 181 182 my $rs_dp = schema('netdisco')->resultset('DevicePort'); 183 if ($sightings->has_rows or $ips->has_rows or $netbios->has_rows) { 184 my $ports = param('deviceports') 185 ? $rs_dp->search({ -and => [@where_mac] }) : undef; 186 187 return template 'ajax/search/node_by_mac.tt', { 188 ips => $ips, 189 sightings => $sightings, 190 ports => $ports, 191 wireless => $wireless, 192 netbios => $netbios, 193 }; 194 } 195 else { 196 my $ports = param('deviceports') 197 ? $rs_dp->search({ -and => [@where_mac, @porttimes] }) : undef; 198 199 if (defined $ports and $ports->has_rows) { 200 return template 'ajax/search/node_by_mac.tt', { 201 ips => $ips, 202 sightings => $sightings, 203 ports => $ports, 204 wireless => $wireless, 205 netbios => $netbios, 206 }; 207 } 208 } 209 210 my $set = schema('netdisco')->resultset('NodeNbt') 211 ->search_by_name({nbname => $likeval, @active, @times}); 212 213 unless ( $set->has_rows ) { 214 if (my $ip = NetAddr::IP::Lite->new($node)) { 215 # search_by_ip() will extract cidr notation if necessary 216 $set = schema('netdisco')->resultset('NodeIp') 217 ->search_by_ip({ip => $ip, @active, @times}); 218 } 219 else { 220 $set = schema('netdisco')->resultset('NodeIp') 221 ->search_by_dns({ 222 ($using_wildcards ? (dns => $likeval) : 223 (dns => "${likeval}.\%", 224 suffix => setting('domain_suffix'))), 225 @active, 226 @times, 227 }); 228 229 # if the user selects Vendor search opt, then 230 # we'll try the OUI company name as a fallback 231 232 if (param('show_vendor') and not $set->has_rows) { 233 $set = schema('netdisco')->resultset('NodeIp') 234 ->with_times 235 ->search( 236 {'oui.company' => { -ilike => ''.sql_match($node)}, @times}, 237 {'prefetch' => 'oui'}, 238 ); 239 } 240 } 241 } 242 243 return unless $set and $set->has_rows; 244 $set = $set->search_rs({}, { order_by => 'me.mac' }); 245 246 return template 'ajax/search/node_by_ip.tt', { 247 macs => $set, 248 archive_filter => {@active}, 249 }; 250}; 251 252true; 253