fPaste.me

A free, anonymous, minimalist and open source paste tool.


Untitled
21-05-2021 23:28:45
Anonymous
#!/usr/bin/env perl

use strict;
use warnings;
use MikroTik::API;
use Time::HiRes qw(time);
use Data::Dump::Color;
use Getopt::Long;

###############################################################################
###############################################################################

my $host = "192.168.5.1";
my $user = "user";
my $pass = "*******************";
my $ssl = 0;

my $block;
my $unblock;
my $file;

GetOptions(
'block' => \$block,
'unblock' => \$unblock,
'file=s' => \$file,
);

my $api = MikroTik::API->new({
host => $host,
username => $user,
password => $pass,
use_ssl => $ssl,
});

my @macs = get_macs($file);
my $ok;

if ($block) {
foreach my $mac (@macs) {
$ok = add_block($mac);

if (!defined($ok)) {
print "$mac is already blocked\n";
} elsif (!$ok) {
print "Error blocking $mac\n";
}
}
} elsif ($unblock) {
foreach my $mac (@macs) {
$ok = remove_block($mac);

if (!defined($ok)) {
print "Error removing $mac\n";
} elsif (!$ok) {
print "$mac not found\n";
}
}
} else {
my @macs = get_blocked_mac();

if (@macs) {
print join("\n", @macs);
print "\n";
} else {
print "No blocked MAC addresses found\n";
}
}

$api->logout();

###############################################################################
###############################################################################

sub add_block {
my $mac = uc(shift());

my @blocked = get_blocked_mac($mac);
foreach (@blocked) {
if ($mac eq $_) {
return undef;
}
}

my $cmd = "/ip/firewall/filter/add";
my $params = {
'chain' => 'forward',
'action' => 'drop',
'src-mac-address' => $mac,
};

my ( $x, @reg ) = $api->query( $cmd, $params, {} );

# Anything less than 2 is "good"
my $ok = $x < 2;

return $ok;
}

sub remove_block {
my $mac = uc(shift());

my $ok = 0;
my $cmd = "/ip/firewall/filter/print";
my ( $x, @reg ) = $api->query( $cmd, {}, {} );

foreach my $i (@reg) {
my $chain = $i->{chain};
my $action = $i->{action};
my $smac = $i->{'src-mac-address'} // "";
my $id = $i->{'.id'};

if ($chain eq 'forward' && $action eq 'drop' && ($mac eq $smac)) {
$ok = remove_firewall_rule($id);
}
}

return $ok;
}

sub remove_firewall_rule {
my $id = shift();

#dd("Removing $id");

my $cmd = "/ip/firewall/filter/remove";
my ( $x, @reg ) = $api->query( $cmd, { '.id' => $id} );

#k($x, \@reg);

# Anything less than 2 is "good"
my $ok = $x < 2;

return $ok;
}

sub get_blocked_mac {
my $cmd = "/ip/firewall/filter/print";

my ( $x, @reg ) = $api->query( $cmd, {}, {} );
my @ret;

foreach my $i (@reg) {
my $chain = $i->{chain};
my $action = $i->{action};
my $mac = $i->{'src-mac-address'};
my $id = $i->{'.id'};

if ($chain eq 'forward' && $action eq 'drop' && $mac) {
push(@ret, $mac);
}
}

return @ret;
}

sub mikrotik_str_to_time {
my $str = shift();

if (!$str) {
return 0;
}

my ($days) = $str =~ /(\d+)d/;
my ($hours) = $str =~ /(\d+)h/;
my ($mins) = $str =~ /(\d+)m/;
my ($secs) = $str =~ /(\d+)s/;

$days //= 0;
$hours //= 0;
$mins //= 0;
$secs //= 0;

my $ret = 0;
$ret += ($days * 86400);
$ret += ($hours * 3600);
$ret += ($mins * 60);
$ret += ($secs);

#dd($days, $hours, $mins, $secs, $ret);

return $ret;
};

my $oui_cache = {};
sub oui_lookup {
my $prefix = uc(shift() || "");
$prefix =~ s/://g;
$prefix = substr($prefix,0,6);

#print "Looking up $prefix\n";

my $size = keys(%$oui_cache);
if (!$size) {
my $c = populate_cache();
}

my $ret = $oui_cache->{$prefix} // "";
return $ret;
}

sub populate_cache {
my $start = time();

my $file = "/home/bakers/oui.txt";
if (!-r $file) {
my $red = color('red');
my $reset = color('reset');

print $red;
print "Error loading OUI Cache $file\n\n";
print $reset;

return undef;
}
open(my $fh, "<", $file);

my $count = 0;
while (<$fh>) {
my @p = split(/\t/,$_);
my $mac = $p[0];
$mac =~ s/://g;
if (length($mac) != 6) {
next;
}

my $name = $p[2] || $p[1];
$name = trim($name);

$oui_cache->{$mac} = $name;
$count++;
}

close $fh;

my $total = sprintf("%0.2f", time() - $start);

#print "Loaded $count cache items in $total seconds\n";

return $count;
}

sub argv {
my $ret = {};

for (my $i = 0; $i < scalar(@ARGV); $i++) {
# If the item starts with "-" it's a key
if ((my ($key) = $ARGV[$i] =~ /^--?([a-zA-Z_]\w*)/) && ($ARGV[$i] !~ /^-\w\w/)) {
# If the next item does not start with "--" it's the value for this item
if (defined($ARGV[$i + 1]) && ($ARGV[$i + 1] !~ /^--?\D/)) {
$ret->{$key} = $ARGV[$i + 1];
# Bareword like --verbose with no options
} else {
$ret->{$key}++;
}
}
}

# We're looking for a certain item
if ($_[0]) { return $ret->{$_[0]}; }

return $ret;
}

sub trim {
if (wantarray) {
my @ret;
foreach (@_) {
push(@ret,scalar(trim($_)));
}

return @ret;
} else {
my $s = shift();
if (!defined($s) || length($s) == 0) { return ""; }
$s =~ s/^\s*//;
$s =~ s/\s*$//;

return $s;
}
}

# Debug print variable using either Data::Dump::Color (preferred) or Data::Dumper
# Creates methods k() and kd() to print, and print & die respectively
BEGIN {
if (eval { require Data::Dump::Color }) {
*k = sub { Data::Dump::Color::dd(@_) };
} else {
require Data::Dumper;
*k = sub { print Data::Dumper::Dumper(\@_) };
}

sub kd {
k(@_);

printf("Died at %2\$s line #%3\$s\n",caller());
exit(15);
}
}

# String format: '115', '165_bold', '10_on_140', 'reset', 'on_173'
sub color {
my $str = shift();

# If we're NOT connected to a an interactive terminal don't do color
if (-t STDOUT == 0) { return ''; }

# No string sent in, so we just reset
if (!length($str) || $str eq 'reset') { return "\e[0m"; }

# Some predefined colors
my %color_map = qw(red 160 blue 21 green 34 yellow 226 orange 214 purple 93 white 15 black 0);
$str =~ s|([A-Za-z]+)|$color_map{$1} // $1|eg;

# Get foreground/background and any commands
my ($fc,$cmd) = $str =~ /(\d+)?_?(\w+)?/g;
my ($bc) = $str =~ /on_?(\d+)/g;

# Some predefined commands
my %cmd_map = qw(bold 1 italic 3 underline 4 blink 5 inverse 7);
my $cmd_num = $cmd_map{$cmd // 0};

my $ret = '';
if ($cmd_num) { $ret .= "\e[${cmd_num}m"; }
if (defined($fc)) { $ret .= "\e[38;5;${fc}m"; }
if (defined($bc)) { $ret .= "\e[48;5;${bc}m"; }

return $ret;
}

sub known_mac {
my $mac = shift();
$mac = trim(uc($mac));

my $db = {
'2C:0E:3D:0F:E3:21' => "Dad's Phone",
'90:97:F3:97:87:CA' => "Dad's Tablet",
'80:7D:3A:75:EB:89' => "Dad's NodeMCU",
'98:B6:E9:23:AD:86' => "Nintendo Switch",
'0C:51:01:B9:D5:5E' => "Gabe's iPad",
'2C:0E:3D:33:0B:85' => "Mom's Phone",
'98:F1:70:53:A0:68' => "Gabe's Phone",
'80:1F:02:CC:4B:31' => "Roland's OSMC",
};

my $ret = $db->{$mac} || undef;

return $ret;
}

sub get_macs {
my $file = shift();
my @ret;

# Get the macs from the TSV
if ($file) {
my $str = pfile($file);
my %x = split(/\t+|\n/, $str);

@ret = keys(%x);
# Get the MACs from the ARGV
} else {
@ret = @ARGV;
}

my @final;

# Validate that everything is a valid MAC
foreach my $x (@ret) {
if ($x =~ /^([0-9A-Fa-f]{2}[:]){5}([0-9A-Fa-f]{2})$/) {
push(@final, $x);
} else {
print "Skipping $x as it's not a valid MAC address\n";
}
}

# Remove any duplicate entries
@final = array_unique(@final);

return @final;
}

sub pfile {
my $file = shift();
if (!-r $file) { return ''; } # Make sure the file is readable

my $ret = '';

open(INPUT, "<", $file);
while (<INPUT>) {
$ret .= $_;
}
close INPUT;

if (wantarray) {
return split(/\n/,$ret);
} else {
return $ret;
}
}

sub array_unique {
my %seen;
grep !$seen{$_}++, @_;
}

# vim: tabstop=4 shiftwidth=4 autoindent softtabstop=4