#!/usr/bin/perl -w

=head1 NAME

pfcmd_vlan - packetfence commands for VLAN isolation

=head1 SYNOPSIS

pfcmd_vlan command [options]

 Command:
   -deauthenticate      de-authenticate a dot11 client
   -deauthenticateDot1x de-authenticate a dot1x client (pass ifIndex for wired 802.1x and mac for wireless 802.1x)
   -getAlias            show the description of the specified switch port
   -getAllMACs          show all MACS on all switch ports
   -getHubs             show switch ports with several MACs
   -getIfOperStatus     show the operational status of the specified switch port
   -getIfType           show the ifType on the specified switch port
   -getLocation         show at which switch port the MAC is found
   -getSwitchLocation   show SNMP location of specified switch
   -getMAC              show all MACs on the specified switch port
   -getType             show switch type
   -getUpLinks          show the upLinks of the specified switch
   -getVersion          show switch OS version
   -getVlan             show the VLAN on the specified switch port
   -getVlanType         show the VLAN type on the specified port
   -help                brief help message
   -isolate             set the switch port to the isolation VLAN
   -man                 full documentation
   -reAssignVlan        re-assign a switch port VLAN
   -reevaluateAccess    reevaluate the current VLAN or firewall rules of a given MAC
   -runSwitchMethod     run a particular method call on a given switch (FOR ADVANCED PURPOSES)
   -setAlias            set the description of the specified switch port
   -setDefaultVlan      set the switch port to the default VLAN
   -setIfAdminStatus    set the admin status of the specified switch port
   -setVlan             set VLAN on the specified switch port
   -setVlanAllPort      set VLAN on all non-UpLink ports of the specified switch

 Options:
   -alias               switch port description
   -ifAdminStatus       ifAdminStatus
   -ifIndex             switch port ifIndex
   -mac                 MAC address
   -showPF              show additional information available in PF
   -switch              switch description
   -verbose             log verbosity level
                         0 : fatal messages
                         1 : warn messages
                         2 : info messages
                         3 : debug
                         4 : trace
   -vlan                VLAN id
   -vlanName            VLAN name (as in switches.conf)

=head1 DESCRIPTION

This script allows to execute the following commands related to switches and VLANs:

=over

=item obtain all MAC found at the specified switch port

=item obtain and set the VLAN configuration of the specified switch port

=item given a MAC, determine a which switch port it is connect

=item obtain the list of ports with several MAC addresses

=back


=cut

use strict;
use warnings;

use Data::Dumper;
use File::Basename qw(basename);
use Getopt::Long;
use Log::Log4perl;
use Log::Log4perl::Logger;
use Log::Log4perl::Level;
use Pod::Usage;
use Net::SNMP;

use constant {
    NB_THREADS  => 15,
    INSTALL_DIR => '/usr/local/pf',
};

use lib INSTALL_DIR . '/lib';
use Net::MAC::Vendor;
use pf::db;
use pf::constants;
use pf::config qw(%connection_type_explained $UNKNOWN);
use pf::file_paths qw($bin_dir);
use pf::enforcement;
use pf::locationlog;
use pf::SwitchFactory;
use pf::util;
use pf::config::util;
use pf::log;

use threads;
use threads::shared;

require 5.8.8;

my $threadBlocker : shared;
my $switchDescRegExp = '';
my $ifIndex          = 0;
my $vlan             = 0;
my $vlanName         = '';
my $mac              = undef;
my $alias            = '';
my $cmd              = '';
my $help;
my $man;
my $getMac;
my $getAllMacs;
my $getAlias;
my $getSwitchLocation;
my $setAlias;
my $getVlan;
my $setVlan;
my $setVlanAllPort;
my $setIfAdminStatus;
my $getVlanType;
my $getIfType;
my $getUpLinks;
my $getIfOperStatus;
my $getLocation;
my $setDefaultVlan;
my $isolate;
my $getHubs;
my $showPF;
my $showMacVendor;
my $logLevel = 0;
my $getVersion;
my $getType;
my $ifAdminStatus = 0;
my $deauthenticate;
my $deauthenticateDot1x;
my $reAssignVlan;
my $reevaluateAccess;
my $runSwitchMethod;

GetOptions(
    "alias:s"             => \$alias,
    "deauthenticate"      => \$deauthenticate,
    "deauthenticateDot1x" => \$deauthenticateDot1x,
    "getAlias"            => \$getAlias,
    "getSwitchLocation"   => \$getSwitchLocation,
    "getAllMacs"          => \$getAllMacs,
    "getHubs"             => \$getHubs,
    "getIfOperStatus"     => \$getIfOperStatus,
    "getIfType"           => \$getIfType,
    "getLocation"         => \$getLocation,
    "getMac"              => \$getMac,
    "getType"             => \$getType,
    "getUpLinks"          => \$getUpLinks,
    "getVersion"          => \$getVersion,
    "getVlan"             => \$getVlan,
    "getVlanType"         => \$getVlanType,
    "help|?"              => \$help,
    "ifAdminStatus:i"     => \$ifAdminStatus,
    "ifIndex:i"           => \$ifIndex,
    "isolate"             => \$isolate,
    "mac:s"               => \$mac,
    "man"                 => \$man,
    "reAssignVlan"        => \$reAssignVlan,
    "reevaluateAccess"    => \$reevaluateAccess,
    "runSwitchMethod"     => \$runSwitchMethod,
    "setAlias"            => \$setAlias,
    "setDefaultVlan"      => \$setDefaultVlan,
    "setIfAdminStatus"    => \$setIfAdminStatus,
    "setVlan"             => \$setVlan,
    "setVlanAllPort"      => \$setVlanAllPort,
    "showMacVendor"       => \$showMacVendor,
    "showPF"              => \$showPF,
    "switch:s"            => \$switchDescRegExp,
    "verbose:i"           => \$logLevel,
    "vlan:i"              => \$vlan,
    "vlanName:s"          => \$vlanName
) or pod2usage( -verbose => 1 );

pod2usage( -verbose => 2 ) if $man;
pod2usage( -verbose => 1, -exitval => 0 ) if $help;

our $logger = get_logger();

# if the verbosity flag is present we log both on file and on STDOUT and adjust log level
if ($logLevel) {
    $logger->info(
        "verbosity flag passed. "
        . "Messages now logged to stdout and logs but logged message priority will change for this pfcmd_vlan run."
    );

    # working on the root logger and not $logger (so it affects all other packages loggers too)
    my $root_logger = Log::Log4perl::Logger->get_root_logger();
    if ( $logLevel == 1 ) {
        $logger->info("New loglevel: WARN");
        $root_logger->level($WARN);
    } elsif ( $logLevel == 2 ) {
        $logger->info("New loglevel: INFO");
        $root_logger->level($INFO);
    } elsif ( $logLevel == 3 ) {
        $logger->info("New loglevel: DEBUG");
        $root_logger->level($DEBUG);
    } else {
        $logger->info("New loglevel: TRACE");
        $root_logger->level($TRACE);
    }

    my $appender = Log::Log4perl::Appender->new(
        "Log::Log4perl::Appender::Screen", stderr => 0
    );
    $root_logger->add_appender($appender);
}

# TODO we should consolidate calls which need similar parameters and unduplicate tests and error reporting
if ($reevaluateAccess) {

    if (!defined($mac) || $mac eq '' ) {
        exit_wrong_args("MAC argument is necessary");
    }

    pf::enforcement::reevaluate_access( $mac, 'pfcmd_vlan' );

# Warning: powerfully dangerous method below (reconsider if unstrusted users have access to pfcmd_vlan)
# This executes a specified method call passed on the command line on a
# proper switch object with the parameters supplied.
} elsif ($runSwitchMethod) {

    if ( $switchDescRegExp eq '' ) {
        exit_wrong_args("the switch argument is necessary");
    }
    my $switch = pf::SwitchFactory->instantiate($switchDescRegExp);
    if (!$switch) {
        exit_wrong_args("unknown switch $switchDescRegExp");
    } else {
        # grabbing parameters
        my ($method, @params) = @ARGV;
        local $" = ", ";
        $logger->debug("start handling 'runSwitchMethod' command with: ->$method(@params)");
        {
            no strict 'refs';
            $switch->$method(@params);
        }
        $logger->debug("finished handling 'runSwitchMethod' command");
    }

} elsif ($getUpLinks) {

    if ( $switchDescRegExp eq '' ) {
        exit_wrong_args("the switch argument is necessary");
    }
    my $switch = pf::SwitchFactory->instantiate($switchDescRegExp);
    if (!$switch) {
        exit_wrong_args("unknown switch $switchDescRegExp");
    } else {
        $logger->debug("start handling 'getUpLinks' command");
        my @upLinks = $switch->getUpLinks();
        foreach my $currentIfIndex (@upLinks) {
            print "- $currentIfIndex\n";
        }
        $logger->debug("finished handling 'getUpLinks' command");
    }

} elsif ($getSwitchLocation) {

    if ( $switchDescRegExp eq '' ) {
        exit_wrong_args("the switch argument is necessary");
    }
    my $switch = pf::SwitchFactory->instantiate($switchDescRegExp);
    if (!$switch) {
        exit_wrong_args("unknown switch $switchDescRegExp");
    } else {
        $logger->debug("start handling 'getSwitchLocation' command");
        print $switch->getSwitchLocation($ifIndex) . "\n";
        $logger->debug("finished handling 'getSwitchLocation' command");
    }

} elsif ($getVersion) {

    if ( $switchDescRegExp eq '' ) {
        exit_wrong_args("the switch argument is necessary");
    }
    my $switch = pf::SwitchFactory->instantiate($switchDescRegExp);
    if (!$switch) {
        exit_wrong_args("unknown switch $switchDescRegExp");
    } else {
        $logger->debug("start handling 'getVersion' command");
        print $switchDescRegExp . "," . $switch->getVersion() . "\n";
        $logger->debug("finished handling 'getVersion' command");
    }

} elsif ($getType) {

    # TODO: remove this feature or add all switches we support (saying Oops if we can't tell)
    if ( $switchDescRegExp eq '' ) {
        exit_wrong_args("the switch argument is necessary");
    }
    my $switch = pf::SwitchFactory->instantiate($switchDescRegExp);
    if (!$switch) {
        exit_wrong_args("unknown switch $switchDescRegExp");
    } else {
        $logger->debug("start handling 'getType' command");
        my $session;
        my $error;
        if ( $switch->{_SNMPVersion} eq '3' ) {
            ( $session, $error ) = Net::SNMP->session(
                -hostname     => $switch->{_ip},
                -version      => $switch->{_SNMPVersion},
                -username     => $switch->{_SNMPUserNameRead},
                -timeout      => 2,
                -retries      => 1,
                -authprotocol => $switch->{_SNMPAuthProtocolRead},
                -authpassword => $switch->{_SNMPAuthPasswordRead},
                -privprotocol => $switch->{_SNMPPrivProtocolRead},
                -privpassword => $switch->{_SNMPPrivPasswordRead},
                -maxmsgsize => 4096
            );
        } else {
            ( $session, $error ) = Net::SNMP->session(
                -hostname  => $switch->{_ip},
                -version   => $switch->{_SNMPVersion},
                -timeout   => 2,
                -retries   => 1,
                -community => $switch->{_SNMPCommunityRead},
                -maxmsgsize => 4096
            );
        }
        my $type      = 'unknown';
        my $version   = 'unknown';
        my $versionOk = 0;
        if ( defined($session) ) {
            my $oid_sysDescr = '1.3.6.1.2.1.1.1.0';    #SNMPv2-MIB
            my $result       = $session->get_request( -varbindlist => [$oid_sysDescr] );
            if ( defined( $result->{$oid_sysDescr} ) ) {
                my $sysDescr = $result->{$oid_sysDescr};
                $type = $sysDescr;
                if ( $sysDescr =~ m/IOS \(tm\) C(\w+) / ) {
                    $type = "Cisco::Catalyst_$1";
                } elsif ( $sysDescr =~ /IOS Software, C(\w+) / ) {
                    $type = "Cisco::Catalyst_$1";
                } elsif ( $sysDescr =~ /Intel Express 460T Standalone Switch/ ) {
                    $type = "Intel::Express_460";
                } elsif ( $sysDescr =~ /Intel\(R\) Express 530T Switch/ ) {
                    $type = "Intel::Express_530";
                } elsif ( $sysDescr =~ /Switch ES3526XA/ ) {
                    $type = "Accton::ES3526XA";
                } elsif ( $sysDescr =~ /^Ethernet Switch$/ ) {
                    my $oid_productIdentificationDisplayName = '1.3.6.1.4.1.674.10895.3000.1.2.100.1.0';
                    $result = $session->get_request( -varbindlist => [$oid_productIdentificationDisplayName] );
                    if ( defined( $result->{$oid_productIdentificationDisplayName} ) ) {
                        if ( $result->{$oid_productIdentificationDisplayName} ==~ /PowerConnect 3424/ ) {
                            $type = "Dell::PowerConnect3424";
                        } else {
                            $type = $result->{$oid_productIdentificationDisplayName};
                        }
                    }
                }
                if ( $type eq $switch->{'_type'} ) {
                    $version   = $switch->getVersion();
                    $versionOk = $switch->isNewerVersionThan( $switch->getMinOSVersion() );
                }
            }
        }
        print "$switchDescRegExp, $type, $version, ";
        if ( $type ne $switch->{'_type'} ) {
            print "typeProblem, configuration file tells us " . $switch->{'_type'};
        } else {
            if ( !$versionOk ) {
                print "versionProblem: minOSVersion is " . $switch->getMinOSVersion();
            } else {
                print "ok";
            }
        }
        print "\n";
        $logger->debug("finished handling 'getType' command");

    }

} elsif ($getAllMacs) {

    if ( $switchDescRegExp eq '' ) {
        exit_wrong_args("the switch argument is necessary");
    }
    my $switch = pf::SwitchFactory->instantiate($switchDescRegExp);
    if (!$switch) {
        exit_wrong_args("unknown switch $switchDescRegExp");
    } else {
        $logger->debug("start handling 'getAllMacs' command");
        my $ifIndexVlanMacHashRef = $switch->getAllMacs();
        foreach my $ifIndex ( %{$ifIndexVlanMacHashRef} ) {
            foreach my $vlan ( %{ $ifIndexVlanMacHashRef->{$ifIndex} } ) {
                foreach my $mac ( @{ $ifIndexVlanMacHashRef->{$ifIndex}->{$vlan} } ) {
                    print "$ifIndex\t$vlan\t$mac\n";
                }
            }
        }
        $logger->debug("finished handling 'getAllMacs' command");
    }

} elsif ($getHubs) {

    if ( $switchDescRegExp eq '' ) {
        exit_wrong_args("the switch argument is necessary");
    }
    my $switch = pf::SwitchFactory->instantiate($switchDescRegExp);
    if (!$switch) {
        exit_wrong_args("unknown switch $switchDescRegExp");
    } else {
        $logger->debug("start handling 'getHubs' command");
        my $hubPorts;
        eval { $hubPorts = $switch->getHubs(); };
        foreach my $port ( sort keys %$hubPorts ) {
            print "hub at switch $switchDescRegExp, port $port\n";
            foreach my $mac ( sort @{ $hubPorts->{$port} } ) {
                print "- $mac";
                if ($showMacVendor) {
                    my $oui_info = Net::MAC::Vendor::lookup($mac);
                    print ", " . ( $$oui_info[0] || 'unknown' );
                }
                if ($showPF) {
                    my @pfcmd = pf_run("$bin_dir/pfcmd node view $mac");
                    @pfcmd = split( /[|]/, $pfcmd[1] );
                    print ", $pfcmd[7]" unless ( $pfcmd[7] eq '' );
                    print ", $pfcmd[1]" unless ( $pfcmd[1] eq '' );
                }
                print "\n";
            }
            print "\n";
        }
        $logger->debug("finished handling 'getHubs' command");
    }

} elsif ($deauthenticate) {

    if (!defined($mac) || $mac eq '') {
        exit_wrong_args("the MAC argument is necessary");
    }
    if ( $switchDescRegExp eq '' ) {
        exit_wrong_args("the switch argument is necessary");
    }
    my $switch = pf::SwitchFactory->instantiate($switchDescRegExp);
    if (!$switch) {
        exit_wrong_args("unknown switch $switchDescRegExp");
    } else {
        $logger->debug("start handling 'deauthenticate' command");
        $switch->deauthenticateMac($mac);
        $logger->debug("finished handling 'deauthenticate' command");
    }

} elsif ($deauthenticateDot1x) {

    my $macUndefOrEmpty     = (!defined($mac) || $mac eq '');
    my $ifIndexUndefOrEmpty = (!defined($ifIndex) || $ifIndex == 0);
    if ($macUndefOrEmpty && $ifIndexUndefOrEmpty) {
        exit_wrong_args("Please provide a MAC for wireless 802.1x or an ifIndex for wired 802.1x");
    }
    if ( $switchDescRegExp eq '' ) {
        exit_wrong_args("the switch argument is necessary");
    }
    my $switch = pf::SwitchFactory->instantiate($switchDescRegExp);
    if (!$switch) {
        exit_wrong_args("unknown switch $switchDescRegExp");
    } else {
        $logger->debug("start handling 'deauthenticateDot1x' command");
        if (defined($mac) && $mac ne '') {
            $logger->info("wireless deauthentication of a 802.1x MAC");
            # TODO make sure the return code of deauthenticateMac() is coherent in all modules
            # this was not done when this simple fix was committed
            my $result = $switch->deauthenticateMac($mac, $TRUE);
            exit(1) if (!defined($result) || $result != $TRUE );
        } elsif (defined($ifIndex) && $ifIndex != 0) {
            $logger->info("wired deauthentication of a 802.1x MAC");
            $switch->dot1xPortReauthenticate($ifIndex);
        } else {
            exit_wrong_args("Please provide a MAC for wireless 802.1x or an ifIndex for wired 802.1x");
        }
        $logger->debug("finished handling 'deauthenticateDot1x' command");
    }

} elsif ( $getMac
    || $getAlias
    || $setAlias
    || $getVlan
    || $setVlan
    || $setVlanAllPort
    || $getVlanType
    || $isolate
    || $setDefaultVlan
    || $getIfType
    || $getIfOperStatus
    || $setIfAdminStatus
    || $reAssignVlan )
{

    if ( !$setVlanAllPort ) {
        if ( $ifIndex == 0 ) {
            exit_wrong_args("the ifIndex option is necessary");
        }
    }
    if ( $switchDescRegExp eq '' ) {
        exit_wrong_args("the switch argument is necessary");
    }
    my $switch = pf::SwitchFactory->instantiate($switchDescRegExp);
    if (!$switch) {
        exit_wrong_args("unknown switch $switchDescRegExp");
    } else {
        if ($getMac) {

            $logger->debug("start handling 'getMac' command");

            foreach my $currentMac ( $switch->getMacAtIfIndex($ifIndex) ) {
                print "$currentMac";
                if ($showMacVendor) {
                    my $oui_info = Net::MAC::Vendor::lookup($currentMac);
                    print ", " . ( $$oui_info[0] || 'unknown' );
                }
                if ($showPF) {
                    my @pfcmd = pf_run("$bin_dir/pfcmd node view $currentMac");
                    @pfcmd = split( /[|]/, $pfcmd[1] );
                    print ", $pfcmd[8]" unless ( $pfcmd[8] eq '' );
                    print ", $pfcmd[1]" unless ( $pfcmd[1] eq '' );
                }
                print "\n";
            }
            $logger->debug("finished handling 'getMac' command");

        # FIXME: error handling please? all the calls below will generate warnings if the methods return undef or 0
        # illegal concatenation of undef and str
        } elsif ($getIfOperStatus) {
            $logger->debug("start handling 'getIfOperStatus' command");
            print $switch->getIfOperStatus($ifIndex) . "\n";
            $logger->debug("finished handling 'getIfOperStatus' command");
        } elsif ($getIfType) {
            $logger->debug("start handling 'getIfType' command");
            print $switch->getIfType($ifIndex) . "\n";
            $logger->debug("finished handling 'getIfType' command");
        } elsif ($getVlan) {
            $logger->debug("start handling 'getVlan' command");
            print $switch->getVlan($ifIndex) . "\n";
            $logger->debug("finished handling 'getVlan' command");
        } elsif ($reAssignVlan) {

            $logger->debug("start handling 'reAssignVlan' command");
            my @locationlog_entry = locationlog_view_open_switchport_no_VoIP($switch->{'_id'}, $ifIndex);
            if (@locationlog_entry) {
                my $conn_type = str_to_connection_type($locationlog_entry[0]->{'connection_type'});

                if ($conn_type != $UNKNOWN) {
                    $logger->info(
                        "sending local reAssignVlan trap to force VLAN re-evaluation on " .
                        "switch ".$switch->{_ip}." ifIndex $ifIndex " .
                        "connection type: ".$connection_type_explained{$conn_type}
                    );
                    my $trapSender = pf::SwitchFactory->instantiate('127.0.0.1');
                    $trapSender->sendLocalReAssignVlanTrap($switch, $ifIndex, $conn_type);
                } else {
                    $logger->warn(
                        "Unknown connection type! ".
                        "We won't perform VLAN reassignment since we don't know how to disconnect it."
                    );
                }
            } else {
                $logger->warn("We won't perform VLAN reassignment since it seems no one is connected here");
            }

            $logger->debug("finished handling 'reAssignVlan' command");
        } elsif ($setVlan) {
            if ( $vlan == 0 && $vlanName eq '' ) {
                exit_wrong_args("you must specify the VLAN to set (either with -vlan or -vlanName)");
            }
            if ($vlan != 0 && $vlanName ne '') {
                exit_wrong_args("you must specify only one way to set VLAN (-vlan or -vlanName)");
            }
            $logger->debug("start handling 'setVlan' command");
            my %switch_locker : shared;
            $switch_locker{ $switch->{_ip} } = &share( {} );
            if ($vlan) {
                $switch->setVlan( $ifIndex, $vlan, \%switch_locker );
            } else {
                $switch->setVlanByName($ifIndex, $vlanName, \%switch_locker);
            }
            print "new VLAN: " . $switch->getVlan($ifIndex) . "\n";
            $logger->debug("finished handling 'setVlan' command");
        } elsif ($setIfAdminStatus) {
            $logger->debug("start handling 'setIfAdminStatus' command");
            $switch->setAdminStatus( $ifIndex, $ifAdminStatus );
            $logger->debug("finished handling 'setIfAdminStatus' command");
        } elsif ($getAlias) {
            $logger->debug("start handling 'getAlias' command");
            print $switch->getAlias($ifIndex) . "\n";
            $logger->debug("finished handling 'getAlias' command");
        } elsif ($setAlias) {
            if ( $alias == '' ) {
                exit_wrong_args("you must specify the description to set");
            }
            $logger->debug("start handling 'setAlias' command");
            $switch->setAlias( $ifIndex, $alias );
            print "new alias: " . $switch->getAlias($ifIndex) . "\n";
            $logger->debug("finished handling 'setAlias' command");
        } elsif ($setVlanAllPort) {
            if ( $vlan == 0 ) {
                exit_wrong_args("you must specify the VLAN to set");
            }
            $logger->debug("start handling 'setVlanAllPort' command");
            my %switch_locker : shared;
            $switch_locker{ $switch->{_ip} } = &share( {} );
            $switch->setVlanAllPort( $vlan, \%switch_locker );
            print "finished handling 'setVlanAllPort' command\n";
            $logger->debug("finished handling 'setVlan' command");
        } elsif ($getVlanType) {
            $logger->debug("start handling 'getVlanType' command");
            print $switch->getVmVlanType($ifIndex) . "\n";
            $logger->debug("finished handling 'getVlantype' command");
        } elsif ($isolate) {
            $logger->debug("start handling 'isolate' command");
            my %switch_locker : shared;
            $switch_locker{ $switch->{_ip} } = &share( {} );
            $switch->setVlanByName($ifIndex, 'isolationVlan', \%switch_locker);
            print "new VLAN: " . $switch->getVlan($ifIndex) . "\n";
            $logger->debug("finished handling 'isolate' command");
        } elsif ($setDefaultVlan) {
            $logger->debug("start handling 'setDefaultVlan' command");
            my %switch_locker : shared;
            $switch_locker{ $switch->{_ip} } = &share( {} );
            $switch->setVlanByName($ifIndex, 'normalVlan', \%switch_locker);
            print "new VLAN: " . $switch->getVlan($ifIndex) . "\n";
            $logger->debug("finished handling 'setDefaultVlan' command");
        }
    }

} elsif ($getLocation) {

    if (! defined($mac) || ($mac eq '')) {
       exit_wrong_args("The MAC argument is necessary");
    }
    $logger->debug("start handling 'getLocation' command");
    my %Config = %{ pf::SwitchFactory->config };

    #remove unwanted switches
    if ( $switchDescRegExp eq '' ) {
        $switchDescRegExp = '.';
    }
    delete $Config{'default'};
    delete $Config{'127.0.0.1'};

    my @switchDescList = sort keys %Config;
    for ( my $i = 0; $i < scalar(@switchDescList); $i++ ) {
        if ( !( $switchDescList[$i] =~ $switchDescRegExp ) ) {
            delete $Config{ $switchDescList[$i] };
        }
    }

    my @switchList = keys %Config;
    my $i          = 0;
    my $found      = 0;
    my $switch_ip  = undef;
    my $ifIndex    = undef;
    my $ifDesc     = undef;
    while ( ( $i < scalar(@switchList) ) && ( !$found ) ) {
        my $key = $switchList[$i];
        $i++;
        if ( ( $key ne 'default' ) && ( $key ne '127.0.0.1') ) {
            $switch_ip = $key;
            my $switch = pf::SwitchFactory->instantiate($switch_ip);
            if (!$switch) {
                print "Can not instantiate switch $switch_ip ! See log files for details\n";
            } else {
                $ifIndex = $switch->getIfIndexForThisMac($mac);
                if ( $ifIndex != -1 ) {
                    $found  = 1;
                    $ifDesc = $switch->getIfDesc($ifIndex);
                }
            }
        }
    }
    if ($showMacVendor) {
        my $oui_info = Net::MAC::Vendor::lookup($mac);
        $mac = "$mac (" . ( $$oui_info[0] || 'unknown' ) . ")";
    }
    if ($found) {
        print
            "MAC $mac found at switch $switch_ip, ifIndex $ifIndex ($ifDesc)\n";
    } else {
        print "MAC $mac cannot be found\n";
    }
    $logger->debug("finished handling 'getLocation' command");

} else {
    pod2usage( -verbose => 1 );
}

# simple wrapper to use to display documentation when wrong arguments were
# provided
sub exit_wrong_args {
    my ($msg) = @_;
    pod2usage(-message => $msg, -exitval => 1);
}

=head1 AUTHOR

Inverse inc. <info@inverse.ca>

=head1 COPYRIGHT

Copyright (C) 2005-2019 Inverse inc.

=head1 LICENSE

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
USA.

=cut

# vim: set shiftwidth=4:
# vim: set expandtab:
# vim: set backspace=indent,eol,start:

