# Functions for managing a domain's Unix user

$feature_depends{'unix'} = [ 'dir' ];

# setup_unix(&domain)
# Creates the Unix user and group for a domain
sub setup_unix
{
local $tmpl = &get_template($_[0]->{'template'});
&obtain_lock_unix($_[0]);
&require_useradmin();
local (%uinfo, %ginfo);

# Do some sanity checks
if ($_[0]->{'user'} eq '') {
	&error("Domain is missing Unix username!");
	}
if ($_[0]->{'group'} eq '' && &mail_system_needs_group()) {
	&error("Domain is missing Unix group name!");
	}

# Check if the UID or GID has been allocated to someone else, and if so
# re-allocate them. Also allocate if they haven't been done yet.
local @allusers = &list_all_users();
local ($clash) = grep { $_->{'uid'} == $_[0]->{'uid'} } @allusers;
if ($clash || !$_[0]->{'uid'}) {
	local (%taken, %utaken);
	&build_taken(\%taken, \%utaken, \@allusers);
	$_[0]->{'uid'} = &allocate_uid(\%taken);
	}
local @allgroups = &list_all_groups();
local ($gclash) = grep { $_->{'gid'} == $_[0]->{'gid'} } @allgroups;
if ($gclash || !$_[0]->{'gid'}) {
	local (%gtaken, %ggtaken);
	&build_group_taken(\%gtaken, \%ggtaken, \@allgroups);
	$_[0]->{'gid'} = &allocate_gid(\%gtaken);
	}
$_[0]->{'ugid'} = $_[0]->{'gid'} if ($_[0]->{'ugid'} eq '');

if (&mail_system_needs_group() || $_[0]->{'gid'} == $_[0]->{'ugid'}) {
	# Create the group
	&$first_print(&text('setup_group', $_[0]->{'group'}));
	%ginfo = ( 'group', $_[0]->{'group'},
		   'gid', $_[0]->{'gid'},
		 );
	eval {
		local $main::error_must_die = 1;
		&foreign_call($usermodule, "set_group_envs", \%ginfo,
			      'CREATE_GROUP');
		&foreign_call($usermodule, "making_changes");
		&foreign_call($usermodule, "create_group", \%ginfo);
		&foreign_call($usermodule, "made_changes");
		};
	if (@$ || !defined(getgrnam($_[0]->{'group'}))) {
		&delete_partial_group(\%ginfo);
		&$second_print($@ ? &text('setup_ecrgroup2', $@)
				  : $text{'setup_ecrgroup'});
		&release_lock_unix($_[0]);
		return 0;
		}
	&$second_print($text{'setup_done'});
	}
else {
	# Server has no group!
	delete($_[0]->{'gid'});
	delete($_[0]->{'group'});
	}

# Then the user
&$first_print(&text('setup_user', $_[0]->{'user'}));
%uinfo = ( 'user', $_[0]->{'user'},
	   'uid', $_[0]->{'uid'},
	   'gid', $_[0]->{'ugid'},
	   'pass', $_[0]->{'enc_pass'} ||
		   &foreign_call($usermodule, "encrypt_password",
				 $_[0]->{'pass'}),
	   'real', $_[0]->{'owner'},
	   'home', $_[0]->{'home'},
	   'shell', &default_available_shell('owner'),
	   'mailbox', $_[0]->{'user'},
	   'dom', $_[0]->{'dom'},
	   'dom_prefix', substr($_[0]->{'dom'}, 0, 1),
	   'plainpass', $_[0]->{'pass'},
	   'domainowner', 1,
	   'unix', 1,
	   'email', $_[0]->{'user'}.'\@'.$_[0]->{'dom'},
	 );
&set_pass_change(\%uinfo);
eval {
	local $main::error_must_die = 1;
	&foreign_call($usermodule, "set_user_envs", \%uinfo,
		      'CREATE_USER', $_[0]->{'pass'}, [ ]);
	&foreign_call($usermodule, "making_changes");
	&foreign_call($usermodule, "create_user", \%uinfo);
	&foreign_call($usermodule, "made_changes");
	if ($config{'other_doms'}) {
		&foreign_call($usermodule, "other_modules",
			      "useradmin_create_user", \%uinfo);
		}
	};
if ($@ || !defined(getpwnam($_[0]->{'user'}))) {
	&delete_partial_group(\%ginfo) if (%ginfo);
	&delete_partial_user(\%uinfo);
	&$second_print($@ ? &text('setup_ecruser2', $@)
			  : $text{'setup_ecruser'});
	&release_lock_unix($_[0]);
	return 0;
	}
&$second_print($text{'setup_done'});

# Set the user's quota
if (&has_home_quotas()) {
	&set_server_quotas($_[0]);
	}

&$first_print($text{'setup_usermail2'});
eval {
	# Create virtuser pointing to new user, and possibly generics entry
	local $main::error_must_die = 1;
	if ($_[0]->{'mail'}) {
		local @virts = &list_virtusers();
		local $email = $_[0]->{'user'}."\@".$_[0]->{'dom'};
		local ($virt) = grep { $_->{'from'} eq $email } @virts;
		if (!$virt) {
			$virt = { 'from' => $email,
				  'to' => [ $_[0]->{'user'} ] };
			&create_virtuser($virt);
			&sync_alias_virtuals($_[0]);
			}
		if ($config{'generics'}) {
			&create_generic($_[0]->{'user'}, $email);
			}
		}
	};
if ($@) {
	&$second_print(&text('setup_eusermail2', $@));
	}
else {
	&$second_print($text{'setup_done'});
	}

# Add to denied SSH group and domain owner group. Don't let the failure of
# this block the whole user creation though
&$first_print($text{'setup_usergroups'});
eval {
	local $main::error_must_die = 1;
	&build_denied_ssh_group($_[0]);
	&update_domain_owners_group($_[0]);
	};
if ($@) {
	&$second_print(&text('setup_eusergroups', $@));
	}
else {
	&$second_print($text{'setup_done'});
	}

# Setup resource limits from template
if (defined(&supports_resource_limits) && $tmpl->{'resources'} ne 'none' &&
    &supports_resource_limits()) {
	local $rv = { map { split(/=/, $_) }
		split(/\s+/, $tmpl->{'resources'}) };
	&save_domain_resource_limits($_[0], $rv, 1);
	}

&release_lock_unix($_[0]);
return 1;
}

# modify_unix(&domain, &olddomain)
# Change the password and real name for a domain unix user
sub modify_unix
{
if (!$_[0]->{'pass_set'} &&
    $_[0]->{'user'} eq $_[1]->{'user'} &&
    $_[0]->{'home'} eq $_[1]->{'home'} &&
    $_[0]->{'owner'} eq $_[1]->{'owner'} &&
    $_[0]->{'group'} eq $_[1]->{'group'} &&
    $_[0]->{'quota'} eq $_[1]->{'quota'} &&
    $_[0]->{'uquota'} eq $_[1]->{'uquota'} &&
    $_[0]->{'parent'} eq $_[1]->{'parent'}) {
	# Nothing important has changed, so return now
	return 1;
	}
if (!$_[0]->{'parent'}) {
	# Check for a user change
	&obtain_lock_unix($_[0]);
	&require_useradmin();
	local @allusers = &list_domain_users($_[1]);
	local ($uinfo) = grep { $_->{'user'} eq $_[1]->{'user'} } @allusers;
	local $rv;
	if (defined(&supports_resource_limits) &&
	    &supports_resource_limits() &&
	    ($_[0]->{'user'} ne $_[1]->{'user'} ||
	     $_[0]->{'group'} ne $_[1]->{'group'})) {
		# Get old resource limits, and clear
		$rv = &get_domain_resource_limits($_[1]);
		&save_domain_resource_limits($_[1], { });
		}
	if ($uinfo) {
		local %old = %$uinfo;
		&$first_print($text{'save_user'});
		$uinfo->{'real'} = $_[0]->{'owner'};
		if ($_[0]->{'pass_set'}) {
			# Update the Unix user's password
			local $enc = &foreign_call($usermodule,
				"encrypt_password", $_[0]->{'pass'});
			if ($d->{'disabled'}) {
				# Just keep for later use when enabling
				$d->{'disabled_oldpass'} = $enc;
				}
			else {
				# Set password now
				$uinfo->{'pass'} = $enc;
				}
			delete($d->{'enc_pass'});	# Any stored encrypted
							# password is not valid
			$uinfo->{'plainpass'} = $_[0]->{'pass'};
			&set_pass_change($uinfo);
			&set_usermin_imap_password($uinfo);
			}

		if ($_[0]->{'user'} ne $_[1]->{'user'}) {
			# Unix user was re-named
			$uinfo->{'olduser'} = $_[1]->{'user'};
			$uinfo->{'user'} = $_[0]->{'user'};
			&rename_mail_file($uinfo, \%old);
			&obtain_lock_cron($_[0]);
			&rename_unix_cron_jobs($_[0]->{'user'},
					       $_[1]->{'user'});
			&release_lock_cron($_[0]);
			}

		if ($_[0]->{'home'} ne $_[1]->{'home'}) {
			# Home directory was changed
			$uinfo->{'home'} = $_[0]->{'home'};
			}

		if ($_[0]->{'owner'} ne $_[1]->{'owner'}) {
			# Domain description was changed
			$uinfo->{'real'} = $_[0]->{'owner'};
			}

		&modify_user($uinfo, \%old, undef);

		if ($config{'other_doms'}) {
			&foreign_call($usermodule, "other_modules",
				      "useradmin_modify_user", $uinfo, \%old);
			}

		&$second_print($text{'setup_done'});
		}

	if (&has_home_quotas() && $access{'edit'} == 1) {
		# Update the unix user's and domain's quotas (if changed)
		if ($_[0]->{'quota'} != $_[1]->{'quota'} ||
		    $_[0]->{'uquota'} != $_[1]->{'uquota'}) {
			&$first_print($text{'save_quota'});
			&set_server_quotas($_[0]);
			&$second_print($text{'setup_done'});
			}
		}

	# Check for a group change
	local ($ginfo) = grep { $_->{'group'} eq $_[1]->{'group'} }
			      &list_all_groups();
	if ($ginfo && $_[0]->{'group'} ne $_[1]->{'group'}) {
		local %old = %$ginfo;
		&$first_print($text{'save_group'});
		$ginfo->{'group'} = $_[0]->{'group'};
		&foreign_call($usermodule, "set_group_envs", $ginfo,
					   'MODIFY_GROUP', \%old);
		&foreign_call($usermodule, "making_changes");
		&foreign_call($usermodule, "modify_group", \%old, $ginfo);
		&foreign_call($usermodule, "made_changes");
		&$second_print($text{'setup_done'});
		}

	if ($rv) {
		# Put back resource limits, perhaps under new name
		&save_domain_resource_limits($_[0], $rv);
		}
	&release_lock_unix($_[0]);
	}
elsif ($_[0]->{'parent'} && !$_[1]->{'parent'}) {
	# Unix feature has been turned off .. so delete the user and group
	&delete_unix($_[1]);
	}
}

# delete_unix(&domain)
# Delete the unix user and group for a domain
sub delete_unix
{
if (!$_[0]->{'parent'}) {
	# Get the user object
	&obtain_lock_unix($_[0]);
	&obtain_lock_cron($_[0]);
	&require_useradmin();
	local @allusers = &foreign_call($usermodule, "list_users");
	local ($uinfo) = grep { $_->{'user'} eq $_[0]->{'user'} } @allusers;

	# Zero his quotas
	if ($uinfo) {
		&set_user_quotas($uinfo->{'user'}, 0, 0, $_[0]);
		}

	# Delete his cron jobs
	&delete_unix_cron_jobs($_[0]->{'user'});

	# Delete virtuser and generic
	local @virts = &list_virtusers();
	local $email = $_[0]->{'user'}."\@".$_[0]->{'dom'};
	local ($virt) = grep { $_->{'from'} eq $email } @virts;
	if ($virt) {
		&delete_virtuser($virt);
		&sync_alias_virtuals($_[0]);
		}
	if ($config{'generics'}) {
		local %generics = &get_generics_hash();
		local $g = $generics{$_[0]->{'user'}};
		if ($g) {
			&delete_generic($g);
			}
		}

	# Delete his mail file
	if ($uinfo && !$uinfo->{'nomailfile'}) {
		&delete_mail_file($uinfo);
		}

	# Clear any resource limits
	if (defined(&supports_resource_limits) && &supports_resource_limits()) {
		&save_domain_resource_limits($_[0], { });
		}

	# Delete unix user
	if ($uinfo) {
		&$first_print($text{'delete_user'});
		&delete_user($uinfo, $_[0]);
		if ($config{'other_doms'}) {
			&foreign_call($usermodule, "other_modules",
				      "useradmin_delete_user", $uinfo);
			}
		&$second_print($text{'setup_done'});
		}

	# Delete unix group
	local @allgroups = &foreign_call($usermodule, "list_groups");
	local ($ginfo) = grep { $_->{'group'} eq $_[0]->{'group'} } @allgroups;
	if ($ginfo) {
		&$first_print($text{'delete_group'});
		&foreign_call($usermodule, "set_group_envs", $ginfo, 'DELETE_GROUP');
		&foreign_call($usermodule, "making_changes");
		&foreign_call($usermodule, "delete_group", $ginfo);
		&foreign_call($usermodule, "made_changes");
		&$second_print($text{'setup_done'});
		}
	&release_lock_unix($_[0]);
	&release_lock_cron($_[0]);
	}

# Update any groups
&build_denied_ssh_group(undef, $_[0]);
&update_domain_owners_group(undef, $_[0]);
}

# validate_unix(&domain)
# Check for the Unix user and group
sub validate_unix
{
local ($d) = @_;
return undef if ($d->{'parent'});	# sub-servers have no user
local @users = &list_all_users();
local ($user) = grep { $_->{'user'} eq $d->{'user'} } @users;
return &text('validate_euser', $d->{'user'}) if (!$user);
return &text('validate_euid', $d->{'user'}, $d->{'uid'}, $user->{'uid'})
	if ($d->{'uid'} != $user->{'uid'});
if (&mail_system_needs_group() || $d->{'gid'} == $d->{'ugid'}) {
	local @groups = &list_all_groups();
	local ($group) = grep { $_->{'group'} eq $d->{'group'} } @groups;
	return &text('validate_egroup', $d->{'group'}) if (!$group);
	return &text('validate_egid', $d->{'group'}, $d->{'gid'},
				      $group->{'gid'})
		if ($d->{'gid'} != $group->{'gid'});
	}
return undef;
}

# check_unix_clash(&domain, [field])
sub check_unix_clash
{
return 0 if ($_[0]->{'parent'});	# user already exists!
if (!$_[1] || $_[1] eq 'user') {
	return 1 if (defined(getpwnam($_[0]->{'user'})));
	}
if (!$_[1] || $_[1] eq 'group') {
	return 1 if ($_[0]->{'group'} &&
		     defined(getgrnam($_[0]->{'group'})));
	}
return 0;
}

# disable_unix(&domain)
# Lock out the password of this domain's Unix user
sub disable_unix
{
if (!$_[0]->{'parent'}) {
	&obtain_lock_unix($_[0]);
	&require_useradmin();
	local @allusers = &foreign_call($usermodule, "list_users");
	local ($uinfo) = grep { $_->{'user'} eq $_[0]->{'user'} } @allusers;
	if ($uinfo) {
		&$first_print($text{'disable_unix'});
		&foreign_call($usermodule, "set_user_envs", $uinfo,
			      'MODIFY_USER', "", undef, $uinfo, "");
		&foreign_call($usermodule, "making_changes");
		$_[0]->{'disabled_oldpass'} = $uinfo->{'pass'};
		$uinfo->{'pass'} = $uconfig{'lock_string'};
		&foreign_call($usermodule, "modify_user", $uinfo, $uinfo);
		&foreign_call($usermodule, "made_changes");
		&$second_print($text{'setup_done'});
		}
	&release_lock_unix($_[0]);
	}
}

# enable_unix(&domain)
# Re-enable this domain's Unix user
sub enable_unix
{
if (!$_[0]->{'parent'}) {
	&obtain_lock_unix($_[0]);
	&require_useradmin();
	local @allusers = &foreign_call($usermodule, "list_users");
	local ($uinfo) = grep { $_->{'user'} eq $_[0]->{'user'} } @allusers;
	if ($uinfo) {
		&$first_print($text{'enable_unix'});
		&foreign_call($usermodule, "set_user_envs", $uinfo,
			      'MODIFY_USER', "", undef, $uinfo, "");
		&foreign_call($usermodule, "making_changes");
		$uinfo->{'pass'} = $_[0]->{'disabled_oldpass'};
		delete($_[0]->{'disabled_oldpass'});
		&foreign_call($usermodule, "modify_user", $uinfo, $uinfo);
		&foreign_call($usermodule, "made_changes");
		&$second_print($text{'setup_done'});
		}
	&release_lock_unix($_[0]);
	}
}

# backup_unix(&domain, file)
# Backs up the users crontab file
sub backup_unix
{
local ($d, $file) = @_;
&foreign_require("cron", "cron-lib.pl");
local $cronfile = &cron::cron_file({ 'user' => $d->{'user'} });
&$first_print(&text('backup_cron'));
if (-r $cronfile) {
	&copy_source_dest($cronfile, $file);
	&$second_print($text{'setup_done'});
	}
else {
	&$second_print($text{'backup_cronnone'});
	}
return 1;
}

# restore_unix(&domain, file, &options)
# Update's the domain's unix user's password, description, quotas and cron jobs
sub restore_unix
{
local ($d, $file, $opts) = @_;
&obtain_lock_unix($_[0]);
&obtain_lock_cron($_[0]);
&$first_print($text{'restore_unixuser'});

# Also re-set quotas
if (&has_home_quotas()) {
	&set_server_quotas($_[0]);
	}

# And update password and description
&require_useradmin();
local @allusers = &foreign_call($usermodule, "list_users");
local ($uinfo) = grep { $_->{'user'} eq $d->{'user'} } @allusers;
if ($uinfo && !$d->{'parent'}) {
	local $olduinfo = { %$uinfo };
	$uinfo->{'real'} = $d->{'owner'};
	local $enc = &foreign_call($usermodule, "encrypt_password",
			$d->{'pass'});
	$uinfo->{'pass'} = $enc;
	&set_pass_change($uinfo);
	&foreign_call($usermodule, "set_user_envs",
		      $uinfo, 'MODIFY_USER', $d->{'pass'}, undef, $olduinfo);
	&foreign_call($usermodule, "making_changes");
	&foreign_call($usermodule, "modify_user", $uinfo, $uinfo);
	&foreign_call($usermodule, "made_changes");
	}
&$second_print($text{'setup_done'});

# Copy cron jobs file
if (-r $file) {
	&$first_print($text{'restore_cron'});
	&foreign_require("cron", "cron-lib.pl");
	&copy_source_dest($file, $cron::cron_temp_file);
	&cron::copy_crontab($d->{'user'});
	&$second_print($text{'setup_done'});
	}
&release_lock_unix($_[0]);
&release_lock_cron($_[0]);

return 1;
}

# bandwidth_unix(&domain, start, &bw-hash)
# Updates the bandwidth count for FTP traffic by the domain's unix user, and
# any mail users with FTP access
sub bandwidth_unix
{
local $log = $config{'bw_ftplog'} || &get_proftpd_log();
if ($log) {
	local @users;
	if (!$_[0]->{'parent'}) {
		# Only do the domain owner if this is the parent domain, to
		# avoid double-counting in subdomains
		push(@users, $_[0]->{'user'});
		}
	foreach $u (&list_domain_users($_[0], 0, 1, 1, 1)) {
		push(@users, $u->{'user'}) if ($u->{'unix'});
		}
	return &count_ftp_bandwidth($log, $_[1], $_[2], \@users, "ftp",
				    $config{'bw_ftplog_rotated'});
	}
else {
	return $_[1];
	}
}

# show_template_unix(&tmpl)
# Outputs HTML for editing unix-user-related template options
sub show_template_unix
{
local ($tmpl) = @_;

# Quota-related defaults .. starting with quota type (hard or soft)
print &ui_table_row(&hlink($text{'tmpl_quotatype'}, "template_quotatype"),
    &ui_radio("quotatype", $tmpl->{'quotatype'},
	      [ $tmpl->{'default'} ? ( ) : ( [ "", $text{'default'} ] ),
		[ "hard", $text{'tmpl_hard'} ],
		[ "soft", $text{'tmpl_soft'} ] ]));

# Default domain quota
print &ui_table_row(&hlink($text{'tmpl_quota'}, "template_quota"),
    &none_def_input("quota", $tmpl->{'quota'}, $text{'tmpl_quotasel'}, 1,
		    0, undef, [ "quota", "quota_units" ])."\n".
    &quota_input("quota", $tmpl->{'quota'} eq "none" ?
				"" : $tmpl->{'quota'}, "home"));

# Default admin user quota
print &ui_table_row(&hlink($text{'tmpl_uquota'}, "template_uquota"),
    &none_def_input("uquota", $tmpl->{'uquota'}, $text{'tmpl_quotasel'}, 1,
		    0, undef, [ "uquota", "uquota_units" ])."\n".
    &quota_input("uquota", $tmpl->{'uquota'} eq "none" ?
				"" : $tmpl->{'uquota'}, "home"));

# Domain owner primary group
print &ui_table_row(&hlink($text{'tmpl_ugroup'}, "template_ugroup_mode"),
    &none_def_input("ugroup", $tmpl->{'ugroup'}, $text{'tmpl_ugroupsel'},
		    0, 0, undef, [ "ugroup" ])."\n".
    &ui_textbox("ugroup", $tmpl->{'ugroup'} eq "none" ?
				"" : $tmpl->{'ugroup'}, 13)."\n".
    &group_chooser_button("ugroup", 0, 1));
}

# parse_template_unix(&tmpl)
# Updates unix-user-related template options from %in
sub parse_template_unix
{
local ($tmpl) = @_;

# Save quota-related defaults
$tmpl->{'quotatype'} = $in{'quotatype'};
$tmpl->{'quota'} = &parse_none_def("quota");
if ($in{"quota_mode"} == 2) {
	$in{'quota'} =~ /^[0-9\.]+$/ || &error($text{'tmpl_equota'});
	$tmpl->{'quota'} = &quota_parse("quota", "home");
	}
$tmpl->{'uquota'} = &parse_none_def("uquota");
if ($in{"uquota_mode"} == 2) {
	$in{'uquota'} =~ /^[0-9\.]+$/ || &error($text{'tmpl_euquota'});
	$tmpl->{'uquota'} = &quota_parse("uquota", "home");
	}

# Save domain owner primary group option
$tmpl->{'ugroup'} = &parse_none_def("ugroup");
if ($in{"ugroup_mode"} == 2) {
	getgrnam($in{'ugroup'}) || &error($text{'tmpl_eugroup'});
	}
}

# get_unix_shells()
# Returns a list of tuples containing shell types and paths, like :
# [ 'nologin', '/dev/null' ], [ 'ftp', '/bin/false' ], [ 'ssh', '/bin/sh' ]
sub get_unix_shells
{
# Read FTP-capable shells
local @rv;
local $_;
local @shells;
open(SHELLS, "/etc/shells");
while(<SHELLS>) {
	s/\r|\n//g;
	s/#.*$//;
	push(@shells, $_) if (/\S/);
	}
close(SHELLS);
local %shells = map { $_, 1 } @shells;

# Find no-login shells
local @nologin = ($config{'shell'}, '/dev/null', '/sbin/nologin',
		  '/bin/nologin', '/sbin/noshell', '/bin/noshell');
push(@rv, map { [ 'nologin', $_ ] } grep { !$shells{$_} } @nologin);

# Find a good FTP-capable shell
local @ftp = ( $config{'ftp_shell'}, '/bin/false', '/bin/true',
	       '/sbin/nologin', '/bin/nologin' );
push(@rv, map { [ 'ftp', $_ ] } grep { $shells{$_} } @ftp);

# Find FTP and SSH login shells
foreach my $s (keys %shells) {
	if (&indexof($s, @nologin) < 0 && &indexof($s, @ftp) < 0) {
		push(@rv, [ 'ssh', $s ]);
		}
	}
return @rv;
}

# build_denied_ssh_group([&new-domain])
# Update the deniedssh Unix group's membership list with all domain owners
# who don't get to login (based on their shell)
sub build_denied_ssh_group
{
local ($newd, $deld) = @_;
return 0 if ($config{'nodeniedssh'});	# Disabled in config

# First make sure the group exists
&obtain_lock_unix($_[0]);
&require_useradmin();
local @allgroups = &list_all_groups();
local ($group) = grep { $_->{'group'} eq $denied_ssh_group } @allgroups;
if (!$group) {
	&release_lock_unix($_[0]);
	return 0;
	}

# Find domain owners who can't login
local @shells = &list_available_shells();
foreach my $d (&list_domains(), $newd) {
	next if ($d->{'parent'} || !$d->{'unix'} || $d eq $deld);
	local $user = &get_domain_owner($d);
	local ($sinfo) = grep { $_->{'shell'} eq $user->{'shell'} } @shells;
	if ($sinfo && $sinfo->{'id'} ne 'ssh') {
		# On the denied list..
		push(@members, $user->{'user'});
		}
	}

# Update the group
local $oldgroup = { %$group };
$group->{'members'} = join(",", &unique(@members));
if ($group->{'members'} ne $oldgroup->{'members'}) {
	&foreign_call($group->{'module'}, "set_group_envs", $group,
				   	  'MODIFY_GROUP', $oldgroup);
	&foreign_call($group->{'module'}, "making_changes");
	&foreign_call($group->{'module'}, "modify_group", $oldgroup, $group);
	&foreign_call($group->{'module'}, "made_changes");
	}

&release_lock_unix($_[0]);
return 1;
}

# update_domain_owners_group([&new-domain], [&deleting-domain])
# If configure, update the member list of a secondary group which should
# contain all domain owners.
sub update_domain_owners_group
{
local ($newd, $deld) = @_;
return 0 if (!$config{'domains_group'});

# First make sure the group exists
&obtain_lock_unix($_[0]);
&require_useradmin();
local @allgroups = &list_all_groups();
local ($group) = grep { $_->{'group'} eq $config{'domains_group'} } @allgroups;
if (!$group) {
	&release_lock_unix($_[0]);
	return 0;
	}

# Find domain owners with Unix logins
local @members;
foreach my $d (&list_domains(), $newd) {
	if ($d->{'unix'} && $d ne $deld) {
		push(@members, $d->{'user'});
		}
	}

# Update the group
local $oldgroup = { %$group };
$group->{'members'} = join(",", &unique(@members));
if ($group->{'members'} ne $oldgroup->{'members'}) {
	&foreign_call($group->{'module'}, "set_group_envs", $group,
				      'MODIFY_GROUP', $oldgroup);
	&foreign_call($group->{'module'}, "making_changes");
	&foreign_call($group->{'module'}, "modify_group", $oldgroup, $group);
	&foreign_call($group->{'module'}, "made_changes");
	}
&release_lock_unix($_[0]);
}

sub startstop_unix
{
if (!$config{'ftp'} && &foreign_installed("proftpd")) {
	# Even if the FTP feature is not enabled, show the proftpd start/stop
	# buttons.
	return &startstop_ftp();
	}
return ( );
}

sub stop_service_unix
{
return &stop_service_ftp();
}

sub start_service_unix
{
return &start_service_ftp();
}

# delete_partial_group(&group)
# Deletes a group that may not have been created completely
sub delete_partial_group
{
local ($group) = @_;
eval {
	local $main::error_must_die = 1;
	local @allgroups = &list_all_groups();
	local ($ginfo) = grep { $_->{'group'} eq $group->{'group'} &&
				$_->{'module'} eq $usermodule } @allgroups;
	if ($ginfo) {
		&foreign_call($ginfo->{'module'}, "set_group_envs", $ginfo,
			      'DELETE_GROUP');
		&foreign_call($ginfo->{'module'}, "making_changes");
		&foreign_call($ginfo->{'module'}, "delete_group", $ginfo);
		&foreign_call($ginfo->{'module'}, "made_changes");
		}
	};
}

# delete_partial_user(&user)
# Deletes a user that may not have been created completely
sub delete_partial_user
{
local ($user) = @_;
eval {
	local $main::error_must_die = 1;
	local @allusers = &list_all_users();
	local ($uinfo) = grep { $_->{'user'} eq $user->{'user'} &&
				$_->{'module'} eq $usermodule } @allusers;
	if ($uinfo) {
		&foreign_call($uinfo->{'module'}, "set_user_envs", $uinfo,
			      'DELETE_USER');
		&foreign_call($uinfo->{'module'}, "making_changes");
		&foreign_call($uinfo->{'module'}, "delete_user", $uinfo);
		&foreign_call($uinfo->{'module'}, "made_changes");
		}
	};
}

# Lock all Unix password files
sub obtain_lock_unix
{
&obtain_lock_anything();
if ($main::got_lock_unix == 0) {
	&require_useradmin();
	&foreign_call($usermodule, "lock_user_files");
	undef(@useradmin::list_users_cache);
	undef(@useradmin::list_groups_cache);
	undef(%main::soft_home_quota);
	undef(%main::hard_home_quota);
	undef(%main::used_home_quota);
	undef(%main::soft_mail_quota);
	undef(%main::hard_mail_quota);
	if (defined(&supports_resource_limits)) {
		# Lock resource limits file too
		if ($gconfig{'os_type'} =~ /-linux$/) {
			&lock_file($linux_limits_config);
			undef(@get_linux_limits_config_cache);
			}
		}
	}
$main::got_lock_unix++;
}

# Unlock all Unix password files
sub release_lock_unix
{
if ($main::got_lock_unix == 1) {
	&require_useradmin();
	&foreign_call($usermodule, "unlock_user_files");
	if (defined(&supports_resource_limits)) {
		if ($gconfig{'os_type'} =~ /-linux$/) {
			&unlock_file($linux_limits_config);
			}
		}
	}
$main::got_lock_unix-- if ($main::got_lock_unix);
&release_lock_anything();
}

# obtain_lock_cron(&domain)
# Locks a domain's user's crontab file, and root's
sub obtain_lock_cron
{
local ($d) = @_;
&obtain_lock_anything($d);
foreach my $u ($d ? ( $d->{'user'} ) : ( ), 'root') {
	if ($main::got_lock_cron_user{$u} == 0) {
		&foreign_require("cron", "cron-lib.pl");
		&lock_file(&cron::cron_file({ 'user' => $u }));
		}
	$main::got_lock_cron_user{$u}++;
	}
}

# release_lock_cron(&domain)
# Un-locks a domain's user's crontab file
sub release_lock_cron
{
local ($d) = @_;
foreach my $u ($d ? ( $d->{'user'} ) : ( ), 'root') {
	if ($main::got_lock_cron_user{$u} == 1) {
		&foreign_require("cron", "cron-lib.pl");
		&unlock_file(&cron::cron_file({ 'user' => $u }));
		}
	$main::got_lock_cron_user{$u}--
		if ($main::got_lock_cron_user{$u});
	}
&release_lock_anything($d);
}

$done_feature_script{'unix'} = 1;

1;

