#! /usr/bin/perl

# Clean JunOS config
# Author: Pavel Gulchouck <gul@gul.kiev.ua>
# Version 0.1, 25.02.2011
# Free software

$debug = 0;

while ($ARGV[0] =~ /^-/) {
	if ($ARGV[0] eq "-v") {
		$debug++;
	} elsif ($ARGV[0] eq "-1") {
		$onepass = 1;
	} elsif ($ARGV[0] eq "-d") {
		$onlydyn = 1;
	} elsif ($ARGV[0] eq "-h") {
		print "Clean JunOs config, remove unused tokens\n";
		print "  Usage:\n";
		print "cleanconf [<options>] [<config> [<dyn-config>]]\n";
		print "  Options:\n";
		print "    -v  - increase verbose level,\n";
		print "    -d  - remove only from dynamic config,\n";
		print "    -1  - single pass,\n";
		print "    -h  - print this help and exit.\n";
		exit(0);
	} else {
		printf STDERR "Unknown switch '$ARGV[0]' ignored\n";
	}
	shift;
}

if ($ARGV[0]) {
	parsefile($ARGV[0], ($onlydyn && $ARGV[1]) ? 1 : 0);
	if ($ARGV[1]) {
		$dyn = 1;
		parsefile($ARGV[1]);
	}
} else {
	parsefile("-");
}

sub debug
{
	my ($level, $line) = @_;

	return if $debug < $level;
	print STDERR "line $linenum: $line\n";
}

sub parsefile
{
	my ($fname, $notdelete) = @_;
	my ($out);
	open(F, "<$fname") || die "Cannot open $fname: $!\n";
	%plist = %policy = %aspath = %comm = ();
	%aspathdep = %commdep = %plistdep = $policydep = ();
	$linenum = 0;
	while (<F>) {
		$linenum++;
		if (/^\s+(?:inactive:\s+)?prefix-list (\S+) \{\s*$/) {
			$plist{$1} = 0 unless $plist{$1};
			$inplist = $1;
			$inplistlevel = 1;
		}
		elsif (/^    prefix-list (\S+);\s*/ && !$inpol) {
			$plist{$1} = 0 unless $plist{$1};
			$inplistlevel = 0;
		}
		elsif (/^\s+(?:inactive:\s+)?policy-statement (\S+) \{\s*$/) {
			$policy{$1} = 0 unless $policy{$1};
			$inpol = $1;
			$inplevel = 1;
		}
		elsif (/^\s+(?:inactive:\s+)?as-path (\S+) (.*);\s*$/) {
			$aspath{$1} = 0 unless $aspath{$1};
			$aspathdyn{$1} = 1 if $2 eq "dynamic-db";
		}
		elsif (/^\s+(?:inactive:\s+)?community (\S+) (members (.*)|\{);\s*$/) {
			$comm{$1} = 0 unless $comm{$1};
		}
		elsif (/^\s+(?:inactive:\s+)?community (\S+) dynamic-db;\s*$/) {
			$comm{$1} = 0 unless $comm{$1};
			$commdyn{$1} = 1;
		}
		elsif (/^\s+(?:from\s+)?policy (\S+);\s*$/) {
			if ($inpol) {
				$policydep{$1}->{$inpol} = 1;
			} else {
				$policy{$1} = 1;
				debug(1, "Cannot parse deps for policy-statement $1");
			}
		}
		elsif (/^\s+(?:from\s+)?prefix-list (\S+);\s*$/ ||
		    /^\s+(?:from\s+)?prefix-list-filter (\S+)(\s+(\S+))*;\s*$/) {
			if ($inpol) {
				$plistdep{$1}->{$inpol}=1;
			} else {
				$plist{$1} = 1;
				debug(1, "Cannot parse deps for prefix-list $1");
			}
		}
		elsif (/^\s+(?:inactive:\s+)?(?:(?:instance-)?(?:import|export)|import-policy)\s+(?:(\S+)|\[ (.*) \]);\s*$/) {
			$p = ($1 || $2);
			debug(2, "found import/export for $p");
			foreach (split(/\s+/, $p)) {
				$policy{$_} = 1;
			}
		}
		elsif (/^\s+(?:from\s+)?as-path (?:(\S+)|\[ (.*) \]);\s*$/) {
			$p = $1;
			foreach (split(/\s+/, $p)) {
				if ($inpol) {
					$aspathdep{$_}->{$inpol} = 1;
				} else {
					$aspath{$_} = 1;
					debug(1, "Cannot parse deps for as-path $_");
				}
			}
		}
		elsif (/^\s+(?:from\s+)?community (?:(\S+)|\[ (.*) \]);\s*$/) {
			$p = $1;
			foreach (split(/\s+/, $p)) {
				if ($inpol) {
					$commdep{$_}->{$inpol} = 1;
				} else {
					$comm{$_} = 1;
					debug(1, "Cannot parse deps for community $_");
				}
			}
		}
		elsif (/^\s+(?:inactive:\s+)?community (?:add|delete|set) (?:(\S+)|\[ (.*) \]);\s*$/) {
			$p = $1;
			foreach (split(/\s+/, $p)) {
				if ($inpol) {
					$commdep{$_}->{$inpol} = 1;
				} else {
					$comm{$_} = 1;
					debug(1, "Cannot parse deps for community $_");
				}
			}
		}
		elsif (/^\s+(?:inactive:\s+)?policer (\S+) \{\s*$/) {
			$policer{$1} = 0 unless $policer{$1};
			$infamilylevel++ if $infamily;
		}
		elsif (/^\s+(?:inactive:\s+)?filter (\S+) \{\s*$/) {
			if ($infamily) {
				$infilter = "$infamily:$1";
				$infamilylevel++;
			} else {
				$infilter = $1;
			}
			$filter{$infilter} = 0 unless $filter{$infilter};
			$infilterlevel = 1;
		}
		elsif (/^\s+(?:inactive:\s+)?filter (\S+);\s*$/) {
			$filter = ($infamily ? "$infamily:$1" : $1);
			if ($infilter) {
				$filterdep{$filter}->{$infilter} = 0;
				$filterdep{$1}->{$infilter} = 0 if $infamily eq "inet";
			} else {
				$filter{$filter} = 1;
				$filter{$1} = 1 if $infamily eq "inet";
				debug(1, "Cannot parse deps for filter $1");
			}
		}
		elsif (/^\s+(?:inactive:\s+)?family (\S+) \{\s*$/) {
			$infamily = $1;
			$infamilylevel = 1;
		}
		elsif (/^\s+(?:then )?(?:inactive:\s+)?policer (\S+);$/) {
			if ($infilter) {
				$policerdep{$1}->{$infilter} = 1;
			} else {
				$policer{$1} = 1;
				debug(1, "Cannot parse deps filter for policer $1");
			}
		}
		elsif (/\s+(?:inactive:\s+)?(?:input|output|arp) (\S+);\s*$/) {
			if ($inintpolicer) {
				$policer{$1} = 1;
			} elsif ($inintfilter) {
				$filter = ($infamily ? "$infamily:$1" : "$1");
				$filter{$1} = 1 if $infamily eq "inet";
				$filter{$filter} = 1;
			} else {
				debug(1, "Cannot parse inheritance: $1");
			}
		}
		elsif (/\s+(?:inactive:\s+)?fail-filter (\S+);\s*$/) {
			$filter = ($infamily ? "$infamily:$1" : "$1");
			$filter{$1} = 1 if $infamily eq "inet";
			$filter{$filter} = 1;
		}

		elsif (/^\s+(?:inactive:\s+)?filter \{\s*$/ && $infamily) {
			$inintfilter = 1;
			$infamilylevel++ if $infamily;
		}
		elsif (/^\s+(?:inactive:\s+)?policer \{\s*$/ && $infamily) {
			$inintpolicer = 1;
			$infamilylevel++ if $infamily;
		}
		elsif (/^\s+dynamic-db;\s*$/) {
			if ($inpol) {
				$policydyn{$inpol} = 1;
			} elsif ($inplist) {
				$plistdyn{$inplist} = 1;
			} else {
				debug(1, "Cannot parse dynamic-db relations");
			}
		}
		elsif (/\{\s*$/) {
			$inplevel++ if $inplevel;
			$inplistlevel++ if $inplist;
			$infamilylevel++ if $infamily;
			$infilterlevel++ if $infilter;
		}
		elsif (/^\s*\}\s*$/) {
			$inintfilter = $inintpolicer = 0;
			if ($inplevel) {
				if (--$inplevel == 0) {
					$inpol = '';
				}
			}
			if ($inplistlevel) {
				if (--$inplistlevel == 0) {
					$inplist = '';
				}
			}
			if ($infilterlevel) {
				if (--$infilterlevel == 0) {
					$infilter = '';
				}
			}
			if ($infamilylevel) {
				if (--$infamilylevel == 0) {
					$infamily = '';
				}
			}
		}
	}
	close(F);
	return if $notdelete;

	$out = '';
	do {
		$deleted = 0;
		foreach $pol (sort keys %policy) {
			if ($policydyn{$pol} && $dyn) {
				debug(2, "policy-statement $pol - used by primary config");
				next;
			}
			if ($policy{$pol}) {
				debug(2, "policy-statement $pol - used in import/export");
				next;
			}
			if (keys %{$policydep{$pol}}) {
				debug(2, "policy-statement $pol - used by another policy: " . join(' ',keys %{$policydep{$pol}}));
				next;
			}
			$out .= "delete policy-options policy-statement $pol\n";
			$deleted = 1;
			delete($policy{$pol});
			delete($policydyn{$pol});
			if (!$onepass) {
				foreach $pol2 (keys %policy) {
					next if $pol eq $pol2;
					delete($policydep{$pol2}->{$pol});
				}
				foreach $pref (keys %plistdep) {
					delete($plistdep{$pref}->{$pol});
				}
			}
		}
	} while ($deleted && !$onepass);

	foreach $pref (sort keys %plist) {
		next if keys %{$plistdep{$pref}};
		next if $plistdyn{$pref} && $dyn;
		delete($plistdyn{$pref});
		$out .= "delete policy-options prefix-list $pref\n";
	}
	foreach $aspath (sort keys %aspath) {
		next if keys %{$aspathdep{$aspath}};
		next if $aspathdyn{$aspath} && $dyn;
		delete($aspathdyn{$aspath});
		$out .= "delete policy-options as-path $aspath\n";
	}
	foreach $comm (sort keys %comm) {
		next if keys %{$commdep{$comm}};
		next if $commdyn{$comm} && $dyn;
		delete($commdyn{$comm});
		$out .= "delete policy-options community $comm\n";
	}
	print "    Delete from dynamic config:\n" if $dyn && $out;
	print $out;
	return if $dyn;
	do {
		$deleted = 0;
		foreach $f (sort keys %filter) {
			next if $filter{$f};
			next if (keys %{$filterdep{$f}});
			$deleted = 1;
			delete ($filter{$f});
			if ($f =~ /:/) {
				print "delete firewall family $` filter $'\n";
			} else {
				print "delete firewall filter $f\n";
			}
			if (!$onepass) {
				foreach $f2 (keys %filter) {
					next if $f2 eq $f;
					delete ($filterdep{$f2}->{$f});
				}
				foreach $p (keys %policer) {
					delete ($policerdep{$p}->{$f});
				}
			}
		}
	} while ($deleted && !$onepass);
	foreach $p (sort keys %policer) {
		next if $policer{$p};
		next if (keys %{$policerdep{$p}});
		print "delete firewall policer $p\n";
	}
}

