#!/usr/bin/perl
#
# Module: vyatta-update-src-nat.pl
#
# **** License ****
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
# published by the Free Software Foundation.
#
# 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.
#
# This code was originally developed by Vyatta, Inc.
# Portions created by Vyatta are Copyright (C) 2009 Vyatta, Inc.
# All Rights Reserved.
#
# Author: eng@vyatta.com
# Date: 2011
# Description: Script to update iptables source NAT rules
#
# **** End License ****
#

use strict;
use lib "/opt/vyatta/share/perl5/";
use Vyatta::Config;
use Vyatta::NatRuleCommon;
use Vyatta::SrcNatRule;
use Vyatta::IpTables::Mgr;

my $CONFIG_LEVEL = "nat source";
my $IPTABLES = "/sbin/iptables";

sub numerically {$a <=> $b;}

sub raw_cleanup {

    # remove the conntrack setup.
    ipt_disable_conntrack('iptables', 'NAT_CONNTRACK');
}

my $config = new Vyatta::Config;

my $all_deleted = 1;

$config->setLevel("nat destination rule");
my %rules_dst = $config->listNodeStatus();
my $rule_dst;
for $rule_dst (keys %rules_dst) {
    if ($rules_dst{$rule_dst} ne "deleted") {
        $all_deleted = 0;
    }
}

$config->setLevel($CONFIG_LEVEL." rule");
my %rules = $config->listNodeStatus();
my $rule;
my $debug = 0;
if ($debug) {
    open(OUT, ">>/tmp/nat") or exit 1;
} else {
    open(OUT, ">>/dev/null") or exit 1;
}

my $ipt_rulenum = 2;

my $chain_name = "POSTROUTING";

print OUT "========= src-nat list =========\n";
my @rule_keys = sort numerically keys %rules;
if ($#rule_keys < 0) {
    raw_cleanup();
    exit 0;
}

## it seems that "multiport" does not like port range (p1:p2) if nobody has
## touched the nat table yet after reboot!?
system("$IPTABLES -t nat -L -n >& /dev/null");

# we have some nat rule(s). make sure conntrack is enabled.
ipt_enable_conntrack('iptables', 'NAT_CONNTRACK');

for $rule (@rule_keys) {
    if (($rules{$rule} eq "static") || ($rules{$rule} eq "deleted")) {
        next;
    } else {
        my $test_rule = new Vyatta::SrcNatRule;
        $test_rule->setup($CONFIG_LEVEL." rule $rule");
        my ($err, @rule_strs) = $test_rule->rule_str();
        if (defined $err) {
            # rule check failed => return error
            print OUT "Source NAT configuration error in rule $rule: $err\n";
            print STDERR "Source NAT configuration error in rule $rule: $err\n";
            exit 5;
        }
    }
}

for $rule (@rule_keys) {
    print OUT "$rule: $rules{$rule}\n";
    my $tmp = `iptables -L -nv --line -t nat`;
    print OUT "iptables before:\n$tmp\n";
    my $nrule = new Vyatta::SrcNatRule;
    $nrule->setup($CONFIG_LEVEL." rule $rule");

    if ($rules{$rule} ne "deleted") {
        $all_deleted = 0;
    }

    my $cmd;
    if ($rules{$rule} eq "static") {
        my $ipt_rules = $nrule->get_num_ipt_rules();
        $ipt_rulenum += $ipt_rules;
        next;
    } elsif ($rules{$rule} eq "deleted") {
        my $orule = new Vyatta::SrcNatRule;
        $orule->setupOrig($CONFIG_LEVEL." rule $rule");
        my $ipt_rules = $orule->get_num_ipt_rules();
        for (1 .. $ipt_rules) {
            $cmd = "$IPTABLES -t nat -D $chain_name $ipt_rulenum";
            print OUT "$cmd\n";
            if (system($cmd)) {
                exit 1;
            }
        }
        next;
    }

    my ($err, @rule_strs) = $nrule->rule_str();

    if ($rules{$rule} eq "added") {
        foreach my $rule_str (@rule_strs) {
            next if !defined $rule_str;
            $cmd = "$IPTABLES -t nat -I $chain_name $ipt_rulenum " ."$rule_str";
            print OUT "$cmd\n";
            if (system($cmd)) {
                exit 1;
            }
            $ipt_rulenum++;
        }

    } elsif ($rules{$rule} eq "changed") {

        # delete the old rule(s)
        my $orule = new Vyatta::SrcNatRule;
        $orule->setupOrig($CONFIG_LEVEL." rule $rule");
        my $ipt_rules = $orule->get_num_ipt_rules();
        my $idx = $ipt_rulenum;
        for (1 .. $ipt_rules) {
            $cmd = "$IPTABLES -t nat -D $chain_name $idx";
            print OUT "$cmd\n";
            if (system($cmd)) {
                exit 1;
            }
        }

        # add the new rule(s)
        foreach my $rule_str (@rule_strs) {
            next if !defined $rule_str;
            $cmd = "$IPTABLES -t nat -I $chain_name $ipt_rulenum " ."$rule_str";
            print OUT "$cmd\n";
            if (system($cmd)) {
                exit 1;
            }
            $ipt_rulenum++;
        }

    }
}

if ($all_deleted) {
    raw_cleanup();
}

close OUT;
exit 0;

# Local Variables:
# mode: perl
# indent-tabs-mode: nil
# perl-indent-level: 2
# End:
