#!/usr/pkg/bin/perl
# save_group.cgi
# Saves or creates a new group

require './ldap-useradmin-lib.pl';
&error_setup($text{'gsave_err'});
&ReadParse();
$ldap = &ldap_connect();
$schema = $ldap->schema();
&lock_user_files();

if (!$in{'new'}) {
	# Get existing group
	$rv = $ldap->search(base => $in{'dn'},
			    scope => 'base',
			    filter => &group_filter());
	($ginfo) = $rv->all_entries;
	$ginfo || &error($text{'gsave_egone'});
	$olddesc = $ginfo->get_value('description');
	%ogroup = &dn_to_hash($ginfo);
	&can_edit_group(\%ogroup) || &error($text{'gedit_eedit'});
	}
else {
	# Creating a new one
	$access{'gcreate'} || &error($text{'gedit_ecreate'});
	}

if ($in{'delete'}) {
	# Delete the group, but first check if it is anyone's primary group,
	# and ask first
	&ui_print_header(undef, $text{'gdel_title'}, "");

	if ($in{'confirm'}) {
		# Run the before command
		%ghash = &dn_to_hash($ginfo);
		&set_group_envs(\%ghash, 'DELETE_GROUP', undef);
		$merr = &making_changes();
		&error(&text('gsave_emaking', "<tt>$merr</tt>"))
			if (defined($merr));

		# Delete from other modules
		%group = &dn_to_hash($ginfo);
		if ($in{'others'}) {
			print "$text{'gdel_other'}<br>\n";
			&useradmin::other_modules("useradmin_delete_group",
						  \%group);
			print "$text{'gdel_done'}<p>\n";
			}

		# Delete the LDAP entry
		print "$text{'gdel_group'}<br>\n";
		$rv = $ldap->delete($in{'dn'});
		if ($rv->code) {
			&error(&text('gsave_edelete', $rv->error));
			}
		print "$text{'gdel_done'}<p>\n";

		&made_changes();

		%p = ( %in, %group );
		&webmin_log('delete', 'group', $group{'group'}, \%p);
		}
	else {
		# Check if any user has this group as his primary
		$gid = $ginfo->get_value("gidNumber");
		$group = $ginfo->get_value("cn");
		foreach $u (&list_users()) {
			if ($u->{'gid'} == $gid) {
				$found = $u->{'user'};
				last;
				}
			}

		if ($found) {
			# Cannot delete
			print "<p><b>",&text('gdel_eprimary', $found),
			      "</b> <p>\n";
			}
		else {
			# Ask the user if he is sure
			print &ui_confirmation_form(
				"save_group.cgi",
				&text('gdel_sure', $group),
				[ [ "dn", $in{'dn'} ],
				  [ "delete", 1 ] ],
				[ [ "confirm", $text{'gdel_del'} ] ],
				&ui_checkbox("others", 1,
					$text{'gdel_dothers'},
					$mconfig{'default_other'}),
				undef);
			}
		}

	$ldap->unbind();
	&ui_print_footer("index.cgi?mode=groups", $text{'index_greturn'});
	exit;
	}
elsif ($in{'raw'}) {
	# Show all LDAP attributes for user
	&redirect("raw.cgi?group=1&dn=".&urlize($in{'dn'}));
	exit;
	}

# Strip out \n characters in inputs
$in{'group'} =~ s/\r|\n//g;
$in{'pass'} =~ s/\r|\n//g;
$in{'encpass'} =~ s/\r|\n//g;
$in{'gid'} =~ s/\r|\n//g;

# Validate inputs
if ($in{'new'}) {
	$in{'group'} =~ /^[^:\t]+$/ ||
		&error(&text('gsave_ebadname', $in{'group'}));
	$group = $in{'group'};
	&check_group_used($ldap, $group) &&
		&error(&text('gsave_einuse', $group));
	}
else {
	$group = $in{'group'};
	$oldgroup = $ginfo->get_value("cn");
	}
$in{'gid'} =~ /^[0-9]+$/ || &error(&text('gsave_egid', $in{'gid'}));
$gid = $in{'gid'};
$desc = $in{'desc'} || undef;
@members = split(/\r?\n/, $in{members});
if ($in{'new'} || $oldgroup ne $group) {
	# Check for collision
	defined(&all_getgrnam($group)) &&
		&error(&text('gsave_einuse', $group));
	}

# Check for GID clash
if ($in{'new'} && !$access{'gmultiple'}) {
	&check_gid_used($ldap, $gid) &&
		&error($text{'gsave_egidused2'});
	}

$pfx = $config{'md5'} == 1 || $config{'md5'} == 3 ? "{md5}" :
       $config{'md5'} == 4 ? "{ssha}" :
       $config{'md5'} == 5 ? "{sha}" :
       $config{'md5'} == 0 ? "{crypt}" : "";
if ($in{'passmode'} == 0) {
	$pass = "";
	}
elsif ($in{'passmode'} == 1) {
	$pass = $in{'encpass'};
	$pass = $pfx.$pass if ($pass !~ /^\{[a-z0-9]+\}/i && $pfx);
	}
elsif ($in{'passmode'} == 2) {
	$pass = $pfx.&encrypt_password($in{'pass'});
	}

local %ghash = ( 'group' => $group,
		 'gid' => $gid,
		 'pass' => $pass,
		 'members' => join(",", @members) );

if (!$in{'new'}) {
	# Run the pre-change command
	&set_group_envs(\%ghash, 'MODIFY_GROUP',
		       $in{'passmode'} == 3 ? $in{'pass'} : "");
	$merr = &making_changes();
	&error(&text('gsave_emaking', "<tt>$merr</tt>"))
		if (defined($merr));

	# Change GID on files if needed
	$oldgid = $ginfo->get_value("gidNumber");
	if ($gid != $oldgid && $in{'chgid'}) {
		if ($in{'chgid'} == 1) {
			# Do all the home directories of users in this group
			setpwent();
			while(@tmp = getpwent()) {
                                if ($tmp[3] == $oldgid ||
                                    &indexof($tmp[0], @members) >= 0) {
                                        &useradmin::recursive_change(
                                                $tmp[7], -1, $oldgid,
                                                         -1, $gid);
                                        }
                                }
                        endpwent();
			}
		else {
			# Do all files in this group from the root dir
			&useradmin::recursive_change("/", -1, $oldgid,
						     -1, $gid);
			}
		}

	# Work out old settings
	@classes = $ginfo->get_value("objectClass");
	$wassamba = &indexof($samba_group_class, @classes) >= 0;

	if ($wassamba && !$in{'samba'}) {
		# Remove Samba attributes
		@classes = grep { $_ ne $samba_group_class } @classes;
		push(@rprops,
		     $samba_group_class eq "sambaGroup" ? ( "rid" )
			: ( "sambaSID", "sambaGrouptype" ));
		}
	elsif (!$wassamba && $in{'samba'}) {
		# Add Samba attributes
		push(@classes, $samba_group_class);
		push(@props, "rid", $gid*2+1001)
			if (&in_schema($schema, "rid") &&
			    $samba_group_schema == 2);
		push(@props, "sambaSID",
			     "$config{'samba_domain'}-".($gid*2+1001))
			if (&in_schema($schema, "sambaSID") &&
			    $samba_group_schema == 3);
		push(@props, "sambaGrouptype", 2)
			if (&in_schema($schema, "sambaGrouptype") &&
			    $samba_group_schema == 3);
		}

	# Add extra fields
	&parse_extra_fields($config{'group_fields'}, \@props, \@rprops, $ldap);

	# Get the properties for modified groups
	push(@props, &split_props($config{'group_mod_props'}, \%ghash));

	# Update the LDAP database
	@classes = &unique(@classes);
	@rprops = grep { defined($ginfo->get_value($_)) } @rprops;

	if ($oldgroup ne $group) {
	        # Need to rename the LDAP dn itself, first
	        $base = &get_group_base();
	        $newdn = "cn=$group,$base";
	        $rv = $ldap->moddn($in{'dn'}, newrdn => "cn=$group");
	        if ($rv->code) {
	                &error(&text('gsave_emoddn', $rv->error));
	                }
	        }
	else {
		$newdn = $in{'dn'};
		}

	# Add or remove description
	if ($desc) {
		push(@props, "description" => $desc);
		}
	elsif ($olddesc) {
		push(@rprops, "description");
		}
	if (!$pass && $ginfo->get_value("userPassword")) {
		push(@rprops, "userPassword");
		}

	# Update group properties
	$rv = $ldap->modify($newdn, replace =>
			    { "gidNumber" => $gid,
			      "cn" => $group,
			      $pass ? ( "userPassword" => $pass ) : ( ),
			      @members ? ( "memberUid" => \@members ) : ( ),
			      @props,
			      "objectClass" => \@classes },
			    'delete' => \@rprops);
	if ($rv->code) {
		&error(&text('gsave_emod', $rv->error))
		}
	if (!@members && $ginfo->get_value("memberUid")) {
		$rv = $ldap->modify($in{'dn'}, delete => [ "memberUid" ] );
		if ($rv->code) {
			&error(&text('gsave_emod', $rv->error))
			}
		}

	}
else {
	# Run the pre-change command
	&set_group_envs(\%ghash, 'CREATE_GROUP',
		       $in{'passmode'} == 3 ? $in{'pass'} : "");
	$merr = &making_changes();
	&error(&text('gsave_emaking', "<tt>$merr</tt>"))
		if (defined($merr));

	# Parse extra fields
	&parse_extra_fields($config{'group_fields'}, \@props, \@rprops,
			    $ldap, $in{'dn'});

	# Get the properties for new groups
	push(@props, &split_props($config{'group_props'}, \%ghash));

	# Add to the LDAP database
	$base = &get_group_base();
	$newdn = "cn=$group,$base";
	@classes = ( &def_group_obj_class() );
	push(@classes, split(/\s+/, $config{'gother_class'}));
	if ($in{'samba'}) {
		push(@classes, $samba_group_class);
		push(@props, "rid", $gid*2+1001)
			if (&in_schema($schema, "rid") &&
			    $samba_group_class eq 'sambaGroup');
		push(@props, "sambaSID",
			     "$config{'samba_domain'}-".($gid*2+1001))
			if (&in_schema($schema, "sambaSID") &&
			    $samba_group_schema == 3);
		push(@props, "sambaGrouptype", 2)
			if (&in_schema($schema, "sambaGrouptype") &&
			    $samba_group_schema == 3);
		}
	if ($desc) {
		push(@props, "description" => $desc);
		}
	$rv = $ldap->add($newdn, attr =>
			 [ "cn" => $group,
			   "gidNumber" => $gid,
			   $pass ? ( "userPassword" => $pass ) : ( ),
			   @members ? ( "memberUid" => \@members ) : ( ),
			   @props,
			   "objectClass" => \@classes ] );
	if ($rv->code) {
		&error(&text('gsave_eadd', $rv->error));
		}
	}

&made_changes();

# Run other module's scripts
if ($in{'others'}) {
	if (!$in{'new'}) {
		&useradmin::other_modules("useradmin_modify_group", \%group, \%ogroup);
		}
	else {
		&useradmin::other_modules("useradmin_create_group", \%group);
		}
	}

delete($in{'pass'});
delete($in{'encpass'});
$ldap->unbind();
&unlock_user_files();
&webmin_log(!$in{'new'} ? 'modify' : 'create', 'group', $group, \%in);

# Bounce back to the list
&redirect("index.cgi?mode=groups");

# dn_to_hash(&ldap-object)
sub dn_to_hash
{
local %group = ( 'group' => $_[0]->get_value("cn"),
                 'gid' => $_[0]->get_value("gidNumber"),
                 'pass' => $_[0]->get_value("userPassword"),
		 'members' => join(",", $_[0]->get_value("memberUid")) );
$group{'pass'} =~ s/^{[a-z0-9]+}//i;
return %group;
}

