#!/usr/bin/perl -w

# perldoc {{{1

=head1 NAME

pfsetvlan

=head1 SYNOPSIS

./pfsetvlan [options]

 Options:
   -daemonize      daemonize
   -help           brief help message
   -man            full documentation

=head1 DESCRIPTION

Act on SNMP traps and set switch port VLAN according to
the discovered MACs status in packetfence.

=head1 AUTHOR

Dominik Gehl <dgehl@inverse.ca>

Regis Balzard <rbalzard@inverse.ca>

Olivier Bilodeau <obilodeau@inverse.ca>

=head1 COPYRIGHT

Copyright (C) 2006-2011 Inverse inc.

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

# }}}1

# use, require {{{1
use strict;
use warnings;
use diagnostics;

use Data::Dumper;
use File::Tail;
use threads;
use threads::shared;
use File::Basename qw(basename);
use Log::Log4perl;
use Getopt::Long;
use Pod::Usage;
use Net::SMTP;
use POSIX ();

use constant INSTALL_DIR => '/usr/local/pf';

use constant {
    LOG_FILE   => INSTALL_DIR . "/logs/packetfence.log",
    PFCMD_FILE => INSTALL_DIR . "/bin/pfcmd"
};

use lib INSTALL_DIR . '/lib';

require 5.8.8;
use pf::config;
use pf::floatingdevice::custom;
use pf::inline::custom $INLINE_API_LEVEL;
use pf::locationlog;
use pf::node;
use pf::SNMP 2.00;
use pf::SNMP::constants;
use pf::SwitchFactory;
use pf::traplog;
use pf::util;
use pf::violation;
use pf::vlan::custom $VLAN_API_LEVEL;
$thread = 1;

# }}}1

Log::Log4perl->init_and_watch( INSTALL_DIR . '/conf/log.conf', $LOG4PERL_RELOAD_TIMER );
my $logger = Log::Log4perl->get_logger( basename($0) );
Log::Log4perl::MDC->put( 'proc', basename($0) );
Log::Log4perl::MDC->put( 'tid',  threads->self->tid() );

# sighandler {{{1
POSIX::sigaction(
    &POSIX::SIGTERM,
    POSIX::SigAction->new(
        'normal_sighandler', POSIX::SigSet->new(), &POSIX::SA_NODEFER
    )
) or $logger->logdie("pfsetvlan: could not set SIGTERM handler: $!");

POSIX::sigaction(
    &POSIX::SIGINT,
    POSIX::SigAction->new(
        'normal_sighandler', POSIX::SigSet->new(), &POSIX::SA_NODEFER
    )
) or $logger->logdie("pfsetvlan: could not set SIGINT handler: $!");

POSIX::sigaction(
    &POSIX::SIGALRM,
    POSIX::SigAction->new(
        'ignore_sighandler', POSIX::SigSet->new(), &POSIX::SA_NODEFER
    )
) or $logger->logdie("pfsetvlan: could not set SIGALRM handler: $!");


# }}}1

# command line options {{{1
my $help;
my $man;
my $daemonize;
GetOptions(
    "help|?"    => \$help,
    "man"       => \$man,
    "daemonize" => \$daemonize,
) or pod2usage( -verbose => 1 );

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

# }}}1

# startup {{{1
my $switchFactory = pf::SwitchFactory->getInstance();

# building trap-matcher regexp
my $TRAP_PATTERN = qr/
    ^\d{4}-\d{2}-\d{2}\|\d{2}:\d{2}:\d{2}\|             # date|time
    (?:UDP:\ \[)?                                       # Optional "UDP: [" (since v2 traps I think)
    (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})                # network device ip address
    (?:\]:\d+)?                                         # Optional "]:port" (since v2 traps I think)
    (?:\-\>\[\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\])?     # Optional "->[ip address]" (since net-snmp 5.4)
    \|([^|]*)\|                                         # Used to carry network device ip if it's a local trap
    (.+)$                                               # Trap message
/sx; # s for multiline support (if we encounter an Hex 0a which is encoded as a newline in STRING)

my $fh = new File::Tail(
    'name'        => INSTALL_DIR . '/logs/snmptrapd.log',
    'interval'    => 2,
    'reset_tail'  => 0,
    'maxinterval' => 2
);

if ($daemonize) {

# Begin - Daemonize the program (see http://www.webreference.com/perl/tutorial/9/index.html)

    chdir('/') or $logger->logdie("Can't chdir to /: $!");
    open( STDIN, '<', '/dev/null' )
        or $logger->logdie("Can't read /dev/null: $!");
    open STDERR, '>>', LOG_FILE
        or $logger->logdie( "Can't open " . LOG_FILE );
    open STDOUT, '>>', LOG_FILE
        or $logger->logdie( "Can't open " . LOG_FILE );

    # (at this point STDOUT and STDERR should be redirected to files)

    defined( my $pid = fork ) or $logger->logdie("Can't fork: $!");
    POSIX::_exit(0) if $pid;
    POSIX::setsid() or $logger->logdie("Can't start a new session: $!");
    umask 0;

    # Write the PID
    if ( !createpid() ) {
        $logger->logdie("unable to daemonize");
    }
}

# End - Daemonize...

$logger->info("Process started");

my %threadList_running : shared;
my %threadList_toBeKilled : shared;
my @threadList_queued : shared;
my @trapList_queued : shared;
my $switchFactory_locker : shared;

my @completeThreadList;

my %switch_locker : shared;
foreach my $switch_ip ( sort keys %{ $switchFactory->{_config} } ) {
    if ( $switch_ip ne 'default' ) {
        $switch_locker{$switch_ip} = &share( {} );
    }
}

for ( my $i = 1; $i <= $Config{'vlan'}{'nbtraphandlerthreads'}; $i++ ) {
    my $t = threads->new("signalHandlerThreadListQueued");
    if ( !defined($t) ) {
        $logger->error(
                  "could not create " 
                . $i
                . (
                ( $i == 1 )
                ? "st"
                : ( ( $i == 2 ) ? "nd" : ( ( $i == 3 ) ? "rd" : "th" ) )
                )
                . " signalHandlerThreadListQueued thread"
        );
        exit -1;
    } else {
        push @completeThreadList, $t;
        $logger->debug(
                  "created " 
                . $i
                . (
                ( $i == 1 )
                ? "st"
                : ( ( $i == 2 ) ? "nd" : ( ( $i == 3 ) ? "rd" : "th" ) )
                )
                . " signalHandlerThreadListQueued thread"
        );
    }
}

for ( my $i = 1; $i <= $Config{'vlan'}{'nbtrapparserthreads'}; $i++ ) {
    my $t = threads->new("signalHandlerTrapListQueued");
    if ( !defined($t) ) {
        $logger->error(
                  "could not create " 
                . $i
                . (
                ( $i == 1 )
                ? "st"
                : ( ( $i == 2 ) ? "nd" : ( ( $i == 3 ) ? "rd" : "th" ) )
                )
                . " signalHandlerTrapListQueued thread"
        );
        exit -1;
    } else {
        push @completeThreadList, $t;
        $logger->debug(
                  "created " 
                . $i
                . (
                ( $i == 1 )
                ? "st"
                : ( ( $i == 2 ) ? "nd" : ( ( $i == 3 ) ? "rd" : "th" ) )
                )
                . " signalHandlerTrapListQueued thread"
        );
    }
}

# }}}1

# main program - read trap lines and addTrapLineToQueue($trapLine) {{{1
my $currentTrapLine = undef;
my $completeTrapLine;
my $inMultiLineTrap = 0;
while ( defined( $currentTrapLine = $fh->read ) ) {
    $currentTrapLine =~ s/\r\n/\n/;
    chomp($currentTrapLine);
    if ( $currentTrapLine =~ m/BEGIN VARIABLEBINDINGS/ ) {
        if ( $currentTrapLine =~ m/END VARIABLEBINDINGS$/ ) {
            addTrapLineToQueue($currentTrapLine);
        } else {

            #start multiLine read
            $inMultiLineTrap  = 1;
            $completeTrapLine = $currentTrapLine;
        }
    } else {
        if ($inMultiLineTrap) {
            $completeTrapLine .= "\n$currentTrapLine";
            if ( $currentTrapLine =~ m/END VARIABLEBINDINGS$/ ) {

                #end multiLine read
                $inMultiLineTrap = 0;
                addTrapLineToQueue($completeTrapLine);
            }
        } else {
            $logger->warn("ignoring non trap line $currentTrapLine");
        }
    }
}

# }}}1

# sub addTrapLineToQueue($trapLine) - cond_signal(@trapList_queued){{{1
sub addTrapLineToQueue {
    my ($trapLine) = @_;
    my $logger     = Log::Log4perl->get_logger('pfsetvlan::parsing');
    my $lockLogger = Log::Log4perl->get_logger('pfsetvlan::locking');
    Log::Log4perl::MDC->put( 'tid', threads->self->tid() );
    $lockLogger->trace("locking - trying to lock trapList_queued");
    {
        lock @trapList_queued;
        $logger->debug("adding trapline $trapLine to queued trapList");
        push @trapList_queued, "raw|$trapLine";
        $lockLogger->trace("locking - sending signal for trapList_queued");
        cond_signal(@trapList_queued);
    }
    $lockLogger->trace("locking - unlocked trapList_queued");
}

# }}}1

# sub signalHandlerTrapListQueued - cond_wait(@trapList_queued), parseTrap($trapLineToParse) {{{1
sub signalHandlerTrapListQueued {
    my $logger     = Log::Log4perl->get_logger('pfsetvlan::parsing');
    my $lockLogger = Log::Log4perl->get_logger('pfsetvlan::locking');
    Log::Log4perl::MDC->put( 'tid', threads->self->tid() );

    while (1) {
        my $trapLineToParse = undef;
        my $parseResult     = undef;
        $lockLogger->trace(
            "locking - trying to lock trapList_queued in signalHandlerTrapListQueued"
        );
        {
            lock @trapList_queued;
            $lockLogger->trace(
                "locking - obtained lock on trapList_queued in signalHandlerTrapListQueued"
            );
            $lockLogger->trace(
                "locking - waiting for signal for trapList_queued in signalHandlerTrapListQueued"
            );
            cond_wait(@trapList_queued);
            $lockLogger->trace(
                "locking - received signal for trapList_queued in signalHandlerTrapListQueued"
            );

            $logger->trace( "initially trapList_queued contains\n"
                    . join( "\n", @trapList_queued )
                    . "\n" );

            #find first entry in @trapList_queued which must be parsed
            my $i = 0;
            while (( $i < scalar(@trapList_queued) )
                && ( !( $trapList_queued[$i] =~ /^raw\|/ ) ) )
            {
                $logger->trace(
                    "did not find raw trapLine in trapList_queued at position $i"
                );
                $i++;
            }
            if ( $i < scalar(@trapList_queued) ) {
                $trapLineToParse = substr( $trapList_queued[$i], 4 );
                $logger->debug(
                    "retrieved raw trapline $trapLineToParse from trapList_queued at position $i"
                );
                $trapList_queued[$i]
                    = "inparse" . threads->self->tid() . "|$trapLineToParse";
                $logger->trace(
                    "after retrieval operation trapList_queued contains\n"
                        . join( "\n", @trapList_queued )
                        . "\n" );
            } else {
                $logger->trace(
                    "couldn't find any raw trapLine in trapList_queued");
            }
        }
        $lockLogger->trace(
            "locking - unlocked trapList_queued in signalHandlerTrapListQueued"
        );

        if ( defined($trapLineToParse) ) {
            $logger->debug("calling parseTrap for $trapLineToParse");
            $parseResult = parseTrap($trapLineToParse);
            $lockLogger->trace(
                "locking - trying to lock trapList_queued in signalHandlerTrapListQueued"
            );
            {
                lock @trapList_queued;
                $lockLogger->trace(
                    "locking - obtained lock on trapList_queued in signalHandlerTrapListQueued"
                );
                my $i        = 0;
                my $threadId = threads->self->tid();
                $logger->trace(
                    "after parseTrap operation trapList_queued contains\n"
                        . join( "\n", @trapList_queued )
                        . "\n" );
                while (( $i < scalar(@trapList_queued) )
                    && ( !( $trapList_queued[$i] =~ /^inparse$threadId\|/ ) )
                    )
                {
                    $logger->trace(
                        "did not find line starting with \"inparse$threadId\" in trapList_queued at position $i"
                    );
                    $i++;
                }
                if ( $i < scalar(@trapList_queued) ) {
                    $logger->trace(
                        "replacing threadList_queued[$i] with parseTrap result"
                    );
                    $trapList_queued[$i]
                        = "parsed$threadId|" . ( $parseResult || '' );
                    $logger->trace(
                        "after parseTrap and replacement operation trapList_queued contains\n"
                            . join( "\n", @trapList_queued )
                            . "\n" );
                    $logger->debug(
                        "finished parsing $i"
                            . (
                            ( $i == 2 ) ? 'nd' : ( $i == 3 ) ? 'rd' : 'th'
                            )
                            . " trapList_queued entry"
                    );
                } else {
                    $logger->warn(
                        "could not find line starting with \"inparse$threadId\" in trapList_queued -> this is BAD"
                    );
                }
            }
            $lockLogger->trace(
                "locking - unlocked trapList_queued in signalHandlerTrapListQueued"
            );
        }

        $lockLogger->trace(
            "locking - trying to lock trapList_queued in signalHandlerTrapListQueued"
        );
        {
            lock @trapList_queued;
            $lockLogger->trace(
                "locking - obtained lock on trapList_queued in signalHandlerTrapListQueued"
            );

            #for already parsed entries at the begining of @trapList_queued
            #  shift and check if we need to add them to @threadList_queued
            my $i = 0;
            my @candidatesForThreadList_queued;
            my @tmpTrapList_queued;
            my %switchesWithUnfinishedParse;
            while (( $i < scalar(@trapList_queued) )
                && ( !( $trapList_queued[$i] =~ /^raw\|/ ) ) )
            {
                my $trapLine = undef;
                my $prefix   = undef;
                if ( $trapList_queued[$i] =~ /^(parsed\d+\|)/ ) {
                    $prefix = $1;
                } elsif ( $trapList_queued[$i] =~ /^(inparse\d+\|)/ ) {
                    $prefix = $1;
                }
                if ( defined($prefix) ) {
                    $trapLine
                        = substr( $trapList_queued[$i], length($prefix) );
                    if ( $prefix =~ /^inparse/ ) {
                        push @tmpTrapList_queued, $trapList_queued[$i];
                        if ($trapLine =~ /$TRAP_PATTERN/) {
                            my $switch_ip = $1;
                            $logger->trace(
                                "adding $switch_ip to switchesWithUnfinishedParse"
                            );
                            $switchesWithUnfinishedParse{$switch_ip} = 1;
                        }
                    } else {
                        if ( length($trapLine) > 0 ) {
                            my ( $switch_ip, $switch_port, $trapType,
                                $trapVlan, $trapOperation, $trapMac )
                                = split( /\|/, $trapLine );
                            if (exists(
                                    $switchesWithUnfinishedParse{$switch_ip}
                                )
                                )
                            {
                                $logger->trace(
                                    "parsed trapLine $trapLine with prefix $prefix comes after a trapLine for the same switch which is still being parsed. Keeping it in trapList_queued"
                                );
                                push @tmpTrapList_queued,
                                    $trapList_queued[$i];
                            } else {
                                $logger->trace(
                                    "retrieved parsed trapLine $trapLine with prefix $prefix from trapList_queued. Adding it to candidatesForThreadList_queued"
                                );
                                push @candidatesForThreadList_queued,
                                    $trapLine;
                            }
                        }
                    }
                }
                $i++;
            }
            while ( $i < scalar(@trapList_queued) ) {
                push @tmpTrapList_queued, $trapList_queued[$i];
                $i++;
            }
            @trapList_queued = @tmpTrapList_queued;
            $logger->trace(
                "after intelligent removal of parsed traps trapList_queued contains\n"
                    . join( "\n", @trapList_queued )
                    . "\n" );
            if ( scalar(@candidatesForThreadList_queued) > 0 ) {
                $lockLogger->trace(
                    "locking - trying to lock threadList_queued in signalHandlerTrapListQueued"
                );
                {
                    lock @threadList_queued;
                    $lockLogger->trace(
                        "locking - obtained lock on threadList_queued in signalHandlerTrapListQueued"
                    );
                    foreach my $trapLine (@candidatesForThreadList_queued) {
                        $logger->debug(
                            "validating if parsed trapLine $trapLine should be added to threadList_queued"
                        );
                        my ( $switch_ip, $switch_port, $trapType, $trapVlan,
                            $trapOperation, $trapMac )
                            = split( /\|/, $trapLine );
                        my $switch = $switchFactory->instantiate($switch_ip);
                        if (!$switch) {
                            $logger->error("Can not instantiate switch $switch_ip !");
                            next;
                        }
                        if (( $trapType eq 'secureMacAddrViolation' )
                            && (grep( { /\Q$switch_ip|$switch_port|$trapType|\E/ } @threadList_queued ) != 0) ) {
                            $logger->info(
                                "$trapType trap already in the queue for $switch_ip ifIndex $switch_port. Won't add another one");

                        # we had some side effects while testing the floating device thing. So we added this. 
                        # this is necessary as long as we have not fixed the ugly sleep(5) patch. See floatingdevice.pm
                        # and pf::SNMP::Cisco::Catalyst_2950
                        } elsif (( $trapType eq 'secureMacAddrViolation' )
                            && (! $switch->isPortSecurityEnabled($switch_port)) ) {
                            $logger->info("$trapType trap on $switch_ip ifIndex $switch_port. Port Security is no " . 
                                          "longer configured on the port. Flush the trap");

#
#Revised Section by Josh Fisk for stealth mode 11032011
#
#                        
#                        } elsif (( $trapType eq 'mac' )
#                                 && (grep( { $_ == $trapVlan } @{ $switch->{_vlans} } ) == 0 ) ) {
#                            $logger->info(
#                                "$trapType trap for VLAN $trapVlan on $switch_ip ifIndex $switch_port. We don't manage this VLAN. Flush the trap"
#                            );
#
#Revised Section by Josh Fisk for stealth mode 11032011
#
#
                        } elsif ( ( $trapType eq 'mac' )
                            && ( $trapVlan ne $switch->getVlan($switch_port) )
                            )
                        {
                            $logger->info(
                                "$trapOperation trap for VLAN $trapVlan on $switch_ip ifIndex $switch_port. This port is no longer in this VLAN. Flush the trap"
                            );
                        } else {
                            push @threadList_queued, $trapLine;
                            $logger->debug(
                                "added trap $trapType at $switch_ip ifindex $switch_port to queued threadList"
                            );

                            traplog_insert( $switch_ip, $switch_port,
                                $trapType );
                            $lockLogger->trace(
                                "locking - sending signal for threadList_queued in signalHanderTrapListQueued"
                            );
                            cond_signal(@threadList_queued);
                        }
                    }
                }
                $lockLogger->trace(
                    "locking - unlocked threadList_queued in signalHandlerTrapListQueued"
                );
            }

            #signal if we still have 'raw' entries in trapList_queued
            if ( grep ( {/^raw\|/} @trapList_queued ) > 0 ) {
                $lockLogger->trace(
                    "locking - sending signal for trapList_queued in signalHanderTrapListQueued"
                );
                cond_signal(@trapList_queued);
            }
        }
        $lockLogger->trace(
            "locking - unlocked trapList_queued in signalHandlerTrapListQueued"
        );
    }
}

# }}}1

# sub parseTrap {{{1
sub parseTrap {
    my ($trapLine) = @_;
    my $logger     = Log::Log4perl->get_logger('pfsetvlan::parsing');
    my $lockLogger = Log::Log4perl->get_logger('pfsetvlan::locking');
    Log::Log4perl::MDC->put( 'tid', threads->self->tid() );

    $logger->debug("parsing trap $trapLine");
    if ( $trapLine =~ /$TRAP_PATTERN/ ) {

        #test if this is a 'normal' trap (i.e. a trap sent from a switch
        #or a local trap (INVERSE-PACKETFENCE-NOTIFICATION-MIB)
        my $switch;
        my $switch_ip;
        my $isLocalTrap = ( ( $1 eq '127.0.0.1' ) || ( $3 =~ /BEGIN VARIABLEBINDINGS \.1\.3\.6\.1\.6\.3\.1\.1\.4\.1\.0 = OID: \.1\.3\.6\.1\.4\.1\.29464\.1/ ) );
        $lockLogger->trace("locking - trying to lock switchFactory_locker in parseTrap");
        if ($isLocalTrap) {
            lock $switchFactory_locker;
            $lockLogger->trace("locking - obtained lock on switchFactory_locker in parseTrap");
            $switch_ip = $2;
            $logger->info("local (127.0.0.1) trap for switch $switch_ip");
            $switch = $switchFactory->instantiate($switch_ip);
        } else {
            lock $switchFactory_locker;
            $lockLogger->trace("locking - obtained lock on switchFactory_locker in parseTrap");
            my %config = %{ $switchFactory->{_config} };
            $switch_ip = $1;
            if ( !exists $config{$switch_ip} ) {
                $logger->warn("We have received a trap from switch $switch_ip. This switch is UNREGISTERED. Flush the trap");
                return;
            }
            if ( $config{$switch_ip}{'mode'} eq 'ignore' ) {
                $logger->info("We have received a trap from switch $switch_ip. This switch is in 'ignore' mode. Flush the trap");
                return;
            }
            $switch = $switchFactory->instantiate($switch_ip);
        }
        $lockLogger->trace(
            "locking - unlocked switchFactory_locker in parseTrap");

        if (!$switch) {
            $logger->error("Can not instantiate switch $switch_ip !");
        } else {
            my $trapHashRef;
            # TODO push this out in pf::SNMP::PacketFence, no?
            if ($isLocalTrap) {
                my $trapLine = $3;
                if ($trapLine =~ /BEGIN\ VARIABLEBINDINGS\                                       # metadata
                    \.1\.3\.6\.1\.6\.3\.1\.1\.4\.1\.0\ =\ OID:\ \.1\.3\.6\.1\.4\.1\.29464\.1\.1  # metadata
                    \|\.1\.3\.6\.1\.2\.1\.2\.2\.1\.1\.(\d+)\ =\ INTEGER:\ \d+                    # ifIndex
                    \|\.1\.3\.6\.1\.2\.1\.2\.2\.1\.1\.\d+\ =\ INTEGER:\ (\d+)                    # connection type
                    /x) {
                    # WARNING: using trap operation for something it is not meant to
                    $trapHashRef = {
                        'trapType'       => 'reAssignVlan',
                        'trapIfIndex'    => $1,
                        'trapOperation'  => $2,
                    };
                } elsif ($trapLine =~ /BEGIN VARIABLEBINDINGS \.1\.3\.6\.1\.6\.3\.1\.1\.4\.1\.0 = OID: \.1\.3\.6\.1\.4\.1\.29464\.1\.1\|\.1\.3\.6\.1\.2\.1\.2\.2\.1\.1\.(\d+) = INTEGER:/) {
                    $trapHashRef = {
                        'trapType'    => 'reAssignVlan',
                        'trapIfIndex' => $1,
                    };

                } elsif ($trapLine =~ /BEGIN\ VARIABLEBINDINGS\                                   # metadata
                    \.1\.3\.6\.1\.6\.3\.1\.1\.4\.1\.0\ =\ OID:\ \.1\.3\.6\.1\.4\.1\.29464\.1\.2   # metadata
                    \|\.1\.3\.6\.1\.4\.1\.29464\.1\.3\ =\ STRING:\ \"(.+)\"                       # mac
                    \|\.1\.3\.6\.1\.4\.1\.29464\.1\.4\ =\ INTEGER:\ (\d+)\                        # connection_type
                    END\ VARIABLEBINDINGS/x) {   
                    # WARNING: using trap operation for something it is not meant to
                    # WARNING: always leave trapIfIndex because the trapLine parser expects it
                    $trapHashRef = {
                        'trapType'      => 'desAssociate',
                        'trapIfIndex'   => 'WIFI',
                        'trapMac'       => $1,
                        'trapOperation' => $2
                    };

                } elsif ($trapLine =~ /BEGIN\ VARIABLEBINDINGS\                                   # metadata
                    \.1\.3\.6\.1\.6\.3\.1\.1\.4\.1\.0\ =\ OID:\ \.1\.3\.6\.1\.4\.1\.29464\.1\.3   # metadata
                    \|\.1\.3\.6\.1\.4\.1\.29464\.1\.3\ =\ STRING:\ \"(.+)\"\                      # mac
                    END\ VARIABLEBINDINGS/x) {   
                    # WARNING: using trap operation for something it is not meant to
                    # WARNING: always leave trapIfIndex because the trapLine parser expects it
                    $trapHashRef = {
                        'trapType'      => 'firewallRequest',
                        'trapIfIndex'   => 'NA',
                        'trapMac'       => $1,
                    };

                } elsif ($trapLine =~ /BEGIN VARIABLEBINDINGS \.1\.3\.6\.1\.6\.3\.1\.1\.4\.1\.0 = OID: \.1\.3\.6\.1\.4\.1\.29464\.1\.2\|\.1\.3\.6\.1\.4\.1\.29464\.1\.3 = STRING: \"(.+)\" END VARIABLEBINDINGS/) {
                    # WARNING: always leave trapIfIndex because the trapLine parser expects it
                    $trapHashRef = {
                        'trapType'      => 'desAssociate',
                        'trapIfIndex'   => 'WIFI',
                        'trapMac'       => $1
                    };
                } elsif ( $trapLine
                    =~ /\.1\.3\.6\.1\.4\.1\.45\.1\.6\.5\.3\.12\.1\.3\.(\d+)\.(\d+) = STRING: "([0-9A-F]{2} [0-9A-F]{2} [0-9A-F]{2} [0-9A-F]{2} [0-9A-F]{2} [0-9A-F]{2})"/
                    )
                {
                    $trapHashRef->{'trapType'}    = 's5SbsViolation';
                    $trapHashRef->{'trapIfIndex'} = ( $1 - 1 ) * 64 + $2;
                    $trapHashRef->{'trapMac'}     = lc($3);
                    $trapHashRef->{'trapMac'} =~ s/ /:/g;
                    $trapHashRef->{'trapVlan'}
                        = $switch->getVlan( $trapHashRef->{'trapIfIndex'} );
                } else {
                    $trapHashRef->{'trapType'} = 'unknown';
                }
            } else {
                $trapHashRef = $switch->parseTrap($3);
            }
            my $trapType = $trapHashRef->{'trapType'};

            # skip unknown traps
            if ( $trapType ne 'unknown' ) {
                my $switch_ip     = $switch->{_ip};
                my $switch_port   = $trapHashRef->{'trapIfIndex'};
                my $trapVlan      = '';
                my $trapOperation = '';
                my $trapMac       = '';

                if ( $trapType eq 'mac' ) {
                    $trapVlan      = $trapHashRef->{'trapVlan'};
                    $trapOperation = $trapHashRef->{'trapOperation'};
                    $trapMac       = $trapHashRef->{'trapMac'};

                    if ( $trapOperation eq 'unknown' ) {
                        $logger->info("ignoring unknown trap: $trapLine");
                        return;
                    }
                } elsif ( $trapType eq 'dot11Deauthentication' ) {
                    $trapMac  = $trapHashRef->{'trapMac'};
                    # WARNING: because trapLine parser expects a switch_port entry, we provide him an irrelevant
                    # one here but no one shall consume it anymore (since 2.0 refactoring)
                    $switch_port = 'WIFI';
                } elsif ( ( $trapType eq 'secureMacAddrViolation' )
                    || ( $trapType eq 'secureDynamicMacAddrViolation' ) )
                {
                    $trapMac  = $trapHashRef->{'trapMac'};
                    $trapVlan = $trapHashRef->{'trapVlan'};

                } elsif ( $trapType eq 'desAssociate' ) {
                    $trapMac       = $trapHashRef->{'trapMac'};
                    $trapOperation = $trapHashRef->{'trapOperation'};

                } elsif ( $trapType eq 'firewallRequest' ) {
                    $trapMac = $trapHashRef->{'trapMac'};

                } elsif ($trapType eq 'reAssignVlan') {
                    $trapOperation = $trapHashRef->{'trapOperation'};
                }

                return "$switch_ip|$switch_port|$trapType|$trapVlan|$trapOperation|$trapMac";
            } else {
                $logger->info("ignoring unknown trap: $trapLine");
            }
        }
    } else {
        $logger->info("ignoring non trap line: $trapLine");
    }
    return;
}

# }}}1

# sub signalHandlerThreadListQueued {{{1
sub signalHandlerThreadListQueued {
    my $logger     = Log::Log4perl->get_logger('pfsetvlan::parsing');
    my $lockLogger = Log::Log4perl->get_logger('pfsetvlan::locking');
    Log::Log4perl::MDC->put( 'tid', threads->self->tid() );

    while (1) {
        my $mustDoSomeThing = 0;
        $lockLogger->trace(
            "locking - trying to lock threadList_queued in signalHandlerThreadListQueued"
        );
        {
            lock @threadList_queued;
            $lockLogger->trace(
                "locking - obtained lock on threadList_queued in signalHandlerThreadListQueued"
            );
            $lockLogger->trace(
                "locking - waiting for signal for threadList_queued in signalHandlerThreadListQueued"
            );
            cond_wait(@threadList_queued);
            $lockLogger->trace(
                "locking - received signal for threadList_queued in signalHandlerThreadListQueued"
            );
            $mustDoSomeThing = ( scalar(@threadList_queued) > 0 );
        }
        $lockLogger->trace(
            "locking - unlocked threadList_queued in signalHandlerThreadListQueued"
        );
        if ($mustDoSomeThing) {
            $logger->debug(
                "new enqueue or dequeue operation happened and threadList_queued is not empty"
            );
            startTrapHandlers();
        }
    }
}

# }}}1

# sub startTrapHandlers {{{1
sub startTrapHandlers {
    my $logger     = Log::Log4perl->get_logger('pfsetvlan::handling');
    my $lockLogger = Log::Log4perl->get_logger('pfsetvlan::locking');
    Log::Log4perl::MDC->put( 'tid', threads->self->tid() );

    my $mustDoSomeThing = 0;
    my $switch_ip;
    my $switch_port;
    my $trapType;
    my $trapVlan;
    my $trapOperation;
    my $trapMac;

    $lockLogger->trace(
        "locking - trying to lock threadList_queued, threadList_running and treadList_toBeKilled in startTrapHandlers"
    );
    {
        lock @threadList_queued;
        lock %threadList_running;
        lock %threadList_toBeKilled;
        $lockLogger->trace(
            "locking - obtained lock on threadList_queued, threadList_running and treadList_toBeKilled in startTrapHandlers"
        );
        my @tmp              = ();
        my $nbThreadsRunning = scalar( keys(%threadList_running) );

        my $threadList_queued_txt = '';
        foreach my $trapLineWithAdditionalInfo (@threadList_queued) {

            #extract information from trap line
            if ($trapLineWithAdditionalInfo =~ /^([^|]+)\|([^|]+)\|([^|]+)\|(.+)$/) {
                $threadList_queued_txt .= "\n$1\t$2\t$3";
            } else {
                $logger->warn("unable to parse trapLine.. here's the line: $trapLineWithAdditionalInfo");
            }
        }
        my $threadList_running_txt = '';
        foreach my $threadList_running_tmp ( keys %threadList_running ) {
            $threadList_running_tmp =~ /^(.+)\|(.+)$/;
            $threadList_running_txt .= "\n$1\t$2";
        }
        $logger->info( "nb of items in queue: "
                . scalar(@threadList_queued)
                . "; nb of threads running: $nbThreadsRunning" );
        $logger->debug("items in queue: $threadList_queued_txt");
        $logger->debug("threads running: $threadList_running_txt");

        foreach my $trapLineWithAdditionalInfo (@threadList_queued) {

            #extract information from trap line
            $trapLineWithAdditionalInfo
                =~ /^(.*)\|(.*)\|(.*)\|(.*)\|(.*)\|(.*)$/;
            my $tmpSwitch_ip     = $1;
            my $tmpSwitch_port   = $2;
            my $tmpTrapType      = $3;
            my $tmpTrapVlan      = $4;
            my $tmpTrapOperation = $5;
            my $tmpTrapMac       = $6;
            if (( !$mustDoSomeThing )
                && (!exists(
                        $threadList_running{"$tmpSwitch_ip|$tmpSwitch_port"}
                    )
                )
                )
            {

                #don't remove the " in the previous line
                #they are absolutely necessary for the code to work
                $switch_ip       = $tmpSwitch_ip;
                $switch_port     = $tmpSwitch_port;
                $trapType        = $tmpTrapType;
                $trapVlan        = $tmpTrapVlan;
                $trapOperation   = $tmpTrapOperation;
                $trapMac         = $tmpTrapMac;
                $mustDoSomeThing = 1;
                $threadList_running{"$switch_ip|$switch_port"} = $trapType;
            } else {
                push @tmp, $trapLineWithAdditionalInfo;

                #check if we have to send a kill signal to another trap
                #(this is the case if we got a 'down' trap and are still
                #handling an 'up' thread for the same switch port)
                if (( $tmpTrapType eq 'down' )
                    && (exists(
                            $threadList_running{
                                "$tmpSwitch_ip|$tmpSwitch_port"}
                        )
                    )
                    && ( $threadList_running{"$tmpSwitch_ip|$tmpSwitch_port"}
                        eq 'up' )
                    && (!exists(
                            $threadList_toBeKilled{
                                "$tmpSwitch_ip|$tmpSwitch_port"}
                        )
                    )
                    )
                {
                    $logger->info(
                        "down after up - sending kill signal to thread for $tmpSwitch_ip ifIndex $tmpSwitch_port"
                    );
                    $threadList_toBeKilled{"$tmpSwitch_ip|$tmpSwitch_port"}
                        = 1;
                }
            }
        }
        @threadList_queued = @tmp;
    }
    $lockLogger->trace(
        "locking  - unlocked threadList_queued, threadList_running and treadList_toBeKilled in startTrapHandlers"
    );
    if ($mustDoSomeThing) {
        $logger->debug("calling handleTrap");
        handleTrap( $switch_ip, $switch_port, $trapType, $trapVlan,
            $trapOperation, $trapMac );
    }
}

# }}}1

# sub handleTrap {{{1
sub handleTrap {

    # initialize {{{2
    my ( $switch_ip, $switch_port, $trapType, $trapVlan, $trapOperation, $trapMac ) = @_;
    my $logger     = Log::Log4perl->get_logger('pfsetvlan::handling');
    my $lockLogger = Log::Log4perl->get_logger('pfsetvlan::locking');
    Log::Log4perl::MDC->put( 'tid', threads->self->tid() );
    my $vlan_obj = new pf::vlan::custom();

    # }}}2

    # test if act on this trap {{{2
    my $switch;
    $lockLogger->trace("locking - trying to lock switchFactory_locker in handleTrap");
    {
        lock $switchFactory_locker;
        $lockLogger->trace("locking - obtained lock on switchFactory_locker in handleTrap");
        $switch = $switchFactory->instantiate($switch_ip);
    }
    $lockLogger->trace("locking - unlocked switchFactory_locker in handleTrap");

    if (!$switch) {
        $logger->error("Can not instantiate switch $switch_ip !");
        cleanupAfterThread( $switch_ip, $switch_port );
        return;
    }

    # do we actually act on this trap ?
    if ( defined($trapMac) && $switch->isFakeMac($trapMac) ) {
        $logger->info("MAC $trapMac is a fake MAC. Stop $trapType handling");
        cleanupAfterThread( $switch_ip, $switch_port );
        $switch->disconnectRead();
        $switch->disconnectWrite();
        return;
    }
    my $weActOnThisTrap = $vlan_obj->doWeActOnThisTrap($switch, $switch_port, $trapType);

    if ( $weActOnThisTrap == 0 ) {
        $logger->info("doWeActOnThisTrap returns false. Stop $trapType handling");
        cleanupAfterThread( $switch_ip, $switch_port );
        $switch->disconnectRead();
        $switch->disconnectWrite();
        return;
    }

    # }}}2

    # trapType eq 'mac' {{{2
    if ( $trapType eq 'mac' ) {

        # initialize {{{3
        my $mac  = lc($trapMac);
        my $vlan = $trapVlan;
        $logger->info("$trapOperation trap received on $switch_ip ifIndex $switch_port for $mac in VLAN $vlan");

        # }}}3

        # test if port is still in current VLAN {{{3
        if ( $vlan ne $switch->getVlan($switch_port) ) {
            $logger->info(
                "$switch_ip ifIndex $switch_port is no longer in this VLAN -> Do nothing"
            );
            cleanupAfterThread( $switch_ip, $switch_port );
            $switch->disconnectRead();
            $switch->disconnectWrite();
            return;
        }

        # }}}3

        # node_updatePF {{{3
        my $isPhone = $switch->isPhoneAtIfIndex( $mac, $switch_port );
        node_update_PF( $switch, $switch_port, $mac, $vlan, $isPhone,
            $switch->isRegistrationMode() );

        # }}}3

        # trapOperation eq 'removed' {{{3
        if ( $trapOperation eq 'removed' ) {
            locationlog_update_end_mac($mac);

            #do nothing if it's a phone
            if ($isPhone) {
                $logger->info("MAC $mac is a VoIP phone -> Do nothing");
            } else {

                #do we have an open entry in locationlog for switch/port ?
                my @locationlog
                    = locationlog_view_open_switchport_no_VoIP( $switch_ip,
                    $switch_port );
                if (   (@locationlog)
                    && ( scalar(@locationlog) > 0 )
                    && ( defined($locationlog[0]->{'mac'}) )
                    && ( $locationlog[0]->{'mac'} ne '' ) )
                {
                    if ($switch->isMacInAddressTableAtIfIndex(
                            $mac, $switch_port
                        )
                        )
                    {
                        $logger->info( "Removed trap for MAC $mac: MAC "
                                . $locationlog[0]->{'mac'}
                                . " is still present in mac-address-table; has probably already been relearned -> DO NOTHING"
                        );
                    } else {
                        $logger->info( "Removed trap for MAC $mac: MAC "
                                . $locationlog[0]->{'mac'}
                                . " DEAD -> setting data VLAN on $switch_ip ifIndex $switch_port to MAC detection VLAN"
                        );
#
#Revised Section by Josh Fisk for stealth mode 11032011
#
#                        $switch->setMacDetectionVlan( $switch_port,
#                            \%switch_locker, 0 );
#      
#
#			
#
#Revised Section by Josh Fisk for stealth mode 11032011
#
                    }
                } else {

                    #no open entry in locationlog for switch/port
                    $logger->info(
                        "no line opened for MAC $mac in locationlog.");

 #try to determine if nothing is left on switch/port (VoIP phones dont' count)
                    my $nothingLeftOnSwitchPort = 0;
                    my @macArray = $switch->_getMacAtIfIndex($switch_port);
                    if ( !@macArray ) {
                        $nothingLeftOnSwitchPort = 1;
                    } elsif ( scalar(@macArray) == 1 ) {
                        my $onlyMacLeft = $macArray[0];
                        $logger->debug("only MAC found is $onlyMacLeft");
                        if ($switch->isPhoneAtIfIndex( $onlyMacLeft,
                                $switch_port )
                            )
                        {
                            $nothingLeftOnSwitchPort = 1;
                        }
                    } else {
                        $logger->debug( scalar(@macArray) . " MACs found." );
                    }

                    if ( $nothingLeftOnSwitchPort == 1 ) {
                        $logger->info(
#
#Revised Section by Josh Fisk for stealth mode 11032011
#                            "setting data VLAN on $switch_ip ifIndex $switch_port to MAC detection VLAN"
			"Removing Active Location Log for MAC $mac on $switch_ip ifIndex $switch_port"
                        );
#
#
#                        $switch->setMacDetectionVlan( $switch_port,
#                            \%switch_locker, 0 );
#
#Revised Section by Josh Fisk for stealth mode 11032011
#
#
                    } else {
                        $logger->info( "no line in locationlog and MACs ("
                                . join( ",", @macArray )
                                . ") still present on this port -> Do nothing"
                        );
                    }
                }
            }

            # }}}3

            # trapOperation eq 'learnt' {{{3
        } elsif ( $trapOperation eq 'learnt' ) {

            # port security handling {{{4
            do_port_security( $mac, $switch, $switch_port, $trapType );

            # }}}4

            #do nothing if it's a phone
            if ($isPhone) {
                $logger->info(
                    "MAC $mac is a VoIP phone -> Do nothing besides updating locationlog"
                );
                locationlog_synchronize($switch_ip, $switch_port, 
                    $switch->getVoiceVlan($switch_port), $mac, $VOIP, $WIRED_SNMP_TRAPS);
            } else {

                my $changeVlan = 0;

                #do we have an open entry in locationlog for switch/port ?
                my @locationlog
                    = locationlog_view_open_switchport_no_VoIP( $switch_ip,
                    $switch_port );
                if (   (@locationlog)
                    && ( scalar(@locationlog) > 0 )
                    && ( defined( $locationlog[0]->{'mac'} ) )
                    && ( $locationlog[0]->{'mac'} ne '' ) )
                {
                    if ( $locationlog[0]->{'mac'} =~ /^$mac$/i ) {
                        if (( $locationlog[0]->{'vlan'} == $vlan )
                            && ($vlan == $vlan_obj->fetchVlanForNode($mac, $switch, $switch_port, $WIRED_SNMP_TRAPS))
                           ) {
                            $logger->info(
                                "locationlog is already up2date. Do nothing");
                        } else {
                            $changeVlan = 1;
                        }
                    } else {

                        $logger->info(
                                  "Learnt trap received for $mac. Old MAC "
                                . $locationlog[0]->{'mac'}
                                . " already connected to the port according to locationlog !"
                        );
                        $changeVlan = 1;
                    }
                } else {
                    $changeVlan = 1;
                }

                if ( $changeVlan == 1 ) {
#
#Revised Section by Josh Fisk for stealth mode 11032011
#
#                        node_determine_and_set_into_VLAN( $mac, $switch, $switch_port, $WIRED_SNMP_TRAPS );
#                            
#
			locationlog_synchronize($switch_ip, $switch_port, $trapVlan, $trapMac, $NO_VOIP, $WIRED_SNMP_TRAPS);
#
#Revised Section by Josh Fisk for stealth mode 11032011
#   
                }
            }
        }

        # }}}2

        # trapType eq 'secureMacAddrViolation' (with STATIC MACs) {{{2
    } elsif ( $trapType eq 'secureMacAddrViolation' ) {
        $logger->info(
            "$trapType trap received on $switch_ip ifIndex $switch_port for $trapMac"
        );

        # floating network devices handling {{{3
        if (exists($ConfigFloatingDevices{$trapMac})) {
            $logger->info("The floating network device $trapMac has just plugged into $switch_ip  port $switch_port. Enabling floating network device configuration on the port.");
            my $floatingDeviceManager = new pf::floatingdevice::custom();

            my $result = $floatingDeviceManager->enablePortConfig($trapMac, $switch, $switch_port, \%switch_locker);
            if (! $result) {
                $logger->info("An error occured while enabling floating network device configuration on port $switch_port. It may not work!");
            }

            cleanupAfterThread( $switch_ip, $switch_port );
            $switch->disconnectRead();
            $switch->disconnectWrite();
            return;
        }

        # generic port security handling {{{3
        my $secureMacAddrHashRef;
        if (do_port_security(
                lc($trapMac), $switch, $switch_port, $trapType
            ) eq 'stopTrapHandling'
            )
        {
            $logger->info(
                "MAC $trapMac is already authorized on $switch_ip ifIndex $switch_port. Stopping secureMacAddrViolation trap handling here"
            );
            cleanupAfterThread( $switch_ip, $switch_port );
            $switch->disconnectRead();
            $switch->disconnectWrite();
            return;
        }

        # node_update_PF {{{3
        my $isPhone = $switch->isPhoneAtIfIndex( $trapMac, $switch_port );
        node_update_PF( $switch, $switch_port, $trapMac, $trapVlan, $isPhone,
            $switch->isRegistrationMode() );

        #}}}3

  # synchronize locationlog with secure MAC addresses found on switchport {{{3
        my $locationlog_phone
            = locationlog_view_open_switchport_only_VoIP( $switch_ip,
            $switch_port );
        my @locationlog_pc
            = locationlog_view_open_switchport_no_VoIP( $switch_ip,
            $switch_port );

        # }}}3

# close locationlog entries for MACs which are not present any more on the switch as secure MACs {{{3
        $secureMacAddrHashRef = $switch->getSecureMacAddresses($switch_port);
        if (defined($locationlog_phone)
            && (!exists(
                    $secureMacAddrHashRef->{ $locationlog_phone->{'mac'} }
                )
            )
            )
        {
            # TODO: not so sure about this behavior
            $logger->debug( $locationlog_phone->{'mac'}
                    . " (VoIP phone) has open locationlog entry at $switch_ip ifIndex $switch_port but is not a secure MAC address. Closing locationlog entry"
            );
            locationlog_update_end_switchport_only_VoIP( $switch_ip,
                $switch_port );
            $locationlog_phone = undef;
        }
        if (   (@locationlog_pc)
            && ( scalar(@locationlog_pc) > 0 )
            && ( defined( $locationlog_pc[0]->{'mac'} ) )
            && (!exists(
                    $secureMacAddrHashRef->{ $locationlog_pc[0]->{'mac'} }
                )
            )
            )
        {
            $logger->debug( $locationlog_pc[0]->{'mac'}
                    . " has open locationlog entry at $switch_ip ifIndex $switch_port but is not a secure MAC address. Closing locationlog entry"
            );
            locationlog_update_end_mac( $locationlog_pc[0]->{'mac'} );
            @locationlog_pc = ();
        }

        # }}}3

        # if trap came from a VoIP phone {{{3
        if ($isPhone) {
            my $voiceVlan = $switch->getVoiceVlan($switch_port);
            $logger->debug("$trapType trap comes from VoIP $trapMac");

            #is another VoIP phone authorized here ?
            if ( defined($locationlog_phone) ) {
                my $oldVoIPPhone = $locationlog_phone->{'mac'};
                $logger->debug(
                    "VoIP $oldVoIPPhone has still open locationlog entry at $switch_ip ifIndex $switch_port"
                );
                if ( exists( $secureMacAddrHashRef->{$oldVoIPPhone} ) ) {
                    $logger->info(
                        "de-authorizing VoIP $oldVoIPPhone at old location $switch_ip ifIndex $switch_port VLAN $voiceVlan"
                    );
                    $switch->authorizeMAC( $switch_port, $oldVoIPPhone, 0,
                        $voiceVlan, 0 );
                }
                $logger->debug(
                    "closing VoIP $oldVoIPPhone locationlog entry at $switch_ip ifIndex $switch_port VLAN $voiceVlan"
                );
                locationlog_update_end_switchport_only_VoIP( $switch_ip,
                    $switch_port );
            }

            #authorize MAC
            my $secureMacAddrHashRef
                = $switch->getSecureMacAddresses($switch_port);
            my $old_mac_to_remove = undef;
            foreach my $old_mac ( keys %$secureMacAddrHashRef ) {
                my $old_isPhone
                    = $switch->isPhoneAtIfIndex( $old_mac, $switch_port );
                if ((   grep( { $_ == $voiceVlan }
                            @{ $secureMacAddrHashRef->{$old_mac} } ) >= 1
                    )
                    || $old_isPhone
                    )
                {
                    $old_mac_to_remove = $old_mac;
                }
            }
            if ( defined($old_mac_to_remove) ) {
                $logger->info(
                    "authorizing VoIP $trapMac (old entry $old_mac_to_remove) at new location $switch_ip ifIndex $switch_port VLAN $voiceVlan"
                );
                $switch->authorizeMAC( $switch_port, $old_mac_to_remove,
                    $trapMac, $voiceVlan, $voiceVlan );
            } else {
                $logger->info(
                    "authorizing VoIP $trapMac at new location $switch_ip ifIndex $switch_port VLAN $voiceVlan"
                );
                $switch->authorizeMAC( $switch_port, 0, $trapMac, 0,
                    $voiceVlan );
            }

            locationlog_synchronize($switch_ip, $switch_port, $voiceVlan, $trapMac, $VOIP, $WIRED_SNMP_TRAPS);

            # }}}3

            # if trap came from a PC {{{3
        } else {
            $logger->debug("$trapType trap comes from PC $trapMac");
            if (   (@locationlog_pc)
                && ( defined( $locationlog_pc[0]->{'mac'} ) ) )
            {
                my $oldPC = $locationlog_pc[0]->{'mac'};
                $logger->debug("$oldPC has still open locationlog entry at $switch_ip ifIndex $switch_port. Closing it");
                locationlog_update_end_mac($oldPC);
                $logger->info("authorizing $trapMac (old entry $oldPC) at new location $switch_ip ifIndex $switch_port");
                my $correctVlanForThisNode = $vlan_obj->fetchVlanForNode(
                    $trapMac, $switch, $switch_port, $WIRED_SNMP_TRAPS
                );
                $switch->authorizeMAC( $switch_port, $oldPC, $trapMac, $switch->getVlan($switch_port), $correctVlanForThisNode );

                #set the right VLAN
                $logger->debug("setting correct VLAN for $trapMac at new location $switch_ip ifIndex $switch_port");
                $switch->setVlan( $switch_port, $correctVlanForThisNode, \%switch_locker, $trapMac );
            } else {

                #authorize MAC
                my $secureMacAddrHashRef
                    = $switch->getSecureMacAddresses($switch_port);
                my $voiceVlan         = $switch->getVoiceVlan($switch_port);
                my $old_mac_to_remove = undef;
                foreach my $old_mac ( keys %$secureMacAddrHashRef ) {
                    my $old_isPhone
                        = $switch->isPhoneAtIfIndex( $old_mac, $switch_port );
                    if ((   grep( { $_ == $voiceVlan }
                                @{ $secureMacAddrHashRef->{$old_mac} } ) == 0
                        )
                        && ( !$old_isPhone )
                        )
                    {
                        $old_mac_to_remove = $old_mac;
                    }
                }
                my $correctVlanForThisNode = $vlan_obj->fetchVlanForNode(
                    $trapMac, $switch, $switch_port, $WIRED_SNMP_TRAPS
                );
                if ( defined($old_mac_to_remove) ) {
                    $logger->info(
                        "authorizing $trapMac (old entry $old_mac_to_remove) at new location $switch_ip ifIndex $switch_port"
                    );
                    $switch->authorizeMAC( $switch_port, $old_mac_to_remove,
                        $trapMac, $switch->getVlan($switch_port),
                        $correctVlanForThisNode );
                } else {
                    $logger->info(
                        "authorizing $trapMac at new location $switch_ip ifIndex $switch_port"
                    );
                    $switch->authorizeMAC( $switch_port, 0, $trapMac, 0,
                        $correctVlanForThisNode );
                }

                #set the right VLAN
                $logger->debug(
                    "setting correct VLAN for $trapMac at new location $switch_ip ifIndex $switch_port"
                );
                $switch->setVlan( $switch_port, $correctVlanForThisNode,
                    \%switch_locker, $trapMac );
            }
        }

        # }}}3
        # }}}2

        # trapType eq 'secureDynamicMacAddrViolation' {{{2
    } elsif ( $trapType eq 'secureDynamicMacAddrViolation' ) {

        $logger->warn(
            "ERROR: secureDynamicMacAddrViolation traps are currently not supported by PacketFence. Please check our website frequently regarding upgrades !"
        );
        cleanupAfterThread( $switch_ip, $switch_port );
        $switch->disconnectRead();
        $switch->disconnectWrite();
        return;

        # }}}2

        # trapType eq 'down' {{{2
    } elsif ( $trapType eq 'down' ) {
        $logger->info("$trapType trap received on $switch_ip ifIndex $switch_port");

        # continue only if security traps are not available on this port {{{3
        if ( $switch->isPortSecurityEnabled($switch_port) ) {
            $logger->info("security traps are configured on this switch port. Stopping DOWN trap handling here");
            cleanupAfterThread( $switch_ip, $switch_port );
            $switch->disconnectRead();
            $switch->disconnectWrite();
            return;
        }

        # }}}3

        # floating network devices handling {{{3
        # if the last device pluggoed in that port is a floating network device then we handle it
        my @locationlog_switchport = locationlog_view_open_switchport_no_VoIP($switch_ip, $switch_port);
        my $valid_locationlog_entry = (@locationlog_switchport && ref($locationlog_switchport[0]) eq 'HASH');
        if ($valid_locationlog_entry && (exists($ConfigFloatingDevices{$locationlog_switchport[0]->{mac}}))) {
            my $mac = $locationlog_switchport[0]->{mac};
            $logger->info("The floating network device $mac has just unplugged from $switch_ip port $switch_port. " . 
                          "Disabling floating network device configuration on the port.");
            my $floatingDeviceManager = new pf::floatingdevice::custom();

            my $result = $floatingDeviceManager->disablePortConfig($mac, $switch, $switch_port, \%switch_locker);
            if (!$result) {
                $logger->info("An error occured while disabling floating network device configuration on port " .
                              "$switch_port. The port may not work!");
            }
            cleanupAfterThread( $switch_ip, $switch_port );
            $switch->disconnectRead();
            $switch->disconnectWrite();
            return;
        }
        # }}}3


        # set into MAC detection VLAN {{{3
#        
#
#Revised Section by Josh Fisk for stealth mode 11032011
#
			$logger->info("Sealth Mode - Not setting $switch_ip port $switch_port to MAC detection VLAN");
#
#			$logger->info("setting $switch_ip port $switch_port to MAC detection VLAN");
#
#                        $switch->setMacDetectionVlan( $switch_port, \%switch_locker, 1 );
#      
#
#			locationlog_synchronize($switch_ip, $switch_port, $trapVlan, $trapMac, $NO_VOIP, $WIRED_SNMP_TRAPS);
#
#Revised Section by Josh Fisk for stealth mode 11032011
#

        # }}}3
        # }}}2

        # trapType eq 'up' {{{2
    } elsif ( $trapType eq 'up' ) {
        $logger->info(
            "$trapType trap received on $switch_ip ifIndex $switch_port");

        # continue only if security traps are not available on this port {{{3
        if ( $switch->isPortSecurityEnabled($switch_port) ) {
            $logger->info(
                "security traps are configured on this switch port. Stopping UP trap handling here"
            );
            cleanupAfterThread( $switch_ip, $switch_port );
            $switch->disconnectRead();
            $switch->disconnectWrite();
            return;
        }

        # }}}3

        # floating network devices handling {{{3
        # if the last device pluggoed in that port is a floating network device then we handle it
        my @locationlog_switchport = locationlog_view_open_switchport_no_VoIP($switch_ip, $switch_port);
        my $valid_locationlog_entry = (@locationlog_switchport && ref($locationlog_switchport[0]) eq 'HASH');
        if ($valid_locationlog_entry && (exists($ConfigFloatingDevices{$locationlog_switchport[0]->{mac}}))) {
            $logger->info("The logs shows that the last device pluged was a floating network device. We may have missed"
                          . "the LinkDown trap. Disabling floating network device configuration on the port.");
            my $floatingDeviceManager = new pf::floatingdevice::custom();

            # shut the port down
            $logger->debug("Shuting down port $switch_port");
            if (! $switch->setAdminStatus( $switch_port, $SNMP::DOWN )) {
                $logger->error("An error occured while shuting down port $switch_port. The port may not work!");
            }

            my $result = $floatingDeviceManager->disablePortConfig(
                $locationlog_switchport[0]->{mac}, $switch, $switch_port, \%switch_locker);

            if (!$result) {
                $logger->error("An error occured while disabling floating network device configuration on port " .
                               " $switch_port. The port may not work!");
            }

            # open the port            
            $logger->debug("Re-opening port $switch_port");
            if (! $switch->setAdminStatus( $switch_port, $SNMP::UP )) {
                $logger->error("An error occured while opening port $switch_port. The port may not work!");
            }
 
            cleanupAfterThread( $switch_ip, $switch_port );
            $switch->disconnectRead();
            $switch->disconnectWrite(); 
            return;
        }   
        # }}}3  

        # set into MAC detection VLAN {{{3
#        
#
#Revised Section by Josh Fisk for stealth mode 11032011
#
			$logger->info("Sealth Mode - Not setting $switch_ip port $switch_port to MAC detection VLAN");
#
#			$logger->info("setting $switch_ip port $switch_port to MAC detection VLAN");
#
#                        $switch->setMacDetectionVlan( $switch_port, \%switch_locker, 1 );
#      
#
#			locationlog_synchronize($switch_ip, $switch_port, $trapVlan, $trapMac, $NO_VOIP, $WIRED_SNMP_TRAPS);
#
#Revised Section by Josh Fisk for stealth mode 11032011
#

        # }}}3

       # continue only if MAC learnt traps are not available on this port {{{3
        if ( $switch->isLearntTrapsEnabled($switch_port) ) {
            $logger->info(
                "MAC learnt traps are configured on this switch port. Stopping UP trap handling here"
            );
            cleanupAfterThread( $switch_ip, $switch_port );
            $switch->disconnectRead();
            $switch->disconnectWrite();
            return;
        }

        # }}}3

        # try to determine MAC address(es) {{{3
        my $nbAttempts = 0;
        my $start      = time;
        my @macArray   = ();
        my $secureMacAddrHashRef;
        do {
            sleep( $switch->{_macSearchesSleepInterval} )
                unless ( $nbAttempts == 0 );
            my $mustRunCleanupAfterThread = 0;
            $lockLogger->trace(
                "locking - trying to lock threadLists_toBeKilled in handleTrap"
            );
            {
                lock %threadList_toBeKilled;
                $lockLogger->trace(
                    "locking - obtained lock on threadLists_toBeKilled in handleTrap"
                );
                if (exists(
                        $threadList_toBeKilled{"$switch_ip|$switch_port"}
                    )
                    )
                {
                    $logger->info(
                        "received kill signal for thread at $switch_ip ifIndex $switch_port"
                    );
                    delete $threadList_toBeKilled{"$switch_ip|$switch_port"};
                    $mustRunCleanupAfterThread = 1;
                }
            }
            $lockLogger->trace(
                "locking - unlocked threadLists_toBeKilled in handleTrap");
            if ( $mustRunCleanupAfterThread == 1 ) {
                cleanupAfterThread( $switch_ip, $switch_port );
                $switch->disconnectRead();
                $switch->disconnectWrite();
                return;
            }
            $logger->debug( "attempt " . ($nbAttempts + 1) . " to obtain MAC at $switch_ip ifIndex $switch_port" );
            @macArray = $switch->_getMacAtIfIndex($switch_port);
            $nbAttempts++;
            # TODO constantify the 120 seconds
            } while (($nbAttempts < $switch->{_macSearchesMaxNb}) && ((time-$start) < 120) && (scalar(@macArray) == 0));

            if (scalar(@macArray) == 0) {
                if ($nbAttempts >= $switch->{_macSearchesMaxNb}) {
                    $logger->warn("Tried to grab MAC address at ifIndex $switch_port "
                        ."on switch ".$switch->{_ip}." ".$switch->{_macSearchesMaxNb}." times and failed");
                } else {
                    $logger->warn("Tried to grab MAC address at ifIndex $switch_port "
                        ."on switch ".$switch->{_ip}." for 2 minutes and failed");
                }
            }

        # }}}3

        # node_update_PF {{{3
        my @tmpMacArray = ();
        if ( scalar(@macArray) > 0 ) {

            #remove VoIP phones from list

            foreach my $currentMac (@macArray) {
                if ( $switch->isPhoneAtIfIndex( $currentMac, $switch_port ) )
                {

                    #this Mac is a phone
                    $logger->debug("$currentMac is a phone");
                    node_update_PF( $switch, $switch_port, $currentMac, '', $TRUE,
                        $switch->isRegistrationMode() );
                } else {
                    push( @tmpMacArray, $currentMac );
                    node_update_PF( $switch, $switch_port, $currentMac, '', $FALSE,
                        $switch->isRegistrationMode() );
                }
            }
        }
        @macArray = @tmpMacArray;

        # }}}3

        # number of MACs found > 1 {{{3
        if ( scalar(@macArray) > 1 ) {
            $logger->info("several MACs found. Do nothing");

            # }}}3

            # number of MACs found == 1 {{{3
        } elsif ( scalar(@macArray) == 1 ) {

            my $mac = lc( $macArray[0] );

            # port security handling {{{4
            do_port_security( $mac, $switch, $switch_port, $trapType );

            # }}}4

            # node_determine_and_set_into_VLAN {{{4
#
#Revised Section by Josh Fisk for stealth mode 11032011
#
#                        node_determine_and_set_into_VLAN( $mac, $switch, $switch_port, $WIRED_SNMP_TRAPS );
#                            
#
			locationlog_synchronize($switch_ip, $switch_port, $trapVlan, $trapMac, $NO_VOIP, $WIRED_SNMP_TRAPS);
#
#Revised Section by Josh Fisk for stealth mode 11032011
# 

            # }}}4
            # }}}3

            # number of MACs found == 0 {{{3
        } else {
            $logger->info(
                "cannot find MAC (maybe we found a VoIP, but they don't count here). Do nothing"
            );
        }

        # }}}3
        # }}}2

        # trapType eq 'desAssociate' {{{2
    } elsif ( $trapType eq 'desAssociate' ) {
        my $connection_type = $trapOperation; # WARNING: I used trapOperation to carry the connection type
        $logger->info("$trapType trap received on $switch_ip for wireless client $trapMac");
        #$switch->deauthenticateMac($trapMac);
        my $switchIp = $switch->{_ip};
        if ($connection_type == $WIRELESS_802_1X) {
            # we spawn a shell to workaround a thread safety bug in Net::Appliance::Session when using SSH transport
            # http://www.cpanforum.com/threads/6909
            pf_run("/usr/local/pf/bin/pfcmd_vlan -deauthenticateDot1x -switch $switchIp -mac $trapMac");
        } else {
            # we spawn a shell to workaround a thread safety bug in Net::Appliance::Session when using SSH transport
            # http://www.cpanforum.com/threads/6909
            pf_run("/usr/local/pf/bin/pfcmd_vlan -deauthenticate -switch $switchIp -mac $trapMac");
        }

        # }}}2

        # trapType eq 'reAssignVlan' {{{2
    } elsif ( $trapType eq 'reAssignVlan' ) {
        my $connection_type = $trapOperation; # WARNING: I used trapOperation to carry the connection type
        $logger->info("$trapType trap received on $switch_ip ifIndex $switch_port");

        if (defined($connection_type) && $connection_type == $WIRED_802_1X) {
            # we spawn a shell to workaround a thread safety bug in Net::Appliance::Session when using SSH transport
            # http://www.cpanforum.com/threads/6909
            $logger->info("Forcing 802.1x re-authentication on $switch_ip:$switch_port. A new VLAN will be assigned.");
            pf_run("/usr/local/pf/bin/pfcmd_vlan -deauthenticateDot1x -switch $switch_ip -ifIndex $switch_port");

        } elsif (defined($connection_type) && $connection_type == $WIRED_MAC_AUTH) {
            $switch->handleReAssignVlanTrapForWiredMacAuth($switch_port);

        } else {

            my @locationlog = locationlog_view_open_switchport_no_VoIP( $switch_ip, $switch_port );
            if ((@locationlog) && ( scalar(@locationlog) > 0 ) && ( $locationlog[0]->{'mac'} ne '' )) {

                my $mac = $locationlog[0]->{'mac'};
    
                if ( $switch->isPortSecurityEnabled($switch_port) ) {
                    $logger->info( "security traps are configured on " . $switch->{_ip}
                        . " ifIndex $switch_port. Re-assigning VLAN for $mac"
                    );

                    my $hasPhone = $switch->hasPhoneAtIfIndex($switch_port);
                    node_determine_and_set_into_VLAN( $mac, $switch, $switch_port, $connection_type );

                    # TODO extract that behavior in a method call in pf::vlan so it can be overridden easily
                    if ( !$hasPhone ) {
                        $logger->info(
                            "no VoIP phone is currently connected at " . $switch->{_ip} . " ifIndex $switch_port. " .
                            "Flipping port admin status"
                        );
                        $switch->bouncePort( $switch_port );

                    } else {
                        my @violations = violation_view_open_desc($mac);
                        if ( scalar(@violations) > 0 ) {
                            my %message;
                            $message{'subject'} = "VLAN isolation of $mac behind VoIP phone";
                            $message{'message'} = "The following computer has been isolated behind a VoIP phone\n";
                            $message{'message'} .= "MAC: $mac\n";

                            my $node_info = node_view($mac);
                            $message{'message'} .= "Owner: " . $node_info->{'pid'} . "\n";
                            $message{'message'} .= "Computer Name: " . $node_info->{'computername'} . "\n";
                            $message{'message'} .= "Notes: " . $node_info->{'notes'} . "\n";
                            $message{'message'} .= "Switch: " . $switch->{_ip} . "\n";
                            $message{'message'} .= "Port (ifIndex): " . $switch_port . "\n\n";
                            $message{'message'} .= "The violation details are\n";
    
                            foreach my $violation (@violations) {
                                $message{'message'} .= "Description: " . $violation->{'description'} . "\n";
                                $message{'message'} .= "Start: " . $violation->{'start_date'} . "\n";
                            }
                            $logger->info("sending email to admin regarding isolation of $mac behind VoIP phone");
                            pfmailer(%message);
                        }
                    }

    
    #
#Revised Section by Josh Fisk for stealth mode 11032011
#
#                } else {
#                    $logger->info(
#                        "no security traps are configured on " . $switch->{_ip} . " ifIndex $switch_port. " . 
#                        "Flipping port admin status"
#                    );
#                    $switch->bouncePort( $switch_port );
#                }
#
                } else {
                    $logger->info(
                        "Stealth Mode - no security traps are configured on " . $switch->{_ip} . " ifIndex $switch_port. " . 
                        "Flipping port admin status"
                    );
                }
#
#
#Revised Section by Josh Fisk for stealth mode 11032011
# 
            } else {
                $logger->warn(
                    "received reAssignVlan trap on $switch_ip ifIndex $switch_port but can't determine non VoIP MAC"
                );
            }
        }

        # }}}2

        # trapType eq 'dot11Deauthentication' {{{2
    } elsif ( $trapType eq 'dot11Deauthentication' ) {
        my $mac = $trapMac;
        $logger->info("$trapType trap received on $switch_ip for wireless client $mac. closing locationlog entry");

        # we close the line opened for the mac. If there is no line, this won't do anything
        locationlog_update_end_mac( $mac );

        # }}}2

        # trapType eq 'desAssociate' {{{2
    } elsif ( $trapType eq 'firewallRequest' ) {
        $logger->info("$trapType trap received for inline client: $trapMac. Modifying firewall.");

        # verify if firewall rule is ok
        my $inline = new pf::inline::custom();
        $inline->performInlineEnforcement($trapMac);

        # }}}2
    } 

    # cleanupAfterThread {{{2
    cleanupAfterThread( $switch_ip, $switch_port );
    $switch->disconnectRead();
    $switch->disconnectWrite();

    # }}}2
}

# }}}1

# sub cleanupAfterThread {{{1
sub cleanupAfterThread {
    my $logger     = Log::Log4perl->get_logger('pfsetvlan::cleanup');
    my $lockLogger = Log::Log4perl->get_logger('pfsetvlan::locking');
    Log::Log4perl::MDC->put( 'tid', threads->self->tid() );
    my ( $switch_ip, $switch_port ) = @_;
    $lockLogger->trace(
        "locking - trying to lock threadList_running, threadList_toBeKilled in cleanupAfterThread"
    );
    {
        lock %threadList_running;
        lock %threadList_toBeKilled;
        $lockLogger->trace(
            "locking - obtained lock on threadList_running, threadList_toBeKilled in cleanupAfterThread"
        );
        delete $threadList_running{"$switch_ip|$switch_port"};

        if ( exists( $threadList_toBeKilled{"$switch_ip|$switch_port"} ) ) {
            $logger->debug(
                "destroyed kill signal for thread at $switch_ip ifIndex $switch_port in cleanupAfterThread"
            );
            delete $threadList_toBeKilled{"$switch_ip|$switch_port"};
        }
    }
    $lockLogger->trace(
        "locking - unlocked threadList_running, threadList_toBeKilled in cleanupAfterThread"
    );
    $lockLogger->trace(
        "locking - trying to lock threadList_queued in cleanupAfterThread");
    {
        lock @threadList_queued;
        $lockLogger->trace(
            "locking - obtained lock on threadList_queued in cleanupAfterThread"
        );
        $lockLogger->trace(
            "locking - sending signal for threadList_queued in cleanupAfterThread"
        );
        cond_signal(@threadList_queued);
    }
    $lockLogger->trace(
        "locking - unlocked threadList_queued in cleanupAfterThread");
    $logger->info("finished");
}

# }}}1

# sub normal_sighandler {{{1
sub normal_sighandler {
    my $logger     = Log::Log4perl->get_logger('pfsetvlan');
    my $lockLogger = Log::Log4perl->get_logger('pfsetvlan::locking');
    Log::Log4perl::MDC->put( 'tid', threads->self->tid() );
    if (   ( isenabled( $Config{'vlan'}{'closelocationlogonstop'} ) )
        && ( threads->self->tid() == 0 ) )
    {

        $logger->debug( "caught SIG" . $_[0] . " - closing open locationlogs" );
        locationlog_close_all();
    }
    $logger->logcroak("pfsetvlan: caught SIG" . $_[0] . " - terminating");
}

# alarm signals are known to be buggy with threaded perl, to fix we create a handler that does nothing
# ref: http://rt.perl.org/rt3/Public/Bug/Display.html?id=16807
# bug: http://www.packetfence.org/mantis/view.php?id=907
sub ignore_sighandler {
    my $logger     = Log::Log4perl->get_logger('pfsetvlan');

    $logger->error("caught SIG" . $_[0] . ". This is probably a telnet or ssh management attempt that timed out. "
        ."THIS WILL HANG A PACKETFENCE THREAD FOR SEVERAL MINUTES! Doublecheck your config or the network between "
        ."packetfence and the problematic device. Look for Can't connect messages in the logs to find the culprit.");
}

# }}}1

# sub node_update_PF {{{1
sub node_update_PF {
    my ($switch, $switch_port, $mac, $vlan, $isPhone, $registrationMode) = @_;
    my $logger = Log::Log4perl->get_logger('pfsetvlan');
    Log::Log4perl::MDC->put( 'tid', threads->self->tid() );
    my $vlan_obj = new pf::vlan::custom();

    #lowercase MAC
    $mac = lc($mac);

    if ( $switch->isFakeMac($mac) ) {
        $logger->info("MAC $mac is fake. Stopping node_update_PF");
        return 0;
    }

    #add node if necessary
    if ( !node_exist($mac) ) {
        $logger->info(
            "node $mac does not yet exist in PF database. Adding it now");
        node_add_simple($mac);
    }

    # There is activity from that mac, call node wakeup
    node_mac_wakeup($mac);

    #should we auto-register?
    if ($vlan_obj->shouldAutoRegister($mac, $registrationMode, 0, $isPhone, $WIRED_SNMP_TRAPS)) {
        # auto-register
        my %autoreg_node_defaults = $vlan_obj->getNodeInfoForAutoReg($switch->{_ip}, $switch_port, 
            $mac, $vlan, $registrationMode, 0, $isPhone, $WIRED_SNMP_TRAPS);
        $logger->debug("auto-registering node $mac");
        if (!node_register($mac, $autoreg_node_defaults{'pid'}, %autoreg_node_defaults)) {
            $logger->error("auto-registration of node $mac failed");
            return 0;
        }
    }
    return 1;
}

# }}}1

# sub node_determine_and_set_into_VLAN {{{1
sub node_determine_and_set_into_VLAN {
    my ( $mac, $switch, $ifIndex, $connection_type ) = @_;

    my $logger = Log::Log4perl->get_logger('pfsetvlan::handling');
    Log::Log4perl::MDC->put( 'tid', threads->self->tid() );

    my $vlan_obj = new pf::vlan::custom();

    $switch->setVlan(
        $ifIndex,
        $vlan_obj->fetchVlanForNode($mac, $switch, $ifIndex, $connection_type),
        \%switch_locker,
        $mac
    );
}

# }}}1

# sub do_port_security {{{1
sub do_port_security {
    my ( $mac, $switch, $switch_port, $trapType ) = @_;
    my $logger     = Log::Log4perl->get_logger('pfsetvlan::handling');
    my $lockLogger = Log::Log4perl->get_logger('pfsetvlan::locking');
    Log::Log4perl::MDC->put( 'tid', threads->self->tid() );

    #determine if $mac is authorized elsewhere
    my $locationlog_mac = locationlog_view_open_mac($mac);
    if ( defined($locationlog_mac) && 
         ( exists($switchFactory->{_config}{$locationlog_mac->{'switch'}}) )
       ) {
        my $old_switch = $locationlog_mac->{'switch'};
        my $old_port   = $locationlog_mac->{'port'};
        my $old_vlan   = $locationlog_mac->{'vlan'};
        my $is_old_voip = is_node_voip($mac);

    #we have to enter to 'if' always when trapType eq 'secureMacAddrViolation'
        if (   ( $old_switch ne $switch->{_ip} )
            || ( $old_port != $switch_port )
            || ( $trapType eq 'secureMacAddrViolation' ) )
        {
            my $oldSwitch;
            $logger->debug(
                "$mac has still open locationlog entry at $old_switch ifIndex $old_port"
            );
            if ( $old_switch eq $switch->{_ip} ) {
                $oldSwitch = $switch;
            } else {
                $lockLogger->trace("locking - trying to lock switchFactory_locker in $trapType handling");
                {
                    lock $switchFactory_locker;
                    $lockLogger->trace("locking - obtained lock on switchFactory_locker in $trapType handling");
                    $oldSwitch = $switchFactory->instantiate($old_switch);
                }
                $lockLogger->trace("locking - unlocked switchFactory_locker in $trapType handling");
            }

            if (!$oldSwitch) {
                $logger->error("Can not instantiate switch $old_switch !");
            } else {
                $logger->info("Will try to check on this node's previous switch if secured entry needs to be removed. ".
                    "Old Switch IP: $old_switch");
                my $secureMacAddrHashRef = $oldSwitch->getSecureMacAddresses($old_port);
                if ( exists( $secureMacAddrHashRef->{$mac} ) ) {
                    if (   ( $old_switch eq $switch->{_ip} )
                        && ( $old_port == $switch_port )
                        && ( $trapType eq 'secureMacAddrViolation' ) )
                    {   
                        return 'stopTrapHandling';
                    }
                    my $fakeMac = $oldSwitch->generateFakeMac( $is_old_voip, $old_port );
                    $logger->info("de-authorizing $mac (new entry $fakeMac) at old location $old_switch ifIndex $old_port");
                    $oldSwitch->authorizeMAC( $old_port, $mac, $fakeMac,
                        ( $is_old_voip ? $oldSwitch->getVoiceVlan($old_port) : $oldSwitch->getVlan($old_port) ),
                        ( $is_old_voip ? $oldSwitch->getVoiceVlan($old_port) : $oldSwitch->getVlan($old_port) ) );
                } else {
                    $logger->info("MAC not found on node's previous switch secure table or switch inaccessible.");
                }
                locationlog_update_end_mac($mac);
            }
        }
    }

    # check if $mac is not already secured on another port (in case locationlog is outdated)
    my $secureMacAddrHashRef = $switch->getAllSecureMacAddresses();
    if ( exists( $secureMacAddrHashRef->{$mac} ) ) {
        foreach my $ifIndex ( keys( %{ $secureMacAddrHashRef->{$mac} } ) ) {
            if ( $ifIndex == $switch_port ) {
                return 'stopTrapHandling';
            } else {
                foreach my $vlan (
                    @{ $secureMacAddrHashRef->{$mac}->{$ifIndex} } )
                {
                    my $is_voice_vlan = ($vlan == $switch->getVoiceVlan($ifIndex));
                    my $fakeMac = $switch->generateFakeMac($is_voice_vlan, $ifIndex);
                    $logger->info( "$mac is a secure MAC address at "
                            . $switch->{_ip}
                            . " ifIndex $ifIndex VLAN $vlan. De-authorizing (new entry $fakeMac)"
                    );
                    $switch->authorizeMAC( $ifIndex, $mac, $fakeMac, $vlan,
                        $vlan );
                }
            }
        }
    }
    return 1;
}

# }}}1

END {
    if ( ( !$man ) && ( !$help ) ) {
        $logger->info("stopping pfsetvlan");
        deletepid();
        foreach my $t (@completeThreadList) {
            $t->detach;
        }
        kill 6, -$$;
    }
}

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