#!/usr/bin/perl
###################################################################################
#
# nuaclgen.pl : insertion of ACls in the Nu Ldap tree.
#
# Copyright(C) 2003,2005 Eric Leblond <eric@regit.org>
#                        Vincent Deffontaines <vincent@gryzor.com>
# INL http://www.inl.fr/
#
# $Id$
#
# 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, version 2 of the License.
#
# 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, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
###################################################################################
use strict;
use warnings;
use Net::LDAP;
use Getopt::Long;
use Socket;
$Getopt::Long::ignorecase=0;

my %acl;
# TODO unused variable ?
my $exit_code;

our ($basedn, $ldap_host, $username, $password);

# include conf variables
do "/etc/nufw/nuaclgen.conf" or die "Can not find config file";


sub convert_addr {
  my @list;
  my $partsum;
  my @parts;
  foreach my $address (@_) {
    @parts=split /\./, $address;
    $partsum=0;
    foreach my $part (@parts) {
      $partsum = $partsum*256 + $part;
    }
    push @list, ($partsum);
  }
  return @list if @list > 1;
  return $list[0] if @list == 1;
}

sub construct_addr_range {
  my ($range, $src , $dst);

  $range=shift;
  if ($range=~m#([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})/([0-9]{1,2})#) {
    return (convert_addr($1),convert_addr($1)+2**(32-$2)-1)
  } elsif ( $range=~m/[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/ ) {
    my $ip=convert_addr($range);
    return ($ip,$ip);
  } else {
    return "Invalid";
  }
}

sub construct_port_range {
  my @range =  split /:/ , shift;
  if (scalar @range == 2) {
    return @range;
  } elsif (scalar @range == 1) {
    return ($range[0], $range[0]);
  } else {
    return "OOPs";
  }
}

sub todotquad {
  my $ip = shift;
  my $stringip = $ip % 256;
  foreach my $i (0..2) {
    $ip = ( $ip - $ip%256 ) / 256;
    $stringip = $ip % 256 . "." . $stringip;
  }
  return $stringip;
}

my $separator = ",";
my ($help, $schema, $saddr, $daddr, $proto, $sport, $dport);
my ($decision, $groups, $users, $aclname, $osname, $osversion, $authquality);
my ($osrelease, $appname, $appsig, $list, $delete );


my $result = GetOptions("help" => \$help,
		     "Schema" => \$schema,
		     "saddr=s" => \$saddr,
		     "daddr=s" => \$daddr,
		     "proto=i" => \$proto,
		     "sport=s" => \$sport,
		     "dport=s" => \$dport,
		     "jump=s" => \$decision,
		     "groups=s" => \$groups,
		     "users=s" => \$users,
		     "Aclname=s" => \$aclname,
		     "OsName=s" => \$osname,
		     "OsVersion=s" => \$osversion,
		     "OsRelease=s" => \$osrelease,
		     "AppName=s" => \$appname,
		     "AppSig=s" => \$appsig,
		     "AuthQuality" => \$authquality,
                     "Separator" => \$separator,
		     "List" => \$list,
		     "Delete=s" => \$delete,
		    );

if ($result == 0) {
  die "Error parsing options\n";
}

if ($help) {
  print "nuaclgen [-S (eq|ineq)] --Aclname [ACLDN] [--saddr NETWORK1] [--daddr NETWORK2] [--proto PROTONUMBER]
        [--sport P1[:P2]] [--dport P3[:P4]] --jump [ACCEPT|DROP] [--groups [GROUPLIST] || --users [USERSLIST]]
        [--OsName N1,N2... [--OsVersion V1,V2...] [--OsRelease R1,R2...]]
        [--AppName N1,N2... [--AppSig S1,S2...]] [--AuthQuality 1]: add an acl
nuaclgen -L -g [Id Group] : list acl(s) for a group.
nuaclgen -L -u [Id User] : list acl(s) for a user.
nuaclgen --Delete dn : delete this dn ACL
SYNTAX :
\t- NETWORK : aaa.bbb.ccc.ddd[/ee]
\t- GROUPSLIST : gid1[,gid2,gid3]
\t- USERSLIST : uid1[,uid2,uid3]
\t- PORTRANGE: NNNN[:MMMM]
";
  exit;
}
if ((defined $aclname)+(defined $list)+defined($delete) > 1)
{
  die "Sorry, one mode allowed at a time only!";
}

if (defined $delete)
{
  my $mybase = $basedn;
  chomp $mybase;
  if ($delete!~/.*$mybase$/){
    print "basedn : $mybase\n";
    print "delete : $delete\n";
    die "Sorry, not allowed to delete that cn. It's not in the ACL base!";
  }
}else{

  if (not (defined($groups) or defined($users))) {
    die "No group(s) or user(s) given\n";
  }

  if (defined($groups)) {
    if ($groups=~m/,/) {
      $acl{"Group"}= [split /,/ , $groups];
    } else {
      $acl{"Group"}= $groups;
    }
  }

  if (defined($users)) {
    if ($users=~m/,/) {
      $acl{"User"}= [split /,/ , $users];
    } else {
      $acl{"User"}= $users;
    }
  }
}

#do ldap connect
my $ldap = Net::LDAP->new($ldap_host)  or die $@;;
# bind to a directory with dn and password
$result = $ldap->bind ( $username,
			password => $password
		      )  or die $@;
$result->code && warn "failed to bind: ", $result->error;


if ($aclname){

if (not defined($saddr)) {
  $saddr="0.0.0.0/0";
}
($acl{"SrcIpStart"},$acl{"SrcIpEnd"})=construct_addr_range($saddr);

if (not defined($daddr)) {
  $daddr="0.0.0.0/0";
}
($acl{"DstIpStart"},$acl{"DstIpEnd"})=construct_addr_range($daddr);

if (not defined($sport)) {
  $sport="0:65535";
}
($acl{"SrcPortStart"},$acl{"SrcPortEnd"})=construct_port_range($sport);





if (not defined($proto)) {
  $acl{"Proto"}=[6,17];
} else {
  $acl{"Proto"}=$proto;
}



if (not defined($schema) or $schema eq 'eq') {
  if (not defined($dport)) {
    die "Equality schema specified, destination port needed";
  } else {
    # split by comma
    my @dports = split(/,/,$dport);
    $acl{"DstPort"}=\@dports;
  }
} else {
  if (not defined($dport)) {
    $dport="0:65535";
  }
  ($acl{"DstPortStart"},$acl{"DstPortEnd"})=construct_port_range($dport);
}

$acl{"objectclass"}  = [ "top", "NuAccessControlList" ];
if (defined $osname){
  my @os = split(/$separator/,$osname);
  $acl{"OsName"}=\@os;
  if (defined $osversion){
    my @ver= split(/$separator/,$osversion);
    $acl{"OsVersion"}=\@ver;
  }
  if (defined $osrelease){
    my @rel= split(/$separator/,$osrelease);
    $acl{"OsVersion"}=\@rel;
  }
}
if (defined $appname){
  my @app= split(/$separator/,$appname);
  $acl{"AppName"}=\@app;
  if (defined $appsig){
    my @sig= split(/$separator/,$appsig);
    $acl{"AppSig"}=\@sig;
  }
}

if (defined $authquality){
    my @sig= split(/$separator/,$authquality);
    $acl{"AuthQuality"}=\@sig;
}

# look for Add mode
if (not defined($aclname)) {
  $exit_code = "No Acl Name given, Aborting\n";
} else {
  $aclname=~/^[a-zA-Z0-9,=_\s]+$/ or die "Sorry, bad characters in Acl name ( $aclname )";
  $aclname=~m/^cn=(\w+),.*/ and $acl{"cn"}=$1;
  if (not defined($decision)) {
    die "No decision given\n";
  } else {
    if ($decision eq "ACCEPT") {
      $acl{"Decision"}=1;
    } else {
      $acl{"Decision"}=0;
    }
  }
  print "Adding $aclname\n";
  $result = $ldap->add( $aclname,
			attr => [%acl ]) ;

  $result->code && warn "failed to add entry: ", $result->error or print "done\n";

  $ldap->unbind;		# take down session
  exit;
}
}

my $filter;

if (defined ($list)) {
  if (defined($groups)) {
    $filter = "(&(objectClass=NuAccessControlList)(Group=".$acl{"Group"}."))";
  } elsif (defined($users)) {
    $filter = "(&(objectClass=NuAccessControlList)(User=".$acl{"User"}."))";
  } else {
    die("No group or user given");
  }
  my $results = $ldap->search(	# perform a search
			   base   => $basedn,
			   filter => $filter,
			  );
  foreach my $entry ($results->all_entries) {
    my $dn = $entry->dn;
    # $entry->dump;
    # print source address
    my $sad = todotquad($entry->get_value("SrcIpStart"));
    $sad .="-". todotquad($entry->get_value("SrcIpEnd"));
    # print dest address
    my $dad = todotquad($entry->get_value("DstIpStart"));
    $dad .="-". todotquad($entry->get_value("DstIpEnd"));
    # print source port
    my $sport = $entry->get_value("SrcPortStart").":".$entry->get_value("SrcPortEnd");
    # print dest port
    my $dport = $entry->get_value("DstPort");
    if (! $dport){
    $dport = $entry->get_value("DstPortStart").":".$entry->get_value("DstPortEnd");
    }
    # print groups
    my ($dec)=$entry->get_value("Decision");
    # OSname
    $osname=$entry->get_value("OsName");
    $osversion=$entry->get_value("OsVersion");
    $osrelease=$entry->get_value("OsRelease");
    $appname=$entry->get_value("AppName");
    $appsig=$entry->get_value("AppSig");
    if ($dec) {
      $dec="ACCEPT";
    } else {
      $dec="DROP";
    }
    print "dn: $dn src : $sad $sport dst : $dad $dport OS: $osname $osversion $osrelease App: $appname $appsig $$dec\n";
  }

  $ldap->unbind;		# take down session
  exit;
}
if (defined ($delete)) {
  my $results = $ldap->delete($delete);
  $results->code && warn "failed to delete entry: ", $results->error ;
  $ldap->unbind;		# take down session
  exit;

}else {
  $exit_code = "No List mode";
}
