<& SELF:matches &>

<h1 class="pagehead">Node Search</h1>
<FORM ACTION="<%$r->uri%>" METHOD="get">
<TABLE CLASS="node-search">
<TR>
    <TD>MAC, Hostname, IP, NetBIOS:</TD>
    <TD><INPUT TYPE="text" name="node" maxlength=255 size=30 VALUE="<%$node || $m->session->{_s_term}  |h%>">
        <BR><span class="smaller"><B>*</B> and <B>?</B> can be used as a wildcard for MAC addresses, IP addresses, and Netbios names
        <BR>Wildcards do not work on hostnames</span>
    </TD>
</TR>
<TR>
    <TD>Time Stamps: </TD>
    <TD> <INPUT TYPE="radio" NAME="dates" VALUE="1" <%$dates ? 'CHECKED' : '' %>>On
         <INPUT TYPE="radio" NAME="dates" VALUE="0" <%$dates ? '' : 'CHECKED' %>>Off
    </TD>
</TR>
<TR>
    <TD>Archived Data: </TD>
    <TD><INPUT TYPE="checkbox" NAME="archive" VALUE="1" <%$archive ? 'CHECKED' : '' %>></TD>
</TR>
<TR>
    <TD>Show Vendor : </TD>
    <TD> <INPUT TYPE="radio" NAME="showoui" VALUE="1" <%$showoui ? 'CHECKED' : ''%>>On
         <INPUT TYPE="radio" NAME="showoui" VALUE="0" <%$showoui ? '' : 'CHECKED' %>>Off
    </TD>
</TR>
<TR>
    <TD>&nbsp;</TD>
    <TD>
        <INPUT TYPE="submit" CLASS="navbutton" Value="Search" class="navbutton">
        <A HREF="<%$r->uri%>" class="navbutton">Clear</A>
    </TD>
</TR>
</TABLE>
</FORM>

<h1 class="pagehead">Vendor / Organizationally Unique Identifier (OUI) Search</h1>

% if ($expandoui){
<& SELF:ouiform &>
% } else {
<A class="navbutton" HREF="<%$r->uri%>?expandoui=1">Search on Vendor / OUI</a>
<BR><SPAN CLASS="small">* Can be slow to load</SPAN>
% }

<h1 class="pagehead"><A NAME="reports">Node Reports</A></h1>
<TABLE CLASS="node-search">
<TR>
    <TH><A HREF="<%$r->uri%>?specific=wireless" class="navbutton">Possible Wireless Access Points (WAP)</A></TH>
    <TD>These aren't guaranteed to be wireless access points, they just have MACs that fall into
            the right range.  Also remember people can hide them under fake MAC addresses as well.
    </TD>
</TR>
<TR>
    <TH><A HREF="<%$r->uri%>?specific=multips" class="navbutton">Nodes with multiple active IP addresses</A></TH>
    <TD>List all the nodes that are using more than one IP, sorted by worst offender. This report is good
        for finding nodes that are randomly changing IP addresses or abusing DHCP leases.
    </TD>
</TR>
</TABLE>
<%args>
$node      => ''
$dates     => 1
$archive   => 0
$showoui   => 1
$expandoui => 0
@vendors   => ()
@oui       => ()
$specific  => ''
$sort      => 'mac'
</%args>
<%shared>
my $matches = []; my $Node_IP_Seen = {};
my $domain = $netdisco::CONFIG{domain};
my ($arg_showdate, $arg_showoui, @arg_vendors, @arg_oui, $arg_archive, $arg_dates, $arg_node,$arg_sort);
my $odd=1; my $ran_query = 0;
my @search = (); my $ouis = undef; my @cols = ();
my $vendors = {
            'Cisco' => qr/cisco/i,
            '3Com'  => qr/3 ?com/i,
            'Compaq' => qr/compaq/i,
            'Intel'  => qr/intel/i,
            'Apple'  => qr/apple/i,
            'Sun'    => qr/sun\s+/i,
            'HP'     => qr/(hp|hewlett.*packard)/i,
            'Linksys' => qr/linksys/i,
            'SGI'   => qr/silicon.*graphics/i,
            'D-Link' => qr/d-link/i,
            'IBM'  => qr/ibm/i,
            'Dell'  => qr/dell/i,
            'Bay'   => qr/bay.*networks/i,
            'APC'   => qr/american.*power.*conversion/i,
            'Lexmark' => qr/lexmark/i,
            'Kingston' => qr/kingston/i,
            'GW2000' => qr/gateway.*2000/i,
            'VMWare' => qr/vmware/i,
            '[No OUI]' => qr/\[NA\]/,
            'Avaya'  => qr/avaya/i,
              };

my %sort_type = ('mac' => 'MAC Address',
                 'dev' => 'Device Name',
                 'oui' => 'Vendor',
                );

# Cheers to the makers of Kismet for their extensive AP list
# which is added to here.
my $wireless = {
                '00:00:94:CB' => 'Asante',
                '00:01:03:7C' => '3Com',
                '00:01:24:24' => 'SMC SMC7004AWBR',
                '00:01:24:F0' => 'Acer',
                '00:01:24:F1' => 'Acer',
                '00:01:f4' => 'Enterasys Networks',
                '00:02:2d' => 'Agere / Lucent Systems',
                '00:02:a5:6e:46' => 'Compaq Computer Corporation',
                '00:02:a5:6e:47' => 'Compaq Computer Corporation',
                '00:02:a5:6f' => 'Compaq Computer Corporation',
                '00:02:b3:65' => 'Intel Corporation',
                '00:02:b3:86' => 'Intel Corporation',
                '00:02:b3:92' => 'Intel Corporation',
                '00:02:b3:94' => 'Intel Corporation',
                '00:02:b3:B1' => 'Intel Corporation',
                '00:02:6F' => 'Senao',
                '00:03:2f' => 'Global Sun Technology, Inc.',
                '00:04:3A:3A' => 'Avaya ad-01444',
                '00:04:5a:0c' => 'The Linksys Group, Inc.',
                '00:04:5A:0E' => 'Linksys WAP11',
                '00:04:5a:0f' => 'The Linksys Group, Inc.',
                '00:04:5a:23' => 'The Linksys Group, Inc.',
                '00:04:5a:26' => 'The Linksys Group, Inc.',
                '00:04:5A:2E' => 'Linksys BEFW11S4',
                '00:04:5a:2f' => 'The Linksys Group, Inc.',
                '00:04:5A:5A' => 'Linksys BEFW11S4',
                '00:04:5a:cc' => 'The Linksys Group, Inc.',
                '00:04:5a:cd' => 'The Linksys Group, Inc.',
                '00:04:5a:ce' => 'The Linksys Group, Inc.',
                '00:04:5a:cf' => 'The Linksys Group, Inc.',
                '00:04:5a:d0' => 'The Linksys Group, Inc.',
                '00:04:5a:d1' => 'The Linksys Group, Inc.',
                '00:04:5a:d2' => 'The Linksys Group, Inc.',
                '00:04:5a:d8' => 'The Linksys Group, Inc.',
                '00:04:5a:da' => 'The Linksys Group, Inc.',
                '00:04:5a:db' => 'The Linksys Group, Inc.',
                '00:04:5a:dd' => 'The Linksys Group, Inc.',
                '00:04:5a:e4' => 'The Linksys Group, Inc.',
                '00:04:5a:e8' => 'The Linksys Group, Inc.',
                '00:04:5a:eb' => 'The Linksys Group, Inc.',
                '00:04:5a:ee' => 'The Linksys Group, Inc.',
                '00:04:5a:f6' => 'The Linksys Group, Inc.',
                '00:04:5a:f9' => 'The Linksys Group, Inc.',
                '00:04:5a:fa' => 'The Linksys Group, Inc.',
                '00:04:5a:fc' => 'The Linksys Group, Inc.',
                '00:04:5a:fd' => 'The Linksys Group, Inc.',
                '00:04:75:62' => '3 Com Corporation',
                '00:04:75:75' => '3Com 3CRWE20096A AP2000 Version 1',
                '00:04:76:a5' => '3 Com Corporation',
                '00:04:8a' => 'Temia Vertriebs GmbH',
                '00:04:db' => 'Tellus Group Corp.',
                '00:04:e2'    => 'SMC Networks, Inc.',
                '00:04:E2:E2' => 'SMC 7004AWBR',
                '00:05:5d:25' => 'D-Link Systems, Inc.',
                '00:05:5D:5D' => 'D-Link DWL-1000AP',
                '00:05:5d:ea' => 'D-Link Systems, Inc.',
                '00:05:5d:ec' => 'D-Link Systems, Inc.',
                '00:05:5d:ed' => 'D-Link Systems, Inc.',
                '00:05:5d:ee' => 'D-Link Systems, Inc.',
                '00:05:5d:f1' => 'D-Link Systems, Inc.',
                '00:05:5d:f2' => 'D-Link Systems, Inc.',
                '00:05:5f' => 'Cisco Systems, Inc.',
                '00:06:25'    => 'The Linksys Group, Inc.',
                '00:06:25:25' => 'Linksys BEFW11S4 v2',
                '00:06:25:50' => 'The Linksys Group, Inc.',
                '00:06:25:51' => 'The Linksys Group, Inc.',
                '00:06:25:53' => 'The Linksys Group, Inc.',
                '00:06:25:54' => 'The Linksys Group, Inc.',
                '00:06:25:59' => 'The Linksys Group, Inc.',
                '00:06:25:5d' => 'The Linksys Group, Inc.',
                '00:06:25:61' => 'The Linksys Group, Inc.',
                '00:06:25:64' => 'The Linksys Group, Inc.',
                '00:06:25:66' => 'The Linksys Group, Inc.',
                '00:06:25:6d' => 'The Linksys Group, Inc.',
                '00:06:25:75' => 'The Linksys Group, Inc.',
                '00:06:25:76' => 'The Linksys Group, Inc.',
                '00:07:0E' => 'Cisco',
                '00:07:50' => 'Cisco Systems, Inc.',
                '00:08:21' => 'Cisco',
                '00:09:43' => 'Cisco',
                '00:09:5b' => 'Netgear, Inc.',
                '00:09:7C' => 'Cisco',
                '00:09:92:92' => 'Sweex LC000010',
                '00:09:E8' => 'Cisco',
                '00:0A:41' => 'Cisco',
                '00:0A:8A:8A' => 'Cisco AIR-AP1200',
                '00:0C:41'    => 'Linksys',
                '00:0D:88'    => 'D-Link',
                '00:10:e7' => 'BreezeCom',
                '00:20:a6' => 'Proxim Wireless',
                '00:20:d8' => 'Nortel Networks',
                '00:30:65:03' => 'Apple',
                '00:30:65:04' => 'Apple',
                '00:30:65:05' => 'Apple',
                '00:30:65:13' => 'Apple',
                '00:30:65:15' => 'Apple',
                '00:30:65:1B' => 'Apple',
                '00:30:65:1C' => 'Apple',
                '00:30:65:1D' => 'Apple',
                '00:30:65:1E' => 'Apple',
                '00:30:65:1F' => 'Apple',
                '00:30:65:2D' => 'Apple',
                '00:30:65:65' => 'Apple Snow Base Station',
                '00:30:ab:07' => 'Netgear',
                '00:30:ab:0a' => 'Netgear',
                '00:30:ab:0c' => 'Netgear',
                '00:30:ab:0d' => 'Netgear',
                '00:30:ab:16' => 'Netgear',
                '00:30:AB:AB' => 'Netgear MR314NA',
                '00:30:BD'    => 'Belkin',
                '00:30:BD:BD' => 'Belkin F5D6230-3',
                '00:30:f1:10' => 'Accton Technology Corp.',
                '00:30:f1:26' => 'Accton Technology Corp.',
                '00:40:05'    => 'ANI',
                '00:40:05:05' => 'D-Link DI-614+',
                '00:40:05:ac' => 'ANI COMMUNICATIONS INC.',
                '00:40:05:de' => 'ANI COMMUNICATIONS INC.',
                '00:40:05:df' => 'ANI COMMUNICATIONS INC.',
                '00:40:26:26' => 'Buffalo WLAR-L11G-L',
                '00:40:96' => 'Aironet Wireless Communication',
                '00:40:96:96' => 'Cisco AIR-AP342E2R',
                '00:50:18' => 'ADVANCED MULTIMEDIA INTERNET TECHNOLOGY INC.',
                '00:50:8B:8B' => 'Compaq WL400 - ETSI region',
                '00:50:8b:99' => 'COMPAQ COMPUTER CORPORATION',
                '00:50:da:97' => '3COM CORPORATION',
                '00:50:DA:DA' => '3Com 3CRWE747A',
                '00:50:F2:F2' => 'Microsoft MN-500',
                '00:60:1d' => 'LUCENT TECHNOLOGIES',
                '00:60:1D:1D' => 'Orinoco RG1000',
                '00:60:b3' => 'Z-COM, INC.',
                '00:80:37:37' => 'Ericsson A11 (AP-4121-105M-ER-EU)',
                '00:80:c6:e3' => 'SOHOWare',
                '00:80:C6:C6' => 'SOHOware NetBlaster II',
                '00:80:C8'    => 'D-Link',
                '00:90:4B:4B' => 'Linksys BEFW11S4',
                '00:90:D1:D1' => 'SMC SMC2652W',
                '00:A0:04:04' => '3Com 3CRWE51196',
                '00:a0:f8' => 'SYMBOL TECHNOLOGIES, INC.',
                '00:e0:03' => 'NOKIA WIRELESS BUSINESS COMMUN',
                '00:E0:29' => 'OEM',
                '00:e0:63' => 'CABLETRON - YAGO SYSTEMS, INC.',
               };
my %MatchMAP = ('ip'      => 'MAC -> IP',
                'port'    => 'Switch Port',
                'host_ip' => 'IP -> MAC',
                'dev'     => 'IP -> Device',
                'devmac'  => 'MAC -> Device',
                'swport'  => 'Switch Port',
                'alias'   => 'Device IP w/out MAC',
                'nbname'  => 'NetBIOS',
                'wireless'=> 'Wireless Info',
               );
my $hex = "[0-9a-fA-F]";
</%shared>
<%init>
# remove other search criteria if specific search
if ($specific) {
    @oui = (); $node=''; @vendors = (); $showoui=0;
}

# Pass the form argument to the global for <%methods>
$arg_dates = $dates; @arg_vendors = @vendors; @arg_oui = @oui;
$arg_archive = $archive; $arg_showoui = $showoui; $arg_node = $node;
$arg_sort = $sort;


# ----------------- Node Search -------------------------
if ($node){
    # Choose columns to show and order to show them in matches()
    @cols = (['mac','MAC']);
    push (@cols,['oui','Vendor']) if $arg_showoui;
    push (@cols,['match','Match'],['type','Device or Node']);
    push (@cols,['time_first','First Seen']) if $arg_dates;
    push (@cols,['time_last','Last Seen']) if $arg_dates;

    $m->flush_buffer;

    $node =~ s/^\s+//;
    $node =~ s/\s+$//;
    # Translate space separated octets into colon separated.
    if ($node =~ /^${hex}{2} ${hex}{2} ${hex}{2} ${hex}{2} ${hex}{2} ${hex}{2}$/){
        $node =~ s/\s+/:/g;
    }
    # Match on a particular format of MAC address and convert to a
    # format that is_mac() and SQL will recognise as being acceptable
    if ($node =~ /^${hex}{4}-${hex}{4}-${hex}{4}$/){
        $node =~ s/-/./g;
    }
    my $wildhex = "[0-9a-fA-F?]";
    my $is_partial_mac = ($node =~ /^([?*]+)?(${wildhex}{2}:)+(${wildhex}{2})([?*]+)?$/);
    my $could_be_partial_mac = ($node =~ /^${hex}+$/ && length($node) < 13 && (length($node) % 2) == 0);
    my $is_mac = is_mac($node) || $is_partial_mac || $could_be_partial_mac;

    # Save search terms to session, change topbar
    $m->session->{_s_term} = $node;
    $m->session->{_s_type} = 'node';

    # Search by MAC Address
    if ($is_mac) {
        my $mac = $node;
        if ($could_be_partial_mac) {
            # An even number of hex digits - turn it into a partial mac
            $mac =~ s/(${hex}{2})/$1:/g;
            $mac =~ s/:$/*/;
            $mac =~ s/^/*/;
        }
        # set mac to be either node or the partial matching string
        $mac = is_mac($node) ? $node : sql_match($mac,0);
        my $where = { 'mac' => $mac };
        $where->{'active'} = 1 unless $arg_archive;

        # Lookup IPs associated with this MAC
        $m->comp('SELF:add_node_ips','where'=>$where);

        # Lookup wireless info associated with this MAC
        $m->comp('SELF:add_node_wireless','where'=>$where);

        # Lookup what switch ports this MAC was seen at.
        my $ports = sql_rows('node',
                    ['mac','switch','port','active','oui','extract(epoch from time_first) as time_first', 'extract(epoch from time_last) as time_last'],
                    $where
                    );

        foreach my $port (@$ports) {
            $port->{type} = 'port';
            $port->{switchname} = sql_scalar('device',['dns'],{'ip'=>$port->{switch}}) || '';
            $port->{switchname} =~ s/\Q$domain\E//;
            push (@$matches,$port);
        }

        # Lookup NetBIOS names.
        my $nbnames = sql_rows('node_nbt',
                ['ip','mac','nbname','domain','nbuser','active','extract(epoch from time_first) as time_first', 'extract(epoch from time_last) as time_last'],
                $where
                );

        foreach my $node_nbt (@$nbnames) {
            $node_nbt->{type} = 'nbname';
            $node_nbt->{oui}  = substr($node_nbt->{mac},0,8);
            push (@$matches,$node_nbt);
        }

        # Check the posibilty that this could be a switch port's MAC
        my $switch_ports = sql_rows('device_port',
                ['ip','port','mac','extract(epoch from creation) as time_first', 'extract(epoch from creation) as time_last'],
                {'mac' => $mac});

        foreach my $sp (@$switch_ports){
            $sp->{type} = 'swport';
            $sp->{switch} = $sp->{ip};
            $sp->{active} = 1;
            $sp->{oui}  = substr($sp->{mac},0,8);
            $sp->{switchname} = sql_scalar('device',['dns'],{'ip' => $sp->{ip}});
            $sp->{switchname} =~ s/\Q$domain\E//;
            push (@$matches,$sp);
        }

        if (@$switch_ports == 0) {
            # It could also be a device's base address
            my $device = sql_rows('device',
                ['ip','mac','dns','extract(epoch from creation) as time_first', 'extract(epoch from creation) as time_last'],
                {'mac' => $mac});

            foreach my $dev (@$device) {
                $dev->{type}   = 'devmac';
                $dev->{active} = 1;
                $dev->{oui}    = substr($dev->{mac},0,8);
                $dev->{dev}    = $dev->{ip};
                $dev->{host}   = $dev->{dns};
                $dev->{host}   =~ s/\Q$domain\E//;
                push (@$matches,$dev);
            }
        }

        # If there are no matches, at least return the vendor info.
        if (!@$matches) {
            my $oui = lc(substr($node, 0, 8));
            unless ( defined $ouis->{$oui} ){
                $ouis->{$oui} = sql_scalar('oui',['company'],{'oui' => $oui});
            }
        }
    }
    # Match by DNS/IP Address
    else {
        my $realip;
        my $device;
        if (defined getip($node)) {
            $realip = getip($node);
            # see if this is a listed device or one of its aliases.
            $device = root_device($realip);
        } else {
            # Assume a search string 
            $realip = sql_match($node);
        }

        # Find MACs associated with this IP
        my $where = { 'ip'=>$realip};
        $where->{'active'} = 1 unless $archive;
        my $macs = sql_rows('node_ip',
                    ['mac','ip','active','extract(epoch from time_first) as time_first', 'extract(epoch from time_last) as time_last'],
                    $where
                           );
        my @foundmacs = ();

        # Add info to each entry
        foreach my $node_ip (@$macs){
            my $hostname = hostname($node_ip->{ip});
            $hostname =~ s/\Q$domain\E//;
            $node_ip->{host} = $hostname;
            $node_ip->{type} = $device ? 'dev' : 'host_ip';
            $node_ip->{dev}  = $device;
            $node_ip->{oui}  = substr($node_ip->{mac},0,8);
            push (@$matches,$node_ip);
            push (@foundmacs, $node_ip->{mac});
            push(@{$Node_IP_Seen->{$node_ip->{mac}}},$realip);
        }

        # Get details for each MAC associated with this IP
        foreach my $mac (@foundmacs){
            $where = { 'mac' => $mac };
            $where->{'active'} = 1 unless $archive;
            my $ports = sql_rows('node',
                        ['mac','switch','port','active','oui','extract(epoch from time_first) as time_first', 'extract(epoch from time_last) as time_last'],
                        $where
                            );
            foreach my $port (@$ports) {
                $port->{type} = 'port';
                $port->{switchname} = sql_scalar('device',['dns'],{'ip'=>$port->{switch}});
                $port->{switchname} =~ s/\Q$domain\E//;
                push(@$matches,$port);
            }

            # Get additional/other IPs for each MAC
            $m->comp('SELF:add_node_ips','where'=>$where);

            # Lookup wireless info associated with this MAC
            $m->comp('SELF:add_node_wireless','where'=>$where);

            # Lookup NetBIOS names.
            my $nbnames = sql_rows('node_nbt',
                ['ip','mac','nbname','domain','nbuser','active','extract(epoch from time_first) as time_first', 'extract(epoch from time_last) as time_last'],
                $where
                );

            foreach my $node_nbt (@$nbnames) {
               $node_nbt->{type} = 'nbname';
               $node_nbt->{oui}  = substr($node_nbt->{mac},0,8);
               push (@$matches,$node_nbt);
            }
        }

        # If the IP belongs to a device, but we have no info, must be a VLAN or unused ALIAS
        if (!scalar @$matches and $device) {
            my $entry = {};
            $entry->{type} = 'alias';
            $entry->{mac} = $realip;
            $entry->{active} = 1;
            $entry->{dev}  = $device;
            $entry->{dns}  = sql_scalar('device',['dns'],{'ip'=>$device});
            $entry->{dns}  =~ s/\Q$domain\E//;
            $entry->{ip} = $realip;
            push (@$matches,$entry);
        }
    }

    # Search by NetBIOS Name
    unless ($is_mac) {
        # Find MACs associated with this NetBIOS Name or User
        my $where = { 'nbname/nbuser'=>sql_match($node)};
        $where->{'active'} = 1 unless $archive;
        my $macs = sql_rows('node_nbt',
                ['ip','mac','nbname','domain','nbuser','active','extract(epoch from time_first) as time_first', 'extract(epoch from time_last) as time_last'],
                $where
                    );

        my @foundmacs = ();
        my %macseen = ();

        # Add info to each entry
        foreach my $node_nbt (@$macs){
            $node_nbt->{type} = 'nbname';
            $node_nbt->{oui}  = substr($node_nbt->{mac},0,8);
            push (@$matches,$node_nbt);
            push (@foundmacs, $node_nbt->{mac}) unless $macseen{$node_nbt->{mac}}++;
        }

        # Get details for each MAC associated with this NetBIOS Name
        foreach my $mac (@foundmacs){
            $where = { 'mac' => $mac };
            $where->{'active'} = 1 unless $archive;
            my $ports = sql_rows('node',
                    ['mac','switch','port','active','oui','extract(epoch from time_first) as time_first', 'extract(epoch from time_last) as time_last'],
                    $where
                        );
            foreach my $port (@$ports) {
                $port->{type} = 'port';
                $port->{switchname} = sql_scalar('device',['dns'],{'ip'=>$port->{switch}});
                $port->{switchname} =~ s/\Q$domain\E//;
                push(@$matches,$port);
            }

           # Get additional/other IPs for each MAC
           $m->comp('SELF:add_node_ips','where'=>$where);
        }
    }

    $ran_query++;
}


# ----------------- Advanced Searches -------------------------

# Search for wireless access points
if ($specific eq 'wireless') {
    push (@search,keys %$wireless);
}

# Search for multiple IP per node
if ($specific eq 'multips') {
    @cols = (['mac','MAC'],['oui','Vendor'],['location','Location'],['cnt','IPs']);

    # select n.mac,n.switch,n.port,count(distinct(i.ip)),d.dns
    # from node_ip i, node n left join device d on d.ip = n.switch
    # where n.mac = i.mac and i.active = true and n.active = true
    # group by n.mac, n.switch, n.port,d.dns
    # having count(distinct(i.ip)) > 1
    # order by count desc;

    $matches = sql_rows('node_ip i, node n left join device d on d.ip = n.switch',
                        ['n.mac','n.switch','n.port','d.dns','true as active','count(distinct(i.ip)) as cnt','n.oui'],
                        {'n.mac' => \'i.mac', 'i.active' => 1, 'n.active' => 1},
                        undef,
                        'group by n.mac, n.switch, n.port, d.dns, n.oui having count(distinct(i.ip)) > 1 order by cnt desc'
                       );
    $arg_sort = 'none';
    $ran_query++;
}

# OUI Search
push(@search,@oui);

# Vendor Search
foreach my $vendor_search (@vendors){
    $m->comp('SELF:init_ouis');
    next unless defined $vendors->{$vendor_search};
    my $regex = $vendors->{$vendor_search};
    # Look for OUIs that match our regex
    foreach my $oui (keys %$ouis){
        my $company = $ouis->{$oui};
        if ($company =~ m/$regex/){
            push(@search,$oui)
        }
    }
}

if (scalar @search){
    @cols = (['mac','MAC'],['oui','Vendor'],['location','Location']);
    # These are taking a while, so we put this here
    #   to let em know we're still working...
    $m->flush_buffer;

    # Sort search by length
    my %search_length;
    foreach my $search (@search){
        my $length = length($search);
        # add to queue matching length
        push @{$search_length{$length}},lc($search);
    }

    # New method using substr() and IN (list) for speed
    foreach my $length (keys %search_length){
        next unless defined $length and scalar @{$search_length{$length}};
        next unless $length > 0;
        my $where = {"substr(text(n.mac),1,$length)" => [$search_length{$length}]};

        $where->{'n.active'} = 1 unless $archive;

        my $submatch = sql_rows('node n left join device d on d.ip = n.switch',
                               ['n.mac','d.dns','n.switch','n.port','n.active','n.oui'], $where);
        push @$matches, @$submatch;
    }

    # don't list OUI matches for wireless
    if ($specific eq 'wireless'){
        @search = ();
    }
    $ran_query++;
}

# Lookup OUI for matches
foreach my $match (@$matches){
    my $oui = $match->{oui};
    # only look each up once.
    unless ( defined $ouis->{$oui} ){
        $ouis->{$oui} = sql_scalar('oui',['company'],{'oui' => $oui});
    }
    $match->{oui} = $ouis->{$oui};
}
</%init>
%#
%# matches() - Outputs each match depending on type
%#
<%method matches>
% return unless $ran_query;
<h1 class="pagehead">Search Results</H1>
<& SELF:oui_matches &>
%unless (scalar @$matches){
No results found.  Try checking Archived Data.
% foreach my $oui (keys %$ouis) {
%  if ($ouis->{$oui}) {
(By the way, OUI <%$oui%> is allocated to <%$ouis->{$oui}%>)
%  }
% }
% return; }
<TABLE class="node-search">
<TR>
% foreach my $c (@cols){
    <TH><%$c->[1]%></TH>
% }
</TR>
<%perl>
my @sorted_matches; $odd=0;my $group;
if ($arg_sort eq 'dev'){
    # sort by dns/ip then by port
    @sorted_matches = sort { ($a->{dns}||$a->{switch}) cmp ($b->{dns}||$b->{switch})
                             or &sort_port } @$matches;
    $group = 'switch';
} elsif ($arg_sort eq 'oui'){
    # sort by vendor then device then port
    @sorted_matches = sort { $a->{oui} cmp $b->{oui} or
                             ($a->{dns}||$a->{switch}) cmp ($b->{dns}||$b->{switch})  or
                             &sort_port
                           } @$matches;
    $group = 'oui';
} elsif ($arg_sort eq 'none') {
    # NOOP
    @sorted_matches = @$matches;
} else {
    # by mac
    @sorted_matches = sort {$a->{mac} cmp $b->{mac}} @$matches;
    $group = 'mac';
}

my $last_match = {'oui'=>'','mac'=>''};
foreach my $match (@sorted_matches) {
    my $active = $match->{active};
    my $type   = $match->{type};
    my $last_group = $last_match->{$group} || '';
    $odd++ if $last_group ne $match->{$group};
    $odd++ if $arg_sort eq 'none';
</%perl>
<TR CLASS="match-<% $odd % 2 %>">
%     foreach my $c (@cols) {
%           my $col = $c->[0];
    <TD>\
%        if ($col eq 'mac' and $last_match->{mac} ne $match->{mac}) {
        <A HREF="node.html?node=<% $match->{mac} |u %><% $active ? '': '&archive=1' %>"><%$match->{mac}%></A>\
%     } elsif ($col eq 'oui' and ($last_match->{mac} ne $match->{mac})){
        <%$match->{oui}|h%>\
%     } elsif ($col eq 'match') {
        <%$MatchMAP{$type}  || 'Bad Match'%>\
%     } elsif ($col eq 'type') {
        <& SELF:type_map, type=>$type, match =>$match &>\
%     } elsif ($col eq 'time_first') {
        <& SELF:format_date, date=>$match->{time_first} &>\
%     } elsif ($col eq 'time_last') {
        <& SELF:format_date, date=>$match->{time_last} &>\
%     } elsif ($col eq 'location') {
%           my $loc = $match->{dns} || $match->{switch};
%           $loc =~ s/\Q$domain\E//;
        <A HREF="device.html?ip=<%$match->{switch}%>&amp;port=<%$match->{port}%>"><% $loc %> (<% $match->{port}%>)</A>\
%     } elsif ($col eq 'cnt') {
        <% $match->{cnt} |h %>
%     } else {
        &nbsp;\
%     }
    </TD>
%   }
%   $last_match=$match;
</TR>
% }
</TABLE>
% $m->out('<span class="small">* Denotes archived data</span><BR>') if $arg_archive;
Matched <% scalar $odd %> nodes.
</%method>
%#
%# format_date(epoch) - Truncates day of the week
%#
<%method format_date>
<%args>
$date
</%args>
<%perl>
return '&nbsp;' unless defined $date;
my $d = scalar localtime($date);
# chop off first word
$d =~ s/^\w+\s+//;
# no seconds
$d =~ s/(\d{2}:\d{2}):\d{2}/$1/;
</%perl>
<%$d|h%>\
</%method>
%#
%#
%# type_map(type) - For displaying data in 'type' column.
%#
<%method type_map>
<%args>
$type
$match
</%args>
%if ($type eq 'ip'){
    <A HREF="node.html?node=<% $match->{ip} |u %>"><% $match->{ip} %></A>\
    <span class="nobr">(<% defined $match->{host} ? $match->{host} : '[No DNS]' %>)</span>\
%} elsif ($type eq 'port' or $type eq 'swport') {
    <span class="nobr"><A HREF="device.html?ip=<% $match->{switch} |u%>&amp;port=<% $match->{port} |u%>"><% $match->{switch} %> [ <% $match->{port} %> ]</A></span>\
    <span class="nobr">(<% $match->{switchname} ? $match->{switchname} : '[No DNS]' %>)</span>\
%} elsif ($type eq 'host_ip') {
    <A HREF="node.html?node=<% $match->{ip} |u %>"><% $match->{ip} %></A>\
    (<% defined $match->{host} ? $match->{host} : '[No DNS]' %>)\
%} elsif ($type eq 'dev' or $type eq 'devmac') {
    <A HREF="device.html?ip=<% $match->{dev} |u %>"><% $match->{ip} %></A>\
    (<% defined $match->{host} ? $match->{host} : '[No DNS]' %>)\
%} elsif ($type eq 'alias') {
    <A HREF="device.html?ip=<%$match->{dev}|u%>"><%$match->{dev}%> (<% defined $match->{dns} ? $match->{dns} : '[No DNS]' %>)</A>\
%} elsif ($type eq 'nbname') {
    \\<A HREF="ip_search.html?domain=<%$match->{domain}|u%>&cmd=nbt" TITLE="Devices in this Domain"><%$match->{domain}%></A>\<A HREF="node.html?node=<% $match->{nbname} |u %>"><%$match->{nbname}%></A>
    <BR><%$match->{nbuser} || '[No User]'%>@<A HREF="node.html?node=<%$match->{ip}|u%>"><%$match->{ip}|h%></A>\
%} elsif ($type eq 'wireless') {
    MaxRate: <% $match->{maxrate} %>Mbps TxRate: <% $match->{txrate} %>Mbps<br>
    SigStr: <% $match->{sigstrength} %> SigQual: <% $match->{sigqual} %><br>
    Rx: <% $match->{rxpkt} %> pkts, <% $match->{rxbyte} %> bytes<br>
    Tx: <% $match->{txpkt} %> pkts, <% $match->{txbyte} %> bytes<br>
%} else {
    <% $match->{type} %>\
%}
<% $match->{active} ? '' : '<SUP>*</SUP>' %>\
</%method>
%#
%# init_ouis() - Populate %$ouis
%#
<%method init_ouis>
<%perl>
    return if defined $ouis;
    $m->flush_buffer;
    # Grab OUIs  that are present in our network
    #  and join in companies
    my $ouil = sql_rows('oui',['oui','company'],{'oui' => \\'in (select distinct(oui) from node)'});

    foreach my $o (@$ouil) {
        $ouis->{$o->{oui}} = $o->{company} || '[NA]';
    }
</%perl>
</%method>
%#
%# ouiform() - Show the Search on OUI
%#
<%method ouiform>
<& SELF:init_ouis &>
<FORM ACTION="<%$r->uri%>" METHOD="get">
<INPUT TYPE="hidden" NAME="expandoui" VALUE=1>
<TABLE CLASS="node-search">
<TR>
    <TH>Vendor:</TH>
    <TD><SELECT MULTIPLE NAME="vendors" SIZE=5>
%       foreach my $vendor (sort keys %$vendors){
            <OPTION VALUE="<%$vendor%>" <% grep(/^$vendor$/,@arg_vendors) ? 'SELECTED' : ''%>><%$vendor%>
%       }
        </SELECT>
    </TD>
    <TH>OUI:</TH>
    <TD><SELECT MULTIPLE NAME="oui" SIZE=5>
%       foreach my $oui (sort keys %$ouis){
%           next if $ouis->{$oui} eq '[NA]';
            <OPTION VALUE="<%$oui%>" <% grep(/^$oui$/,@arg_oui) ? 'SELECTED' : ''%>><%$oui%> : <%$ouis->{$oui}%>
%       }

        </SELECT></TD>
</TR>
<TR>
    <TH>Archived Data:</TH>
    <TD><INPUT TYPE="checkbox" NAME="archive" VALUE="1" <%$arg_archive ? 'CHECKED' : '' %></TD>
    <TH>Sort By:</TH>
    <TD><SELECT NAME="sort">
%           foreach my $s (sort keys %sort_type){
            <OPTION VALUE="<%$s%>"<%$arg_sort eq $s ? ' SELECTED' : ''%>><%$sort_type{$s} |h%>
%           }
        </SELECT>
    </TD>
</TR>
<TR>
    <TD>&nbsp;</TD>
    <TD><INPUT TYPE="submit" CLASS="navbutton" VALUE="Search" class="navbutton"> <A href="<%$r->uri%>" class="navbutton">Reset</A></TD>
<TR>
</TABLE>
</FORM>
</%method>
%#
%# oui_matches() - Counts how many nodes use each OUI
%#
<%method oui_matches>
% return unless scalar @search;
<h2 class="subheader">OUI Matches</h2>
<TABLE CLASS="node-search">
<TR>
    <TH>OUI</TH>
    <TH>Count</TH>
    <TH>Company</TH>
</TR>
<%perl>
# Grab Counts
my %ouicount;
$odd = 0;
foreach my $search (@search){
    $ouicount{$search} = sql_scalar('node',['count(distinct(mac))'],
                                           {'mac' => "$search\%" });
}
foreach my $search (sort {$ouicount{$b} <=> $ouicount{$a} } @search) {
    my $oui = lc(substr($search,0,8));
    my $count = $ouicount{$search};
</%perl>
<TR CLASS="match-<%++$odd%2%>">
    <TD><A HREF="<%$r->uri%>?oui=<%$search|u%>"><%$search%></A></TD>
    <TD>(<%$count%>)</TD>
    <TD><% $ouis->{$oui} || '' %></TD>
</TR>
%}
</TABLE>
<h2 class="subheader">Node Matches</h2>
</%method>
%#
%# add_node_ips(\%where) - Adds to @$matches all the IPs
%#                        for the matching mac sent in %$where
%#                        Resolves IPs to DNS as well.
%#
<%method add_node_ips>
<%args>
$where
</%args>
<%perl>
# Lookup IPs associated with this MAC
my $ips = sql_rows('node_ip',
            ['mac','ip','active','extract(epoch from time_first) as time_first', 'extract(epoch from time_last) as time_last'],
            $where
            );

foreach my $node_ip (@$ips){
    my $mac = $node_ip->{mac};
    my $ip  = $node_ip->{ip};
    # Kill Duplicates
    my $ips_seen = $Node_IP_Seen->{$mac} || [];
    if (grep(/^\Q$ip\E$/,@$ips_seen)){
        next;
    } else {
        push(@{$Node_IP_Seen->{$mac}},$ip);
    }
    $node_ip->{type} = 'ip';
    $node_ip->{host} = hostname($ip);
    $node_ip->{host} =~ s/\Q$domain\E//;
    $node_ip->{oui}  = substr($mac,0,8);
    push (@$matches,$node_ip);
}


</%perl>
</%method>
%#
%# add_node_wireless(\%where) - Adds to @$matches all of the wireless
%#                              info for the matching mac
<%method add_node_wireless>
<%args>
$where
</%args>
<%perl>
# Look up wireless info associated with this mac
my $wireless = sql_rows('node_wireless',
                ['*','extract(epoch from time_last) as time_last', 'extract(epoch from time_last) - uptime as time_first'],
                { 'mac' => $where->{'mac'} }
                );
foreach my $wl (@$wireless) {
    $wl->{type} = 'wireless';
    $wl->{active} = 1;
    push (@$matches, $wl);
}
</%perl>
</%method>
%#
%# title()
%#
<%method title>
- Node Search\
</%method>
%#
%# FILTER: highlight search term
%#
<%filter>
return $_ unless $arg_node;
my $open = '<SPAN CLASS="highlight">';
my $close = '</SPAN>';
my $search = $arg_node;
# Quote all special chars
$search =~ s/(\W)/\\$1/g;
# But turn back on ? and *
$search =~ s/\\\*/[^<]*/g;
$search =~ s/\\\?/[^<]/g;
# find things after > but before < to make sure we don't mess w/
#   the HTML.
s{(>[^<]*)($search)([^<]*<)} {$1$open$2$close$3}ig;
</%filter>
%#
%# $Id: node.html,v 1.36 2011/01/04 15:45:21 jeroenvi Exp $
%# vim:syntax=mason
