/blip

Simple interface for iptables to block IP addresses

Primary LanguagePerl

#!/usr/local/bin/perl -w
use strict;

=head1 PROGRAM

C<blip.pl> - Blocks and unblocks IPs using C<iptables>.

=head2 Usage Examples

Using the Unix iptables can be confusing, blip is a simple interface 
that un/blocks IPs and put them in a designated chain.

 ./blip.pl -block 114.80.97.88
 ./blip.pl -unblock 114.80.97.88

You can pass to it multiple IPs by separating them with commas.

List the blocked IPs:

 ./blip.pl -list

When you're tired of it all:

 ./blip.pl -wipe

=head2 Options

=head3 -verbose <level>

Turns on vebosity.

=head3 -bin <path-to-iptables>

Defaults to C</sbin/iptables> if it's not there, set it yourself.

=head3 -chain <chain>

Chain or target to add IPs to. Defaults to C<BLIP>.

=cut

use vars qw/$BLIP/;

use constant HELP => qq{blip.pl v1.1 - Block IP
 ./blip.pl -[un]block <ip-address> -verbose
 ./blip.pl -[wipe|list]
};

die HELP unless scalar @ARGV;

$BLIP = BlockIP->new(@ARGV);
$BLIP->commands;

exit (0);

package BlockIP;

use constant {
	IPTABLES => '/sbin/iptables',
	CHAIN    => 'BLIP'
};

=head1 NAME

C<BlockIP.pm> - Simple interface for C<iptables>.

=head1 SYNOPSIS

 $BLIP = BlockIP->new(@ARGV);
 $BLIP->commands;

=head1 DESCRIPTION

Blocks and unblocks IPs to a special chain called C<BLIP>.

=head2 Public Methods

=head3 new

Creates the object, subsequently, creates the C<CHAIN> if doesn't exist.

=cut

sub new {
	my $class = shift;
	my $self = {};

	for (my $i=0;$i<=$#_;$i++) {
		if (substr($_[$i],0,1) eq '-') {
			$self->{$_[$i]} = 1;
		} else {
			$self->{$_[$i-1]} = $_[$i];
		}
	}

	bless $self, $class;

	$self->_init;

	return $self;
}

=head3 commands (I<void>)

Accepts these command-line args:

 -verbose = 2 levels of outputs
 -list    = shows current table
 -wipe    = remove chain
 -block   = block this IP
 -unblock = unblock this IP

=cut

sub commands {
	my $self = shift;
	$self->{-data} or die "ABORT: Cannot find chain BLIP in iptables\n";

	if ($self->{-list}) {
		printf "Blocked IPs:\n%s\n", join "\n", $self->_get_ips;
	} elsif ($self->{-unblock}) {
		my @ips = split ',', $self->{-unblock};
		map { $self->unblock($_) } @ips;
	} elsif ($self->{-block}) {
		my @ips = split ',', $self->{-block};
		map { $self->block($_) } @ips;
	} elsif ($self->{-wipe}) {
		my $cmd = sprintf "%s -D INPUT -j %s",
			$self->{-bin}, $self->{-chain};
	
		$self->_execute($cmd) and
			die "ERROR: unchain $cmd\n";
		$cmd = sprintf "%s -X %s",
			$self->{-bin}, $self->{-chain};

		$self->_printf(1,"Removing chain %s\n", $self->{-chain});
		map { $self->unblock($_) } $self->_get_ips;
		
		$self->_execute($cmd) and
			die "ERROR: Cannot wipe $cmd\n";
	}
}

=head3 block (I<ip>)

Blocks an IP if not already blacklisted.

=cut

sub block {
	my ($self,$ip) = (@_);

	if ($self->{-data} =~ /$self->{-block}/) {
		return print "ERROR: $ip already blocked\n";
	}
	my $cmd = sprintf "%s -I %s -s %s -j DROP",
		$self->{-bin}, $self->{-chain}, $ip;

	$self->_printf(1,"Blocking %s\n", $ip);

	$self->_execute($cmd) and
		die "ERROR: Cannot block $ip\n";
}

=head3 unblock (I<ip>)

Unblocks an IP in the C<chain>.

=cut

sub unblock {
	my ($self,$ip) = (@_);

	my $cmd = sprintf "%s -D %s -s %s -j DROP",
		$self->{-bin}, $self->{-chain}, $ip;

	$self->_printf(1,"Unblocking %s\n", $ip);

	$self->_execute($cmd) and
		die "ERROR: Cannot unblock $ip\n";
}

=head2 Private Methods

=head3 _init (I<void>)

Prepares C<iptables> to accept un/block of IPs.

=cut

sub _init {
	my $self = shift;

	$self->{-bin}     ||= IPTABLES;
	$self->{-chain}   ||= CHAIN;
	$self->{-verbose} ||= 0;

	my $cmd = sprintf "%s -L %s -n", $self->{-bin}, $self->{-chain};

	select STDOUT; $| = 1;
	local $/ = undef;

	my $info = $self->_execute($cmd);

	$info =~ /denied/i
		and die "ABORT: Need to be root to do this\n";

	if ($info =~ /no chain/i) {
		$self->_create_chain;
		$info = $self->_execute($cmd);
	}

	if ($info =~ /^target/m) {
		$self->{-data} = $info;
	}
}

sub _get_ips {
	my $self = shift;
	return () unless $self->{-data};

	my @list = ();
	while ($self->{-data} =~ m/--\s+(\d+\.\d+\.\d+\.\d+)/g) {
		push @list, $1;
	}
	return @list;
}

=head3 _create_chain (I<void>)

Creates a new C<chain> and chain this to C<INPUT>.

=cut

sub _create_chain {
	my $self = shift;
	my $cmd = sprintf "%s -N %s", $self->{-bin}, $self->{-chain};

	$self->_printf(1, "$cmd\n");

	my $info = $self->_execute($cmd);
	$info and die "ABORT: $cmd failed ($info)\n";

	$cmd = sprintf "%s -A INPUT -j %s", 
		$self->{-bin}, $self->{-chain};

	$info = $self->_execute($cmd);
	$info and die "ABORT: $cmd failed ($info)\n";
}

=head3 _execute (I<command>,[I<reg-pattern>])

Execute command, if passed a regexp, it'll try to match it with the output
returns true/false otherwise, return entire output for parsing.

=cut

sub _execute {
	my ($self,$cmd,$re) = (@_);
	
	$self->_printf(2, "CMD: $cmd\n");

	open CMD, "$cmd 2>&1 |" or die "ABORT: Cannot execute $cmd\n";
	my $info = <CMD>;
	close CMD;

	$self->_printf(2, "%sOUTPUT%s\n%s\n%s\n\n",
		'-'x20,'-'x50,
		$info||'<empty>',
		'-'x76);

	$re and return $info =~ $re;

	return $info;
}

=head3 _printf (I<level>,I<format>,I<@array>)

Works just like C<printf> except has a built-in check for C<-verbose>.

=cut

sub _printf {
	my $self = shift;
	my $level = shift;

	return 0 if $self->{-verbose} < $level;

	return printf @_;
}

=head1 HISTORY

20120314 - v1.0 - Created. 

v1.0.1 - Remove C<README>. 
Link C<README.pod> to C<blip.pl>.
Change C<blip> to C<blip.pl>.
Adds C<-chain> option so user chan specify which C<chain/target> to use.

v1.1 - Add chaining to C<INPUT> for this thing to actually do the blocking. Oops.

=head1 AUTHOR

This module by Paul Pham.

=head1 COPYRIGHT AND LICENSE

Copyright 2012 by Paul Pham

This program and library is free software;
you can redistribute it and/or modify it under the same terms as Perl itself.

=cut

1;