#!/usr/bin/perl
# Copyright (C) 2014 Adrian Reyer support@lihas.de
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# 
# 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.
# 
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#

# Requirements: libxml-application-config-perl liblog-log4perl liblog-dispatch-perl

our $DEBUG=0;
use Log::Log4perl qw(:easy);
Log::Log4perl::init('/etc/firewall.lihas.d/log4perl.conf');
if (! Log::Log4perl::initialized()) { WARN "uninit"; } else { }
use DBI;

my $expand_hostgroups=0;
my $expand_portgroups=0;
my $fw_privclients=0;
my $do_shaping=0;
my $TARGETLOG="LOG";
our $do_comment=$ENV{'HAVE_COMMENT'};
our %policymark;
my $CONNSTATE=$ENV{'CONNSTATE'};
use Getopt::Long qw(GetOptions);
Getopt::Long::Configure qw(gnu_getopt);
GetOptions(
  'expand-hostgroup|H' => \$expand_hostgroups,
  'log|l=s' => \$TARGETLOG,
  'debug|d' => \$DEBUG,
  'expand-portgroup|P' => \$expand_portgroups,
  'firewall|f' => \$fw_privclients,
  'shaping|s' => \$do_shaping,
  'comment|v' => \$do_comment,
  'conntrack|c' => \$do_conntrack
) or ERROR "Unknown Option\n";
if ($do_conntrack) { WARN "Connection tracking synchronization is not yet implemented"; }
if ($do_comment) { WARN "Comments are not yet fully implemented"; }

=head1 NAME
firewall-lihas.pl

replace shell fragments of firewall.sh and eventually the whole firewall.sh.
=cut

use warnings;
use strict;

$SIG{__WARN__} = sub {
  local $Log::Log4perl::caller_depth =
        $Log::Log4perl::caller_depth + 1;
  WARN @_;
};

use IO::Handle;
my $FILE = IO::Handle->new();
my $FILEfilter = IO::Handle->new();
my $FILEnat = IO::Handle->new();
my $FILEmangle = IO::Handle->new();
$FILE->fdopen(4,"w");
$FILEfilter->fdopen(5,"w");
$FILEnat->fdopen(6,"w");
$FILEmangle->fdopen(7,"w");

use XML::Application::Config;
# use Test::More skip_all => "Derzeit keine Tests";
use lib "/etc/firewall.lihas.d/lib";

my $cfg = new XML::Application::Config("LiHAS-Firewall","/etc/firewall.lihas.d/config.xml");
my $configpath = $cfg->find('config/@path');

our %hostgroup;
our %portgroup;
our %ifacegroup;
our $nextcommentid=1;
my $commentchain;

=head1 Functions

=head2 parse_policies

=cut

sub parse_policies {
}

=head2 parse_ifacegroup

Goal: load every ifacegroup file only once

=cut

sub parse_ifacegroup {
  my ($arg_ref) = @_;
  my $path = $arg_ref->{path};
  my $name = $arg_ref->{name};
  my $dbh = $arg_ref->{dbh};
  if (!defined ${$ifacegroup{$name}}{defined}) {
    ${$ifacegroup{$name}}{defined}=1;
    ${$ifacegroup{$name}}{ifaces}=[];
    open(my $fh, "<", $path."/groups/ifacegroup-".$name) or die "cannot open < ".$path."/groups/ifacegroup-".$name.": $!";
    $ifacegroup{$name}{comment} = firewall_comment_add_key($dbh,"groups/ifacegroup-".$name);
    foreach my $line (<$fh>) {
      chop $line;
      $line =~ m/^#/ && next;
      $line =~ m/^[ \t]*$/ && next;
      if ( $line =~ m/^ifacegroup-([^ ]*)(|[ \t]*#.*)$/ ) {
        if (!defined $ifacegroup{$1}{defined}) {
          parse_ifacegroup({path=>$path, name=>$1, dbh=>$dbh});
        } else {
        }
        foreach my $iface (values(@{$ifacegroup{$1}{ifaces}})) {
          push(@{$ifacegroup{$name}{ifaces}}, $iface);
        }
      } elsif ( $line =~ m/^(.*)$/){
        my $iface = $1;
        push(@{$ifacegroup{$name}{ifaces}}, $iface);
      }
    }
    close($fh);
  }
}

=head2 parse_hostgroup

Goal: load every hostgroup file only once

=cut

sub parse_hostgroup {
  my ($arg_ref) = @_;
  my $path = $arg_ref->{path};
  my $name = $arg_ref->{name};
  my $dbh = $arg_ref->{dbh};
  if (!defined ${$hostgroup{$name}}{defined}) {
    ${$hostgroup{$name}}{defined}=1;
    ${$hostgroup{$name}}{hosts}=[];
    open(my $fh, "<", $path."/groups/hostgroup-".$name) or die "cannot open < ".$path."/groups/hostgroup-".$name.": $!";
    $hostgroup{$name}{comment} = firewall_comment_add_key($dbh,"groups/hostgroup-".$name);
    foreach my $line (<$fh>) {
      chop $line;
      $line =~ m/^#/ && next;
      $line =~ m/^[ \t]*$/ && next;
      if ( $line =~ m/^hostgroup-([^ ]*)(|[ \t]*#.*)$/ ) {
        if (!defined $hostgroup{$1}{defined}) {
          parse_hostgroup({path=>$path, name=>$1, dbh=>$dbh});
        } else {
        }
        foreach my $host (values(@{$hostgroup{$1}{hosts}})) {
          push(@{$hostgroup{$name}{hosts}}, $host);
        }
      } elsif ( $line =~ m/^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+(|\/[0-9]+)|ipset-[a-zA-Z0-9-_\.]+|dns-[a-zA-Z0-9-\.]+)(\s.*|)$/){
        my $host = $1;
        push(@{$hostgroup{$name}{hosts}}, $host);
      }
    }
    close($fh);
  }
}

=head2 expand_ifacegroup

Goal: load every ifacegroup file only once
=cut

sub expand_ifacegroup {
  my ($arg_ref) = @_;
  my $line = $arg_ref->{line};
  my $dbh = $arg_ref->{dbh};
  my $replaceline='';
  my $resultline='';
  my $name = $line;
  if ( $line =~ m/ifacegroup-([a-zA-Z0-9_\.-]+)\b/ ) {
    my $name = $1;
    $commentchain .= " " . firewall_comment_add_key($dbh,"groups/".$name);
    if (!defined ${$ifacegroup{$name}}{defined}) {
      WARN "Ifacegroup $name undefined, '$line' dropped\n";
      $line = "";
    } else {
      foreach my $replacement (values(@{${$ifacegroup{$name}}{ifaces}})) {
        $replaceline = $line;
        $replaceline =~ s/ifacegroup-$name\b/$replacement/;
        $resultline .= expand_ifacegroup({dbh=>$dbh, line=>$replaceline}) . "\n";
      }
      $line = $resultline;
    }
  }
  return $line;
}

=head2 expand_hostgroup

Goal: load every hostgroup file only once
=cut

sub expand_hostgroup {
  my ($arg_ref) = @_;
  my $line = $arg_ref->{line};
  my $dbh = $arg_ref->{dbh};
  my $replaceline='';
  my $resultline='';
  my $name = $line;
  if ( $line =~ m/hostgroup-([a-zA-Z0-9_\.-]+)\b/ ) {
    my $name = $1;
    $commentchain .= " " . firewall_comment_add_key($dbh,"groups/".$name);
    if (!defined ${$hostgroup{$name}}{defined}) {
      WARN "Hostgroup $name undefined, '$line' dropped\n";
      $line = "";
    } else {
      foreach my $replacement (values(@{${$hostgroup{$name}}{hosts}})) {
        $replaceline = $line;
        $replaceline =~ s/hostgroup-$name\b/$replacement/;
        $resultline .= expand_hostgroup({dbh=>$dbh, line=>$replaceline});
      }
      $line = $resultline;
    }
  }
  return $line;
}

=head2 parse_portgroup

Goal: load every portgroup file only once
=cut

sub parse_portgroup {
  my ($arg_ref) = @_;
  my $path = $arg_ref->{path};
  my $name = $arg_ref->{name};
  my $dbh = $arg_ref->{dbh};
  if (!defined ${$portgroup{$name}}{defined}) {
    ${$portgroup{$name}}{defined}=1;
    @{$portgroup{$name}}{ports}=[];
    open(my $fh, "<", $path."/groups/portgroup-".$name) or die "cannot open < ".$path."/groups/portgroup-".$name.": $!";
    $portgroup{$name}{comment} = firewall_comment_add_key($dbh,"groups/portgroup-".$name);
    foreach my $line (<$fh>) {
      chop $line;
      $line =~ m/^#/ && next;
      $line =~ m/^\s*$/ && next;
      if ( $line =~ m/^(any|tcp|udp|icmp)\s+portgroup-([^\s]+)/ ) {
        my $tmpproto = $1;
        my $tmpgrp = $2;
        if (!defined $portgroup{$tmpgrp}{defined}) {
          parse_portgroup({path=>$path, name=>$tmpgrp, dbh=>$dbh});
        } else {
        }
        foreach my $proto (keys(%{$portgroup{$tmpgrp}{proto}})) {
          foreach my $port (values(@{$portgroup{$tmpgrp}{proto}{$proto}{ports}})) {
            push(@{$portgroup{$name}{proto}{$proto}{ports}}, $port);
          }
        }
      } elsif ( $line =~ m/^([a-zA-Z0-9]+)\s+([0-9:]+)/){
        my $proto = $1;
        my $port = $2;
        push(@{$portgroup{$name}{proto}{$proto}{ports}}, $port);
      }
    }
    close($fh);
    if ($DEBUG) {
      foreach my $proto (keys(%{$portgroup{$name}{proto}})) {
        foreach my $port (values(@{$portgroup{$name}{proto}{$proto}{ports}})) {
          DEBUG "portgroup $name: $proto $port";
        }
      }
    }
  }
}

=head2 expand_portgroup
=cut

sub expand_portgroup {
  my ($arg_ref) = @_;
  my $line = $arg_ref->{line};
  my $dbh = $arg_ref->{dbh};
  my $replaceline='';
  my $resultline='';
  my $name = $line;
  if ( $line =~ m/^(.*)\s+(any|tcp|udp|icmp)\s+portgroup-([a-zA-Z0-9_\.-]+)(.*)$/ ) {
    my $base = $1;
    my $proto = $2;
    my $name = $3;
    my $tail = $4;
    $commentchain .= " " . firewall_comment_add_key($dbh,"groups/".$name);
    if (!defined ${$portgroup{$name}}{defined}) {
      WARN "Portgroup $name undefined, '$line' dropped\n";
      $line = "";
    } else {
      if ($proto =~ /^any$/) {
        foreach $proto (keys %{$portgroup{$name}{proto}}) {
          foreach my $port (values @{$portgroup{$name}{proto}{$proto}{ports}}) {
            $resultline .= "$base\t$proto\t$port\t$tail\n";
          }
        }
      } else {
        foreach my $port (values @{$portgroup{$name}{proto}{$proto}{ports}}) {
          $resultline .= "$base\t$proto\t$port\t$tail\n";
        }
      }
    }
  } else {
    $resultline = $line;
  }
  return $resultline;
}

=head2 fw_mark

fw_mark($dbh, $iface, $startpath, $commentchain)

set special rules for features, e.g. dhcpd, natreflect
=cut

sub fw_mark {
  my $dbh = $_[0];
  my $iface = $_[1];
  my $file = $_[2];
  my $commentchain = $_[3];
  my $outline = "";
  open(my $markfile, "<", $file) or die "cannot open < $file: $!";
  foreach my $line (<$markfile>) {
    $line =~ m/^#/ && next;
    $line =~ m/^[ \t]*$/ && next;
    $line =~ s/#.*//;
    if ($line =~ /^include[\s]+([^\s]+)/) {
      $commentchain .= " " . firewall_comment_add_key($dbh,"$1");
      fw_mark($dbh, $iface, "$configpath/$1",$commentchain);
    } elsif ($line =~ /^dhcpd/) {
      print $FILEfilter "-A INPUT -i $iface -p udp --sport 68 --dport 67 -j ACCEPT\n";
      print $FILEfilter "-A OUTPUT -o $iface -p udp --sport 67 --dport 68 -j ACCEPT\n";
    } elsif ($line =~ /^natreflect/) {
      print $FILEmangle "-A FORWARD -i $iface -o $iface -j CONNMARK --set-xmark 0x80000000/0x80000000\n";
    }
  }
  close $markfile;
}

=head2 fw_nonat

fw_nonat($dbh, $iface, $startpath, $commentchain);

=cut

sub fw_nonat {
  my $dbh = $_[0];
  my $iface = $_[1];
  my $file = $_[2];
  my $commentchain = $_[3];
  my $outline = "";
  open(my $nonat, "<", $file) or die "cannot open < $file: $!";
  foreach my $line (<$nonat>) {
    $line =~ m/^#/ && next;
    $line =~ m/^[ \t]*$/ && next;
    $line =~ s/#.*//;
    if ($line =~ /^include[\s]+([^\s]+)/) {
      $commentchain .= " " . firewall_comment_add_key($dbh,"$1");
      fw_nonat($dbh, $iface, "$configpath/$1",$commentchain);
    } else {
      foreach my $line1 (split(/\n/,expand_hostgroup({dbh=>$dbh, line=>$line}))) {
        foreach my $line2 (split(/\n/,expand_portgroup({dbh=>$dbh, line=>$line1}))) {
          foreach my $line3 (split(/\n/,expand_ifacegroup({dbh=>$dbh, line=>$line2}))) {
            my ($snet, $dnet, $proto, $dport) = split(/[\s]+/, $line3);
            $outline = "";
            if ( $do_comment ) {
              $outline .= " -m comment --comment \"$commentchain\"";
            }
            if ( $snet =~ m/ipset-(.*)/ ) {
              $outline .= " -m set --match-set $1 src";
            } else {
              $outline .= " -s $snet";
            }
            if ( $dnet =~ m/ipset-(.*)/ ) {
              $outline .= " -m set --match-set $1 dst";
            } else {
              $outline .= " -d $dnet";
            }
            if ( $dport =~ /^0$/ ) {
              $outline .= " -p $proto";
            } else {
              if ( $proto =~ /^icmp$/ ) {
                $outline .= " -p $proto --icmp-type $dport";
              } else {
                $outline .= " -p $proto --dport $dport";
              }
            }
            print $FILEnat "-A post-$iface $outline -j ACCEPT\n";
            print $FILEnat "-A pre-$iface $outline -j ACCEPT\n";
          }
        }
      }
    }
  }
  close $nonat;
}

=head2 fw_dnat
=cut

sub fw_dnat {
  my $dbh = $_[0];
  my $iface = $_[1];
  my $file = $_[2];
  my $commentchain = $_[3];
  my $outline = "";
  open(my $dnat, "<", $file) or die "cannot open < $file: $!";
  foreach my $line (<$dnat>) {
    $line =~ m/^#/ && next;
    $line =~ m/^[ \t]*$/ && next;
    $line =~ s/#.*//;
    if ($line =~ /^include[\s]+([^\s]+)/) {
      $commentchain .= " " . firewall_comment_add_key($dbh,"$1");
      fw_dnat($dbh, $iface, "$configpath/$1", $commentchain);
    } else {
      foreach my $line1 (split(/\n/,expand_hostgroup({dbh=>$dbh, line=>$line}))) {
        foreach my $line2 (split(/\n/,expand_portgroup({dbh=>$dbh, line=>$line1}))) {
          foreach my $line3 (split(/\n/,expand_ifacegroup({dbh=>$dbh, line=>$line2}))) {
            my ($dnet, $mnet, $proto, $dport, $ndport) = split(/[\s]+/, $line3);
            $outline = "-A pre-$iface";
            if ( $do_comment ) {
              $outline .= " -m comment --comment \"$commentchain\"";
            }
            if ($dnet =~ /^ACCEPT$/) {
              if ( $mnet =~ m/ipset-(.*)/ ) {
                $outline .= " -m set --match-set $1 src";
              } else {
                $outline .= " -s $mnet";
              }
              if ($dport =~ /^0$/ ) {
                print $FILEnat "$outline -p $proto -j ACCEPT\n";
              } else {
                if ( $proto =~ /^icmp$/ ) {
                  print $FILEnat "$outline -p $proto --icmp-type $dport -j ACCEPT\n";
                } else {
                  print $FILEnat "$outline -p $proto --dport $dport -j ACCEPT\n";
                }
              }
            } else {
              if ( $dnet =~ m/^ipset-(.*)/ ) {
                $outline .= " -m set --match-set $1 dst";
              } else {
                $outline .= " -d $dnet";
              }
              if ( $mnet =~ m/^jump-(.*)/ ) {
                print $FILEnat "$outline -j $1\n";
              } elsif ( $dport =~ /^0$/ ) {
                print $FILEnat "$outline -p $proto -j DNAT --to-destination $mnet\n";
              } else {
                $ndport =~ s/:/-/g;
                if ( $proto =~ /^icmp$/ ) {
                  print $FILEnat "$outline -p $proto --icmp-type $dport -j DNAT --to-destination $mnet:$ndport\n";
                } else {
                  print $FILEnat "$outline -p $proto --dport $dport -j DNAT --to-destination $mnet:$ndport\n";
                }
              }
            }
          }
        }
      }
    }
  }
  close $dnat;
}

=head2 fw_snat
=cut

sub fw_snat {
  my $dbh = $_[0];
  my $iface = $_[1];
  my $file = $_[2];
  my $commentchain = $_[3];
  my $outline = "";
  open(my $snat, "<", $file) or die "cannot open < $file: $!";
  foreach my $line (<$snat>) {
    $line =~ m/^#/ && next;
    $line =~ m/^[ \t]*$/ && next;
    $line =~ s/#.*//;
    if ($line =~ /^include[\s]+([^\s]+)/) {
      $commentchain .= " " . firewall_comment_add_key($dbh,"$1");
      fw_snat($dbh, $iface, "$configpath/$1", $commentchain);
    } else {
      foreach my $line1 (split(/\n/,expand_hostgroup({dbh=>$dbh, line=>$line}))) {
        foreach my $line2 (split(/\n/,expand_portgroup({dbh=>$dbh, line=>$line1}))) {
          foreach my $line3 (split(/\n/,expand_ifacegroup({dbh=>$dbh, line=>$line2}))) {
            my ($snet, $mnet, $proto, $dport) = split(/[\s]+/, $line3);
            $outline = "-A post-$iface";
            if ( $do_comment ) {
              $outline .= " -m comment --comment \"$commentchain\"";
            }
            if ($snet =~ /^ACCEPT$/) {
              if ( $mnet =~ m/ipset-(.*)/ ) {
                $outline .= " -m set --match-set $1 src";
              } else {
                $outline .= " -s $mnet";
              }
              if ($dport =~ /^0$/ ) {
                print $FILEnat "$outline -p $proto -j ACCEPT\n";
              } else {
                if ( $proto =~ /^icmp$/ ) {
                  print $FILEnat "$outline -p $proto --icmp-type $dport -j ACCEPT\n";
                } else {
                  print $FILEnat "$outline -p $proto --dport $dport -j ACCEPT\n";
                }
              }
            } else {
              if ( $snet =~ m/ipset-(.*)/ ) {
                $outline .= " -m set --match-set $1 src";
              } else {
                $outline .= " -s $snet";
              }
              if ( $dport =~ /^0$/ ) {
                print $FILEnat "$outline -p $proto -j SNAT --to-source $mnet\n";
              } else {
                if ( $proto =~ /^icmp$/ ) {
                  print $FILEnat "$outline -p $proto --icmp-type $dport -j SNAT --to-source $mnet\n";
                } else {
                  print $FILEnat "$outline -p $proto --dport $dport -j SNAT --to-source $mnet\n";
                }
              }
            }
          }
        }
      }
    }
  }
  close $snat;
}

=head2 fw_masquerade
=cut

sub fw_masquerade {
  my $dbh = $_[0];
  my $iface = $_[1];
  my $file = $_[2];
  my $commentchain = $_[3];
  my $outline = "";
  open(my $masquerade, "<", $file) or die "cannot open < $file: $!";
  foreach my $line (<$masquerade>) {
    $line =~ m/^#/ && next;
    $line =~ m/^[ \t]*$/ && next;
    $line =~ s/#.*//;
    if ($line =~ /^include[\s]+([^\s]+)/) {
      $commentchain .= " " . firewall_comment_add_key($dbh,"$1");
      fw_masquerade($dbh, $iface, "$configpath/$1", $commentchain);
    } else {
      foreach my $line1 (split(/\n/,expand_hostgroup({dbh=>$dbh, line=>$line}))) {
        foreach my $line2 (split(/\n/,expand_portgroup({dbh=>$dbh, line=>$line1}))) {
          foreach my $line3 (split(/\n/,expand_ifacegroup({dbh=>$dbh, line=>$line2}))) {
            $outline = "-A post-$iface";
            if ( $do_comment ) {
              $outline .= " -m comment --comment \"$commentchain\"";
            }
            my ($snet, $dnet, $proto, $dport, $oiface) = split(/[\s]+/, $line3);
            if ( $snet =~ m/ipset-(.*)/ ) {
              $outline .= " -m set --match-set $1 src";
            } else {
              $outline .= " -s $snet";
            }
            if ( $dport !~ /^0$/ ) {
              if ( $proto =~ /^icmp$/ ) {
                $outline .= " -p $proto --icmp-type $dport";
              } else {
                $outline .= " -p $proto --dport $dport";
              }
            }
            if ( defined($oiface) && $oiface !~ /^$/ ) {
              print $FILEnat "$outline -o $oiface -j MASQUERADE\n";
            } else {
              print $FILEnat "$outline -j MASQUERADE\n";
            }
          }
        }
      }
    }
  }
  close $masquerade;
}

=head2 fw_nologclients
=cut

sub fw_nologclients {
  my $dbh = $_[0];
  my $iface = $_[1];
  my $file = $_[2];
  my $commentchain = $_[3];
  my $outline = "";
  open(my $nologclients, "<", $file) or die "cannot open < $file: $!";
  foreach my $line (<$nologclients>) {
    $line =~ m/^#/ && next;
    $line =~ m/^[ \t]*$/ && next;
    $line =~ s/#.*//;
    if ($line =~ /^include[\s]+([^\s]+)/) {
      $commentchain .= " " . firewall_comment_add_key($dbh,"$1");
      fw_nologclients($dbh, $iface, "$configpath/$1", $commentchain);
    } else {
      foreach my $line1 (split(/\n/,expand_hostgroup({dbh=>$dbh, line=>$line}))) {
        foreach my $line2 (split(/\n/,expand_portgroup({dbh=>$dbh, line=>$line1}))) {
          foreach my $line3 (split(/\n/,expand_ifacegroup({dbh=>$dbh, line=>$line2}))) {
            $outline = "$CONNSTATE NEW";
            if ( $do_comment ) {
              $outline .= " -m comment --comment \"$commentchain\"";
            }
            my ($snet, $dnet, $proto, $dport, $oiface) = split(/[\s]+/, $line3);
            if ( $snet =~ m/ipset-(.*)/ ) {
              $outline .= " -m set --match-set $1 src";
            } else {
              $outline .= " -s $snet";
            }
            if ( $dnet =~ m/ipset-(.*)/ ) {
              $outline .= " -m set --match-set $1 dst";
            } else {
              $outline .= " -d $dnet";
            }
            $outline .= " -p $proto";
            if ( $dport !~ /^0$/ ) {
              if ( $proto =~ /^icmp$/ ) {
                $outline .= " --icmp-type $dport";
              } else {
                $outline .= " --dport $dport";
              }
            }
            if ( defined($oiface) && $oiface !~ /^$/ ) {
              print $FILEfilter "-A fwd-$iface $outline -o $oiface -j DROP\n";
            } else {
              print $FILEfilter "-A fwd-$iface $outline -j DROP\n";
              print $FILEfilter "-A in-$iface $outline -j DROP\n";
            }
          }
        }
      }
    }
  }
  close $nologclients;
}

=head2 fw_rejectclients
=cut

sub fw_rejectclients {
  my $dbh = $_[0];
  my $iface = $_[1];
  my $file = $_[2];
  my $commentchain = $_[3];
  my $outline = "";
  open(my $rejectclients, "<", $file) or die "cannot open < $file: $!";
  foreach my $line (<$rejectclients>) {
    $line =~ m/^#/ && next;
    $line =~ m/^[ \t]*$/ && next;
    $line =~ s/#.*//;
    if ($line =~ /^include[\s]+([^\s]+)/) {
      $commentchain .= " " . firewall_comment_add_key($dbh,"$1");
      fw_rejectclients($dbh, $iface, "$configpath/$1", $commentchain);
    } else {
      foreach my $line1 (split(/\n/,expand_hostgroup({dbh=>$dbh, line=>$line}))) {
        foreach my $line2 (split(/\n/,expand_portgroup({dbh=>$dbh, line=>$line1}))) {
          foreach my $line3 (split(/\n/,expand_ifacegroup({dbh=>$dbh, line=>$line2}))) {
            $outline = "$CONNSTATE NEW";
            if ( $do_comment ) {
              $outline .= " -m comment --comment \"$commentchain\"";
            }
            my ($snet, $dnet, $proto, $dport, $oiface) = split(/[\s]+/, $line3);
            if ( $snet =~ m/ipset-(.*)/ ) {
              $outline .= " -m set --match-set $1 src";
            } else {
              $outline .= " -s $snet";
            }
            if ( $dnet =~ m/ipset-(.*)/ ) {
              $outline .= " -m set --match-set $1 dst";
            } else {
              $outline .= " -d $dnet";
            }
            $outline .= " -p $proto";
            if ( $dport !~ /^0$/ ) {
              if ( $proto =~ /^icmp$/ ) {
                $outline .= " --icmp-type $dport";
              } else {
                $outline .= " --dport $dport";
              }
            }
            if ( defined($oiface) && $oiface !~ /^$/ ) {
              print $FILEfilter "-A fwd-$iface $outline -o $oiface -j REJECT\n";
            } else {
              print $FILEfilter "-A fwd-$iface $outline -j REJECT\n";
              print $FILEfilter "-A in-$iface $outline -j REJECT\n";
            }
          }
        }
      }
    }
  }
  close $rejectclients;
}

=head2 fw_privclients
=cut

sub fw_privclients {
  my $dbh = $_[0];
  my $iface = $_[1];
  my $file = $_[2];
  my $commentchain = $_[3];
  my $outline = "";
  open(my $privclients, "<", $file) or die "cannot open < $file: $!";
  foreach my $line (<$privclients>) {
    $line =~ m/^#/ && next;
    $line =~ s/#.*//;
    $line =~ m/^\s*$/ && next;
    if ($line =~ /^include[\s]+([^\s]+)/) {
      $commentchain .= " " . firewall_comment_add_key($dbh,"$1");
      fw_privclients($dbh, $iface, "$configpath/$1", $commentchain);
    } else {
      foreach my $line1 (split(/\n/,expand_hostgroup({dbh=>$dbh, line=>$line}))) {
        foreach my $line2 (split(/\n/,expand_portgroup({dbh=>$dbh, line=>$line1}))) {
          foreach my $line3 (split(/\n/,expand_ifacegroup({dbh=>$dbh, line=>$line2}))) {
						$line3 =~ s/\s+#.*//;
            # FEATURE: flags
            my %flags;
            if ($line3 =~ /\s+--flags=([^\s]+)/) {
              my $flagline = $1;
              foreach my $flag (split(/;/, $flagline)) {
                if ($flag =~ /^masquerade$/) {
                  $flags{'masquerade'}=1;
                }
              }
            }
            $line3 =~ s/\s+--flags=([^\s]+)//;
            my ($snet, $dnet, $proto, $dport, $oiface) = split(/[\s]+/, $line3);
            $outline = "";
            if ( $do_comment ) {
              $outline .= " -m comment --comment \"$commentchain\"";
            }
            if ( $snet =~ m/^ipset-(.*)/ ) {
              $outline .= " -m set --match-set $1 src";
            } else {
              $outline .= " -s $snet";
            }
            if ( $dnet =~ m/^ipset-(.*)/ ) {
              $outline .= " -m set --match-set $1 dst";
            } else {
              $outline .= " -d $dnet";
            }
            $outline .= " -p $proto";
            if ( $dport !~ /^0$/ ) {
              if ( $proto =~ /^icmp$/ ) {
                $outline .= " --icmp-type $dport";
              } else {
                $outline .= " --dport $dport";
              }
            }
            if (defined($oiface)) {
              if ( $oiface =~ /^lo$/ ) {
                print $FILEfilter "-A in-$iface $CONNSTATE NEW $outline -j ACCEPT\n";
              } elsif ( defined($oiface) && $oiface !~ /^$/ ) {
                print $FILEfilter "-A fwd-$iface $CONNSTATE NEW $outline -o $oiface -j ACCEPT\n";
              } else {
                print $FILEfilter "-A fwd-$iface $CONNSTATE NEW $outline -j ACCEPT\n";
                print $FILEfilter "-A in-$iface $CONNSTATE NEW $outline -j ACCEPT\n";
							}
            } else {
              print $FILEfilter "-A fwd-$iface $CONNSTATE NEW $outline -j ACCEPT\n";
              print $FILEfilter "-A in-$iface $CONNSTATE NEW $outline -j ACCEPT\n";
            }
            if (defined($flags{'masquerade'} && $flags{'masquerade'} == 1)) {
              print $FILEnat "-A post-$iface $outline -j MASQUERADE\n";
            }
          }
        }
      }
    }
  }
  close $privclients;
}

=head2 fw_policyrouting
=cut

sub fw_policyrouting {
  my $dbh = $_[0];
  my $iface = $_[1];
  my $file = $_[2];
  my $commentchain = $_[3];
  my $outline = "";
  open(my $policyrouting, "<", $file) or die "cannot open < $file: $!";
  foreach my $line (<$policyrouting>) {
    $line =~ m/^#/ && next;
    $line =~ m/^[ \t]*$/ && next;
    $line =~ s/#.*//;
    if ($line =~ /^include[\s]+([^\s]+)/) {
      $commentchain .= " " . firewall_comment_add_key($dbh,"$1");
      fw_policyrouting($dbh, $iface, "$configpath/$1", $commentchain);
    } else {
      foreach my $line1 (split(/\n/,expand_hostgroup({dbh=>$dbh, line=>$line}))) {
        foreach my $line2 (split(/\n/,expand_portgroup({dbh=>$dbh, line=>$line1}))) {
          foreach my $line3 (split(/\n/,expand_ifacegroup({dbh=>$dbh, line=>$line2}))) {
            my ($snet, $dnet, $proto, $dport, $policy) = split(/[\s]+/, $line3);
            $outline = "";
            if ( $do_comment ) {
              $outline .= " -m comment --comment \"$commentchain\"";
            }
            if ( $snet =~ m/ipset-(.*)/ ) {
              $outline .= " -m set --match-set $1 src";
            } else {
              $outline .= " -s $snet";
            }
            if ( $dnet =~ m/ipset-(.*)/ ) {
              $outline .= " -m set --match-set $1 dst";
            } else {
              $outline .= " -d $dnet";
            }
            $outline .= " -p $proto";
            if ( $dport !~ /^0$/ ) {
              if ( $proto =~ /^icmp$/ ) {
                $outline .= " --icmp-type $dport";
              } else {
                $outline .= " --dport $dport";
              }
            }
            $outline .= " -j MARK --set-mark $policymark{$policy}";
            print $FILEmangle "-A OUTPUT $outline\n";
            print $FILEmangle "-A PREROUTING $outline\n";
          }
        }
      }
    }
  }
  close $policyrouting;
}

=head2 do_shaping
=cut

sub do_shaping {
  
}

=head2 firewall_create_db

Setup the db according to the config.xml
TODO: Unify with firewall-lihasd.pl

=cut
sub firewall_create_db {
  my $dbh = $_[0];
  my $sql;
  foreach $sql (split(/;/,$cfg->find('database/create'))) {
    if ( defined $sql ) {
      chomp $sql;
      $sql =~ s/\n//g;
      $dbh->do("$sql");
    }
  }
  $sql = "CREATE TABLE IF NOT EXISTS fw_comments ( id INTEGER NOT NULL, file TEXT NOT NULL, comment TEXT);";
  $dbh->do("$sql");
}

=head2 firewall_comment_add_key($dbh)

Add a commentindex/comment to the comment table, returns the commentindex used

=cut
sub firewall_comment_add_key {
  my $dbh = $_[0];
  my $file = $_[1];
  my $commentid=$nextcommentid;
  my $sql = "SELECT id FROM fw_comments WHERE file=?";
  my $sth = $dbh->prepare("$sql");
  $sth->execute($file);
  $sth->bind_columns(\$commentid);
  while ($sth->fetch()) {
    return $commentid;
  }
  $sql = "INSERT INTO fw_comments (id, file, comment) VALUES (?,?,'')";
  $sth = $dbh->prepare("$sql");
  $sth->execute($nextcommentid,$file);
  $nextcommentid++;
  return $commentid;
}

=head2 main stuff
=cut

my $dbh = DBI->connect($cfg->find('database/dbd/@connectorstring'), { RaiseError => 1, AutoCommit => 1 });
if ($fw_privclients) {
  firewall_create_db($dbh);
}

if ($expand_hostgroups || $fw_privclients) {
  opendir(my $dh, "$configpath/groups") || die "can't opendir "."$configpath/groups: $!\n";
  my @files = grep { /^hostgroup-/ && -f "$configpath/groups/$_" } readdir($dh);
  closedir $dh;
  my $path = $cfg->find('config/@path');
  foreach my $file (@files) {
    my $name = $file;
    $name =~ s/^hostgroup-//;
    parse_hostgroup({path=>$path, name=>$name, dbh=>$dbh});
  }
}
if ($expand_portgroups || $fw_privclients) {
  opendir(my $dh, "$configpath/groups") || die "can't opendir "."$configpath/groups: $!\n";
  my @files = grep { /^portgroup-/ && -f "$configpath/groups/$_" } readdir($dh);
  closedir $dh;
  my $path = $cfg->find('config/@path');
  foreach my $file (@files) {
    my $name = $file;
    $name =~ s/^portgroup-//;
    parse_portgroup({path=>$path, name=>$name, dbh=>$dbh});
  }
}
my %comment;
if ($fw_privclients) {
  opendir(my $dh, "$configpath/groups") || die "can't opendir "."$configpath/groups: $!\n";
  my @files = grep { /^ifacegroup-/ && -f "$configpath/groups/$_" } readdir($dh);
  closedir $dh;
  my $path = $cfg->find('config/@path');
  foreach my $file (@files) {
    my $name = $file;
    $name =~ s/^ifacegroup-//;
    parse_ifacegroup({path=>$path, name=>$name, dbh=>$dbh});
  }
  opendir($dh, $cfg->find('config/@path')) || die "can't opendir "."$configpath: $!\n";
  my @interfaces = grep { /^interface-/ && -d "$configpath/$_/" } readdir($dh);
  foreach my $interfacedir (@interfaces) {
    -s "$configpath/$interfacedir/comment" || next;
    my $iface = $interfacedir;
    $iface =~ s/^interface-//;
    $comment{$iface}=[];
    if ( -e "$configpath/$interfacedir/comment" ) {
      open(my $cf, "<", "$configpath/$interfacedir/comment") or die "cannot open < "."$configpath/$interfacedir/comment".": $!";
      foreach my $line (<$cf>) {
        push(@{$comment{$iface}}, $line);
      }
      close($cf);
    }
  }
  opendir($dh, $cfg->find('config/@path')) || die "can't opendir "."$configpath: $!\n";
  my @policies = grep { /^policy-routing-/ && -d "$configpath/$_/" } readdir($dh);
  foreach my $policydir (@policies) {
    -s "$configpath/$policydir/key" || next;
    my $policy = $policydir;
    $policy =~ s/^policy-routing-//;
    if ( -e "$configpath/$policydir/key" ) {
      open(my $cf, "<", "$configpath/$policydir/key") or die "cannot open < "."$configpath/$policydir/key".": $!";
      foreach my $line (<$cf>) {
        chop $line;
        $policymark{$policy}=$line;
      }
      close($cf);
    }
  }
}

my $dh;
my $commenthandle;
my @interfaces;
my @policies;

if ($fw_privclients) {
  opendir($dh, $cfg->find('config/@path')) || die "can't opendir "."$configpath: $!\n";
  @interfaces = grep { /^interface-/ && -d "$configpath/$_/" } readdir($dh);
  @policies = grep { /^policy-routing-/ && -d "$configpath/$_/" } readdir($dh);
  closedir $dh;

  print "Setting up IPSEC Spoof Protection\n";
  foreach my $interfacedir (@interfaces) {
    -s "$configpath/$interfacedir/network-ipsec" || next;
    my $iface = $interfacedir;
    $iface =~ s/^interface-//;
    foreach my $line (values(@{$comment{$iface}})) {
      print "  ".$line;
    }
    open(my $cf, "<", "$configpath/$interfacedir/network-ipsec") or die "cannot open < "."$configpath/$interfacedir/network-ipsec".": $!";
    foreach my $line (<$cf>) {
      chomp($line);
      $line =~ s/[ \t]*#.*//;
      $line =~ m/^[ \t]*$/ && next;
      print $FILEfilter "-A INPUT -s $line -i $iface -j in-$iface\n";
      print $FILEfilter "-A FORWARD -s $line -i $iface -j fwd-$iface\n";
      print $FILEfilter "-A INPUT -s $line -i $iface -j dns-in-$iface\n";
      print $FILEfilter "-A FORWARD -s $line -i $iface -j dns-fwd-$iface\n";
      print $FILEmangle "-A PREROUTING -p esp -j MARK --set-mark 8000/0000\n";
      print $FILEmangle "-A PREROUTING -p ah -j MARK --set-mark 8000/0000\n";
        print $FILEfilter "-A in-$iface -s $line -i $iface -m mark ! --mark 8000/8000 -j $TARGETLOG\n";
        print $FILEfilter "-A fwd-$iface -s $line -i $iface -m mark ! --mark 8000/8000 -j $TARGETLOG\n";
        print $FILEfilter "-A in-$iface -s $line -i $iface -m mark ! --mark 8000/8000 -j DROP\n";
        print $FILEfilter "-A fwd-$iface -s $line -i $iface -m mark ! --mark 8000/8000 -j DROP\n";
    }
    close($cf);
    print $FILEnat "-A PREROUTING -i $iface -j pre-$iface\n";
    print $FILEnat "-A POSTROUTING -o $iface -j post-$iface\n";
  }
  my %ifaces;
  print "Setting up Chains\n";
  foreach my $interfacedir (@interfaces) {
    my $iface = $interfacedir;
    $iface =~ s/^interface-//;
    if ( -l "$configpath/$interfacedir" ) {
      $ifaces{physical}{$iface} = readlink "$configpath/$interfacedir";
      $ifaces{physical}{$iface} =~ s/^$configpath\///;
      $ifaces{physical}{$iface} =~ s/\/$//;
      if ($ifaces{physical}{$iface} =~ m/^interface-/) {
        $ifaces{physical}{$iface} =~ s/^interface-//;
      } else  {
        $ifaces{physical}{$iface}=$iface;
      }
      $ifaces{logical}{$ifaces{physical}{$iface}} = $ifaces{physical}{$iface};
    } else {
      $ifaces{physical}{$iface}=$iface;
      $ifaces{logical}{$iface}=$iface;
    }
  }
  foreach my $iface (keys(%{$ifaces{"physical"}})) {
    my $logicaliface = $ifaces{physical}{$iface};
    my $interfacedir="interface-$iface";
    -s "$configpath/$interfacedir/network" || next;
    foreach my $line (values(@{$comment{$iface}})) {
      print "  ".$line;
    }
    if ( $iface =~ /^lo$/ ) {
      print $FILEfilter "-A OUTPUT -j fwd-$logicaliface\n";
      print $FILEnat "-A OUTPUT -j pre-$logicaliface\n";
      print $FILEnat "-A POSTROUTING -o $iface -j post-$logicaliface\n";
      print $FILEfilter "-A OUTPUT -j dns-fwd-$logicaliface\n";
      print $FILEnat "-A OUTPUT -j dns-pre-$logicaliface\n";
      print $FILEnat "-A POSTROUTING -o $iface -j dns-post-$logicaliface\n";
    } else {
      if ( -e "$configpath/$interfacedir/network" ) {
        open(my $cf, "<", "$configpath/$interfacedir/network") or die "cannot open < "."$configpath/$interfacedir/network".": $!";
        foreach my $line (<$cf>) {
          chomp($line);
          $line =~ s/[ \t]*#.*//;
          $line =~ m/^[ \t]*$/ && next;
          print $FILEfilter "-A INPUT -s $line -i $iface -j in-$logicaliface\n";
          print $FILEfilter "-A FORWARD -s $line -i $iface -j fwd-$logicaliface\n";
          print $FILEfilter "-A INPUT -s $line -i $iface -j dns-in-$logicaliface\n";
          print $FILEfilter "-A FORWARD -s $line -i $iface -j dns-fwd-$logicaliface\n";
        }
        close($cf);
      } else {
        print STDERR "WARNING: Interface $iface has no network file\n";
      }
      print $FILEnat "-A PREROUTING -i $iface -j pre-$logicaliface\n";
      print $FILEnat "-A POSTROUTING -o $iface -j post-$logicaliface\n";
      print $FILEnat "-A PREROUTING -i $iface -j dns-pre-$logicaliface\n";
      print $FILEnat "-A POSTROUTING -o $iface -j dns-post-$logicaliface\n";
    }
  }
  print "Loopback Interface is fine\n";
  print $FILEfilter "-A OUTPUT  -j ACCEPT -o lo\n";
  print $FILEfilter "-A INPUT    -j ACCEPT -i lo\n";
  if ( -x "$configpath/script-pre" ) {
    print "Hook: script-pre\n";
    system("$configpath/script-pre");
  }
  print "Special rules (dhcpd, natreflection)\n";
  foreach my $iface (keys(%{$ifaces{"physical"}})) {
    my $interfacedir="interface-$iface";
    -s "$configpath/$interfacedir/mark" || next;
    $commentchain=firewall_comment_add_key($dbh,"$interfacedir/mark");
    my $iface = $interfacedir;
    $iface =~ s/^interface-//;
    foreach my $line (values(@{$comment{$iface}})) {
      print "  ".$line;
    }
    fw_mark($dbh, $iface, "$configpath/$interfacedir/mark", $commentchain);
  }
  print "Adding privileged Clients\n";
  foreach my $iface (keys(%{$ifaces{logical}})) {
    my $interfacedir="interface-$iface";
    -s "$configpath/$interfacedir/privclients" || next;
    $commentchain=firewall_comment_add_key($dbh,"$interfacedir/privclients");
    my $iface = $interfacedir;
    $iface =~ s/^interface-//;
    foreach my $line (values(@{$comment{$iface}})) {
      print "  ".$line;
    }
    fw_privclients($dbh, $iface, "$configpath/$interfacedir/privclients", $commentchain);
  }
  print "Avoiding NAT\n";
  foreach my $iface (keys(%{$ifaces{logical}})) {
    my $interfacedir="interface-$iface";
    -s "$configpath/$interfacedir/nonat" || next;
    $commentchain=firewall_comment_add_key($dbh,"$interfacedir/nonat");
    my $iface = $interfacedir;
    $iface =~ s/^interface-//;
    foreach my $line (values(@{$comment{$iface}})) {
      print "  ".$line;
    }
    fw_nonat($dbh, $iface, "$configpath/$interfacedir/nonat",$commentchain);
  }
  print "Adding DNAT\n";
  foreach my $iface (keys(%{$ifaces{logical}})) {
    my $interfacedir="interface-$iface";
    -s "$configpath/$interfacedir/dnat" || next;
    $commentchain=firewall_comment_add_key($dbh,"$interfacedir/dnat");
    my $iface = $interfacedir;
    $iface =~ s/^interface-//;
    foreach my $line (values(@{$comment{$iface}})) {
      print "  ".$line;
    }
    fw_dnat($dbh, $iface, "$configpath/$interfacedir/dnat", $commentchain);
  }
  print "Adding SNAT\n";
  foreach my $iface (keys(%{$ifaces{logical}})) {
    my $interfacedir="interface-$iface";
    -s "$configpath/$interfacedir/snat" || next;
    $commentchain=firewall_comment_add_key($dbh,"$interfacedir/snat");
    my $iface = $interfacedir;
    $iface =~ s/^interface-//;
    foreach my $line (values(@{$comment{$iface}})) {
      print "  ".$line;
    }
    fw_snat($dbh, $iface, "$configpath/$interfacedir/snat", $commentchain);
  }
  print "Adding MASQUERADE\n";
  foreach my $iface (keys(%{$ifaces{logical}})) {
    my $interfacedir="interface-$iface";
    -s "$configpath/$interfacedir/masquerade" || next;
    $commentchain=firewall_comment_add_key($dbh,"$interfacedir/masquerade");
    my $iface = $interfacedir;
    $iface =~ s/^interface-//;
    foreach my $line (values(@{$comment{$iface}})) {
      print "  ".$line;
    }
    fw_masquerade($dbh, $iface, "$configpath/$interfacedir/masquerade", $commentchain);
  }
  print "Rejecting extra Clients\n";
  foreach my $iface (keys(%{$ifaces{logical}})) {
    my $interfacedir="interface-$iface";
    -s "$configpath/$interfacedir/reject" || next;
    $commentchain=firewall_comment_add_key($dbh,"$interfacedir/reject");
    my $iface = $interfacedir;
    $iface =~ s/^interface-//;
    foreach my $line (values(@{$comment{$iface}})) {
      print "  ".$line;
    }
    fw_rejectclients($dbh, $iface, "$configpath/$interfacedir/reject", $commentchain);
  }
  print "Disabling specific dropped connnection logs\n";
  foreach my $iface (keys(%{$ifaces{logical}})) {
    my $interfacedir="interface-$iface";
    -s "$configpath/$interfacedir/nolog" || next;
    $commentchain=firewall_comment_add_key($dbh,"$interfacedir/nolog");
    my $iface = $interfacedir;
    $iface =~ s/^interface-//;
    foreach my $line (values(@{$comment{$iface}})) {
      print "  ".$line;
    }
    fw_nologclients($dbh, $iface, "$configpath/$interfacedir/nolog", $commentchain);
  }
  print "Adding Policy Routing\n";
  foreach my $iface (keys(%{$ifaces{logical}})) {
    my $interfacedir="interface-$iface";
    -s "$configpath/$interfacedir/policy-routing" || next;
    $commentchain=firewall_comment_add_key($dbh,"$interfacedir/policy-routing");
    my $iface = $interfacedir;
    $iface =~ s/^interface-//;
    foreach my $line (values(@{$comment{$iface}})) {
      print "  ".$line;
    }
    fw_policyrouting($dbh, $iface, "$configpath/$interfacedir/policy-routing", $commentchain);
  }
  print "Policy Routing routing setup";
  foreach my $policydir (@policies) {
    my $policy = $policydir;
    $policy =~ s/^policy-routing-//;
    -s "$configpath/policy-routing-$policy/key" || next;
    -s "$configpath/policy-routing-$policy/gateway" || next;
    foreach my $line (values(@{$comment{$policydir}})) {
      print "  ".$line;
    }
    my @cmd = ("ip", "route", "ls", "table", "$policy");
    if ( system(@cmd) != 0 ) {
      WARN "Please add '$policy' to /etc/iproute2/rt_tables or policy routing won't work. If you don't want policy routing, feel free to delete $configpath/policy-routing-$policy"
    }
    open(my $file, "<", "$configpath/policy-routing-$policy/key") or die "cannot open < "."$configpath/policy-routing-$policy/key".": $!";
    my $policykey=(<$file>);
    DEBUG "policykey: $policykey\n";
    close($file);
    open($file, "<", "$configpath/policy-routing-$policy/gateway") or die "cannot open < "."$configpath/policy-routing-$policy/gateway".": $!";
#INCOMPLETE#    foreach my $line (<$file>) {
#INCOMPLETE#      $line =~ s/[ \t]*#.*//;
#INCOMPLETE#      $line =~ m/^[ \t]*$/ && next;
#INCOMPLETE#      my ($type, $interface, $gateway) = split /[ \t]+/ $line;
#INCOMPLETE#      open();
#INCOMPLETE#      if ( $type == "PPP" ) {
#INCOMPLETE#      } elsif ( $type == "NET" ) {
#INCOMPLETE#      }
#INCOMPLETE#    }
    close($file);
  }

# natreflection, if conntrack bit 32 is set
  print $FILEnat "-A POSTROUTING -m connmark --mark 0x80000000/0x80000000 -j MASQUERADE\n";
} elsif ($expand_hostgroups) {
  foreach my $line (<>) {
    $line =~ m/^#/ && next;
    $line =~ m/^[ \t]*$/ && next;
    if ($expand_portgroups) {
      foreach my $line1 (split(/\n/,expand_hostgroup({dbh=>$dbh, line=>$line}))) {
        $line1.="\n";
        print expand_portgroup({dbh=>$dbh, line=>$line1});
      }
    } else {
      print expand_hostgroup({dbh=>$dbh, line=>$line});
    }
  }
} elsif ($expand_portgroups) {
  foreach my $line (<>) {
    $line =~ m/^#/ && next;
    $line =~ m/^\s*$/ && next;
    print expand_portgroup({dbh=>$dbh, line=>$line});
  }
}

$FILE->close;
$FILEfilter->close;
$FILEnat->close;
$FILEmangle->close;

$dbh->disconnect or die $dbh->errstr;
exit 0;
# vim: ts=2 sw=2 sts=2 sr noet
