#!/usr/local/bin/perl
# save_shared.cgi
# Update, create or delete a shared network

require './dhcpd-lib.pl';
require './params-lib.pl';
&ReadParse();
&lock_file($config{'dhcpd_conf'});
($par, $sha, $indent) = &get_branch('sha');
$parconf = $par->{'members'};

# check acls
%access = &get_module_acl();
&error_setup("<blink><font color=red>$text{'eacl_aviol'}</font></blink>");
if ($in{'delete'}) {
	&error("$text{'eacl_np'} $text{'eacl_pdn'}")
		if !&can('rw', \%access, $sha, 1);
	}
elsif ($in{'options'}) {
	&error("$text{'eacl_np'} $text{'eacl_psn'}")
		if !&can('r', \%access, $sha);
	}
elsif ($in{'new'}) {
	&error("$text{'eacl_np'} $text{'eacl_pin'}") 
		unless &can('c', \%access, $sha) && &can('rw', \%access, $par);
	# restrict duplicates
	if($access{'uniq_sha'}) {
		foreach $s (&find("shared-network", &get_config())) {
			&error("$text{'eacl_np'} $text{'eacl_uniq'}")
				if lc $s->{'values'}->[0] eq lc $in{'name'};
			}
		}
	}
else {
	&error("$text{'eacl_np'} $text{'eacl_pun'}")
		if !&can('rw', \%access, $sha);
	}

# save
if ($in{'options'}) {
	# Redirect to client options
	&redirect("edit_options.cgi?idx=$in{'idx'}");
	exit;
	}
else {
	if ($in{'delete'}) {
		$whatfailed = $text{'sshared_faildel'};
		}
	else {
		$whatfailed = $text{'sshared_failsave'};
		$in{'name'} =~ /^\S+$/ ||
			&error($text{'sshared_invalidsname'});
		$sha->{'values'} = [ $in{'name'} ];
	}

	# Move hosts, groups and subnets into or out of this shared network
	@wasin = &find("host", $sha->{'members'});
	foreach $hn (split(/\0/, $in{'hosts'})) {
		if ($hn =~ /(\d+),(\d+)/) {
			push(@nowin, $parconf->[$2]->{'members'}->[$1]);
			$nowpr{$parconf->[$2]->{'members'}->[$1]} =
				$parconf->[$2];
			}
		elsif ($hn =~ /(\d+),/) {
			push(@nowin, $parconf->[$1]);
			$nowpr{$parconf->[$1]} = $par;
			}
		if ($nowin[$#nowin]->{'name'} ne "host") {
			&error($text{'sgroup_echanged'});
			}
		}
	@wasgin = &find("group", $sha->{'members'});
	foreach $gn (split(/\0/, $in{'groups'})) {
		if ($gn =~ /(\d+),(\d+)/) {
			push(@nowgin, $parconf->[$2]->{'members'}->[$1]);
			$nowgpr{$parconf->[$2]->{'members'}->[$1]} =
				$parconf->[$2];
			}
		elsif ($gn =~ /(\d+),/) {
			push(@nowgin, $parconf->[$1]);
			$nowgpr{$parconf->[$1]} = $par;
			}
		if ($nowgin[$#nowgin]->{'name'} ne "group") {
			&error($text{'sgroup_echanged'});
			}
		}
	@wasuin = &find("subnet", $sha->{'members'});
	foreach $un (split(/\0/, $in{'subnets'})) {
		if ($un =~ /(\d+),(\d+)/) {
			push(@nowuin, $parconf->[$2]->{'members'}->[$1]);
			$nowupr{$parconf->[$2]->{'members'}->[$1]} =
				$parconf->[$2];
			}
		elsif ($un =~ /(\d+),/) {
			push(@nowuin, $parconf->[$1]);
			$nowupr{$parconf->[$1]} = $par;
			}
		if ($nowuin[$#nowuin]->{'name'} ne "subnet") {
			&error($text{'sgroup_echanged'});
			}
		}

	$whatfailed = "<blink><font color=red>$text{'eacl_aviol'}</font></blink>";
	foreach $h (&unique(@wasin, @nowin)) {
		$was = &indexof($h, @wasin) != -1;
		$now = &indexof($h, @nowin) != -1;

		# per-host ACLs for new or updated hosts
		if ($was != $now && !&can('rw', \%access, $h)) {
			&error("$text{'eacl_np'} $text{'eacl_pun'}");
			}
		if ($was && !$now) {
			# Move out of the shared network
			&save_directive($sha, [ $h ], [ ], 0);
			&save_directive($par, [ ], [ $h ], 0);
			}
		elsif ($now && !$was) {
			# Move into the shared network (maybe from another)
			&save_directive($nowpr{$h}, [ $h ], [ ], 0);
			&save_directive($sha, [ ], [ $h ], 1);
			}
		}
	foreach $g (&unique(@wasgin, @nowgin)) {
		$was = &indexof($g, @wasgin) != -1;
		$now = &indexof($g, @nowgin) != -1;

		# per-group ACLs for new or updated groups
		if ($was != $now && !&can('rw', \%access, $g)) {
			&error("$text{'eacl_np'} $text{'eacl_pun'}");
			}	
		if ($was && !$now) {
			# Move out of the shared network
			&save_directive($sha, [ $g ], [ ], 0);
			&save_directive($par, [ ], [ $g ], 0);
			}
		elsif ($now && !$was) {
			# Move into the shared network (maybe from another)
			&save_directive($nowgpr{$g}, [ $g ], [ ], 0);
			&save_directive($sha, [ ], [ $g ], 1);
			}
		}
	foreach $u (&unique(@wasuin, @nowuin)) {
		$was = &indexof($u, @wasuin) != -1;
		$now = &indexof($u, @nowuin) != -1;

		# per-subnet ACLs for new or updated subnetss
		if ($was != $now && !&can('rw', \%access, $u)) {
			&error("$text{'eacl_np'} $text{'eacl_pun'}");
			}		 
		if ($was && !$now) {
			# Move out of the shared network
			&save_directive($sha, [ $u ], [ ], 0);
			&save_directive($par, [ ], [ $u ], 0);
			if ($par->{'name'} eq "shared-network") {
				&fix_sequence($par);
				}
			}
		elsif ($now && !$was) {
			# Move into the shared network (maybe from another)
			&save_directive($nowupr{$u}, [ $u ], [ ], 0);
			&save_directive($sha, [ ], [ $u ], 1);
			if ($nowupr{$u}->{'name'} eq "shared-network") {
				&check_subnets($nowupr{$u});
				}
			}
		}
	&check_subnets($sha);
	&fix_sequence($sha);

	if (!$in{'delete'}) {
		&parse_params($sha);

		if ($in{'new'}) {
			# Add this shared net
			&save_directive($par, [ ], [ $sha ], 0);
			}
		else {
			# Update shared net
			&save_directive($par, [ $sha ], [ $sha ], 0);
			}
		}
	}
&flush_file_lines();
if ($in{'delete'}) {
	# Delete this net
	if ($in{'hosts'} eq "" && $in{'groups'} eq "" && $in{'subnets'} eq "") {
		&save_directive($par, [ $sha ], [ ], 0);
		&flush_file_lines();
		}
	else {
		&unlock_file($config{'dhcpd_conf'});
		&redirect("confirm_delete.cgi?idx=$in{'idx'}\&type=0");
		exit;
		}
	}
&unlock_file($config{'dhcpd_conf'});
&webmin_log($in{'delete'} ? 'delete' : $in{'new'} ? 'create' : 'modify',
	    'shared', $sha->{'values'}->[0], \%in);
&redirect("");

# check whether thist shared network contains any subnet
sub check_subnets
{
local(@subnets);
@subnets = &find("subnet", $_[0]->{'members'});
if (@subnets == 0) {
	$whatfailed = $text{'sshared_failsave'};
	&error(&text('sshared_nosubnet', $_[0]->{'values'}->[0]));
	}
}

# force hosts and groups to follow subnets
sub fix_sequence
{
local(@subnets, $max, $u, $i);
@subnets = &find("subnet", $_[0]->{'members'});
$max = -1;
foreach $u (@subnets) {
	$max = $u->{'index'} > $max ? $u->{'index'} : $max;
	}
for ($i = 0; $i < $max; $i++) {
	$u = $_[0]->{'members'}->[$i];
	if ($u->{'name'} eq "host" || $u->{'name'} eq "group") {
		# move to the end of list
		&save_directive($_[0], [ $u ], [ ], 0);
		&save_directive($_[0], [ ], [ $u ], 0);
		}
	}
}

