#!/usr/bin/perl -w

# Copyright (c) 2008 Rainer Clasen
# 
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# 
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

use strict;

# ... process options manually to avoid interfering with iptables options

my $table = 'filter';
if( scalar @ARGV and $ARGV[0] eq '-t' ){
	shift; $table = shift || '';
}

my $needhelp;
my $cmd = shift || '';


# chains
if( $cmd =~ /^c(?:h(?:a(?:i(?:n(?:s)?)?)?)?)?$/ ){
	&chain_dump( $table, \&do_names );

# (r)list/show <chain_pat>
} elsif( $cmd =~/^(r)?(?:l(?:i(?:s(?:t)?)?)?|s(?:h(?:o(?:w)?)?)?)$/ ){
	my $asrep = defined $1;
	my $chain = shift || '\S+';
	my $ipt = 'ipt'. ( $table eq 'filter' ? '' : " -t $table" );
	&chain_dump( $table, sub { &do_rules( $ipt, $chain, $asrep, @_) } );

# new <chain>
} elsif( $cmd =~/^n(?:e(?:w)?)?$/ ){
	my $chain = shift;
	iptables( $table, '-N', $chain )
		or exit 1;

# imply <chain> <target>
} elsif( $cmd =~/^i(?:m(?:p(?:l(?:y)?)?)?)?$/ ){
	my $chain = shift;
	my $target = shift;
	iptables( $table, '-P', $chain, $target )
		or exit 1;

# drop <chain>
} elsif( $cmd =~/^dr(?:o(?:p)?)?$/ ){
	my $chain = shift;
	iptables( $table, '-F', $chain )
		or exit 1;
	iptables( $table, '-X', $chain )
		or exit 1;

# add <chain> [<n>]
} elsif( $cmd =~/^a(?:d(?:d)?)?$/ ){
	my $chain = shift;

	if( $ARGV[0] =~ /^\d+$/ ){
		my $row = shift;
		iptables( $table, '-I', $chain, $row, @ARGV )
			or exit 1;
	} else {
		iptables( $table, '-A', $chain, @ARGV )
			or exit 1;
	}

# replace <chain> <n>
} elsif( $cmd =~/^r(?:e(?:p(?:l(?:a(?:c(?:e)?)?)?)?)?)?$/ ){
	my $chain = shift;
	my $row = shift || '';
	unless( $row =~ /^\d+$/ ){
		print STDERR "expecting rule number to replace\n";
		exit 1;
	}
	iptables( $table, '-R', $chain, $row, @ARGV )
		or exit 1;

# delete <chain> <n>
} elsif( $cmd =~/^d(?:e(?:l(?:e(?:t(?:e)?)?)?)?)?$/ ){
	my $chain = shift;
	my $row = shift || '';
	unless( $row =~ /^\d+$/ ){
		print STDERR "expecting rule number to replace\n";
		exit 1;
	}
	iptables( $table, '-D', $chain, $row )
		or exit 1;

# TODO: } elsif( $cmd eq 'save|load|flush' ){ 
# TODO: } elsif( $cmd eq 'edit' ){ 
	
} elsif( $cmd =~ /^help$/ ){
	print <<EOH;
usage: $0 <opt> <cmd> ...
frontend for iptables with copy-paste friendly output

options:
 -t <table>	ipchains table to work on (filter|mangle|nat|....)

commands
 help		this cruft
 chains		list all available chain names
 list <chain>	list rules of specified chain (iptables -L)
 rlist <chain>	list rules of specified chain as replace (iptables -L)
 new <chain>	create new chain (iptables -N)
 imply <chain> <target>
		set chain's default target (iptables -P)
 add <chain> [<id>] <iptables-args>
 		adds rule before line <id> (or at end when missing)
 replace <chain> <id> <iptables-args>
 		replaces rule
 delete <chain> <id>
 		deletes rule
 drop <chain>	drops chain
EOH
	exit 0;

} else {
	print STDERR "unknown command\n";
	$needhelp++;
}

if( $needhelp ){
	print STDERR "use $0 help for usage info\n";
	exit 1;
}


sub iptables {
	my $table = shift;
	system( 'iptables', '-t', $table, @_ ) == 0;
}

sub chain_dump {
	my $table = shift;
	my $do = shift;

	open(IPT, '-|', "iptables-save", '-t', $table )
		or die "cannot fork iptables-save";

	my %row;

	my( $intable );
	while(<IPT>){
		chomp;
		s/#.*//;
		next if /^\s*$/;

		if( /^\*(.*)/ ){
			$intable = $1;

		} elsif( /^COMMIT$/ ){

		} elsif( $table eq $intable ){
			&$do( $_, \%row );

		}
	}
	close(IPT);
}

sub do_rules {
	my $ipt = shift;
	my $chain = shift;
	my $asrep = shift;

	my $l = shift;
	my $row = shift;

	if( $l =~ /^:($chain)\s+(\S+)\s+/ ){
		if( ! $asrep ){
			print "n=0 ";
		}
		if( $2 eq "-" ){
			print "$ipt new $1\n";
		} else {
			print "$ipt imp $1 $2\n";
		}

	} elsif(  $l =~ /^-A\s+($chain)\s+(.*)/ ){
		my $r = ++$row->{$1};
		if( $asrep ){
			print "$ipt rep $1 $r $2\n";
		} else {
			print "n=$r $ipt add $1 $2\n";
		}
	}
}

sub do_names {
	my $l = shift;

	if( $l =~ /^:(\S+)\s+/ ){
		print $1,"\n";
	}
}


