sub require_mail
{
return if ($require_mail++);
$can_alias_types{12} = 0;	# this autoreponder for vpopmail only
if ($config{'mail_system'} == 1) {
	# Using sendmail for email
	&foreign_require("sendmail", "sendmail-lib.pl");
	&foreign_require("sendmail", "virtusers-lib.pl");
	&foreign_require("sendmail", "aliases-lib.pl");
	&foreign_require("sendmail", "boxes-lib.pl");
	%sconfig = &foreign_config("sendmail");
	$sendmail_conf = &sendmail::get_sendmailcf();
	$sendmail_vfile = &sendmail::virtusers_file($sendmail_conf);
	($sendmail_vdbm, $sendmail_vdbmtype) =
		&sendmail::virtusers_dbm($sendmail_conf);
	$sendmail_afiles = &sendmail::aliases_file($sendmail_conf);
	if ($config{'generics'}) {
		&foreign_require("sendmail", "generics-lib.pl");
		$sendmail_gfile = &sendmail::generics_file($sendmail_conf);
		($sendmail_gdbm, $sendmail_gdbmtype) =
			&sendmail::generics_dbm($sendmail_conf);
		}
	$can_alias_comments = $virtualmin_pro && &get_webmin_version() >= 1.294;
	$supports_aliascopy = 1;
	}
elsif ($config{'mail_system'} == 0) {
	# Using postfix for email
	&foreign_require("postfix", "postfix-lib.pl");
	&foreign_require("postfix", "boxes-lib.pl");
	%pconfig = &foreign_config("postfix");
	$virtual_type = $postfix::virtual_maps || "virtual_maps";
	$virtual_maps = &postfix::get_real_value($virtual_type);
	@virtual_map_files = &postfix::get_maps_files($virtual_maps);
	$postfix_afiles = [ &postfix::get_aliases_files(
				&postfix::get_real_value("alias_maps")) ];
	if ($config{'generics'}) {
		$canonical_type = "sender_canonical_maps";
		$canonical_maps = &postfix::get_real_value($canonical_type);
		@canonical_map_files =&postfix::get_maps_files($canonical_maps);
		}
	if (defined(&postfix::get_maps_types_files)) {
		# Work out storage type for Postfix
		@virtual_map_backends = map { $_->[0] }
			&postfix::get_maps_types_files($virtual_maps);
		@alias_backends = map { $_->[0] }
			&postfix::get_maps_types_files(
				&postfix::get_real_value("alias_maps"));
		@canonical_backends = map { $_->[0] }
				&postfix::get_maps_types_files($canonical_maps);
		}
	else {
		# Assume hash
		@virtual_map_backends = ( "hash" );
		@alias_backends = ( "hash" );
		@canonical_backends = $canonical_maps ? ( "hash" ) : ( );
		}
	$can_alias_types{9} = 0;	# bounce not yet supported for postfix
	$can_alias_comments = $virtualmin_pro &&
			      &get_webmin_version() >= 1.294;
	if ($can_alias_comments &&
	    $virtual_maps !~ /^hash:/ &&
	    defined(&postfix::can_map_comments) &&
	    !&postfix::can_map_comments($virtual_type)) {
		# Comments not supported by map backend, such as MySQL
		$can_alias_comments = 0;
		}
	if (defined(&postfix::create_postfix_alias)) {
		# New functions that can use maps
		$postfix_list_aliases = \&postfix::list_postfix_aliases;
		$postfix_create_alias = \&postfix::create_postfix_alias;
		$postfix_modify_alias = \&postfix::modify_postfix_alias;
		$postfix_delete_alias = \&postfix::delete_postfix_alias;
		}
	else {
		# Old functions that use files only
		$postfix_list_aliases = \&postfix::list_aliases;
		$postfix_create_alias = \&postfix::create_alias;
		$postfix_modify_alias = \&postfix::modify_alias;
		$postfix_delete_alias = \&postfix::delete_alias;
		}
	$supports_aliascopy = 1;
	}
elsif ($config{'mail_system'} == 2 || $config{'mail_system'} == 4 ||
       $config{'mail_system'} == 5) {
	# Using qmail for email
	&foreign_require("qmailadmin", "qmail-lib.pl");
	%qmconfig = &foreign_config("qmailadmin");
	$can_alias_types{2} = 0;	# cannot use addresses in file
	$can_alias_types{8} = 0;	# cannot use same in other domain
	if ($config{'mail_system'} == 5) {
		$vpopbin = "$config{'vpopmail_dir'}/bin";
		}
	if ($config{'mail_system'} == 4) {
		# Qmail+LDAP can only alias to email addresses
		foreach my $t (3, 4, 5, 6, 7, 9, 10, 11) {
			$can_alias_types{$t} = 0;
			}
		}
	elsif ($config{'mail_system'} == 5) {
		# VPOPMail can use local addresses, files, mailbox, bouncer,
		# deleter and autoresponder
		foreach my $t (5, 6, 7, 8) {
			$can_alias_types{$t} = 0;
			}
		$can_alias_types{12} = 1
			if (&has_command($config{'vpopmail_auto'}));
		}
	else {
		# Plain Qmail cannot use the bouncer
		$can_alias_types{7} = 0;
		$can_alias_types{9} = 0;
		$can_alias_types{10} = 0;
		}
	$can_alias_comments = 0;
	$supports_aliascopy = 0;
	}
}

# list_domain_aliases(&domain, [ignore-plugins])
# Returns just virtusers for some domain
sub list_domain_aliases
{
&require_mail();
local ($u, %foruser);
if ($config{'mail_system'} != 4) {
	# Filter out aliases that point to users
	foreach $u (&list_domain_users($_[0], 0, 1, 1, 1)) {
		local $pop3 = &remove_userdom($u->{'user'}, $_[0]);
		$foruser{$pop3."\@".$_[0]->{'dom'}} = $u->{'user'};
		if ($config{'mail_system'} == 0 && $u->{'user'} =~ /\@/) {
			# Special case for Postfix @ users
			$foruser{$pop3."\@".$_[0]->{'dom'}} =
				&replace_atsign($u->{'user'});
			}
		}
	if ($d->{'mailbox'}) {
		$foruser{$d->{'user'}."\@".$_[0]->{'dom'}} = $d->{'user'};
		}
	}
local @virts = &list_virtusers();
local %ignore;
if ($_[1]) {
	# Get a list to ignore from each plugin
	foreach my $f (@feature_plugins) {
		foreach my $i (&plugin_call($f, "virtusers_ignore", $_[0])) {
			$ignore{lc($i)} = 1;
			}
		}
	}

# Return only virtusers that match this domain,
# which are not for forwarding email for users in the domain,
# and which are not on the plugin ignore list.
return grep { $_->{'from'} =~ /\@(\S+)$/ && $1 eq $_[0]->{'dom'} &&
	      ($foruser{$_->{'from'}} ne $_->{'to'}->[0] ||
	       @{$_->{'to'}} != 1) &&
	      !$ignore{lc($_->{'from'})} } @virts;
}

# setup_mail(&domain, [no-aliases])
# Adds a domain to the list of those accepted by the mail system
sub setup_mail
{
&$first_print($text{'setup_doms'});
&require_mail();
local $tmpl = &get_template($_[0]->{'template'});
if ($config{'mail_system'} == 1) {
	# Just add to sendmail local domains file
	local $conf = &sendmail::get_sendmailcf();
	local $cwfile;
	local @dlist = &sendmail::get_file_or_config($conf, "w", undef,
						     \$cwfile);
	&lock_file($cwfile) if ($cwfile);
	&lock_file($sendmail::config{'sendmail_cf'});
	&sendmail::add_file_or_config($conf, "w", $_[0]->{'dom'});
	&flush_file_lines();
	&unlock_file($sendmail::config{'sendmail_cf'});
	&unlock_file($cwfile) if ($cwfile);
	if (!$no_restart_mail) {
		&sendmail::restart_sendmail();
		}
	}
elsif ($config{'mail_system'} == 0) {
	# Add a special postfix virtual entry just for the domain
	&create_virtuser({ 'from' => $_[0]->{'dom'},
			   'to' => [ $_[0]->{'dom'} ] });
	}
elsif ($config{'mail_system'} == 2) {
	# Add to qmail rcpthosts file and virtualdomains file
	local $rlist = &qmailadmin::list_control_file("rcpthosts");
	push(@$rlist, $_[0]->{'dom'});
	&qmailadmin::save_control_file("rcpthosts", $rlist);

	local $virtmap = { 'domain' => $_[0]->{'dom'},
			   'prepend' => $_[0]->{'prefix'}.'pfx' };
	&qmailadmin::create_virt($virtmap);
	if (!$no_restart_mail) {
		&qmailadmin::restart_qmail();
		}
	}
elsif ($config{'mail_system'} == 4) {
	# Just add to qmail locals file, as virtualdomains is not
	# needed for qmail+ldap
	local $llist = &qmailadmin::list_control_file("locals");
	push(@$llist, $_[0]->{'dom'});
	&qmailadmin::save_control_file("locals", $llist);
	&execute_command("cd /etc/qmail && make");
	if (!$no_restart_mail) {
		&qmailadmin::restart_qmail();
		}
	}
elsif ($config{'mail_system'} == 5) {
	# Call vpopmail domain creation program
	local $qdom = quotemeta($_[0]->{'dom'});
	local $qpass = quotemeta($_[0]->{'pass'});
	local $out = `$vpopbin/vadddomain $qdom $qpass 2>&1`;
	if ($?) {
		&$second_print(&text('setup_evadddomain', "<tt>$out</tt>"));
		return;
		}
	}
&$second_print($text{'setup_done'});

# Create any aliases specified in the template, if missing
if (!$_[1] && !$_[0]->{'no_tmpl_aliases'}) {
	local %gotvirt;
	foreach my $v (&list_virtusers()) {
		$gotvirt{$v->{'from'}} = $v;
		}
	if ($_[0]->{'alias'}) {
		# Alias all mail to this domain to a different domain
		local $aliasdom = &get_domain($_[0]->{'alias'});
		if ($supports_aliascopy) {
			$_[0]->{'aliascopy'} = $tmpl->{'aliascopy'};
			}
		if ($_[0]->{'aliascopy'}) {
			# Sync all virtusers from the dest domain
			&copy_alias_virtuals($_[0], $aliasdom);
			}
		elsif (!$gotvirt{'@'.$_[0]->{'dom'}}) {
			# Just create a catchall
			&create_virtuser({ 'from' => '@'.$_[0]->{'dom'},
				   'to' => [ '%1@'.$aliasdom->{'dom'} ] })
			}
		}
	elsif ($tmpl->{'dom_aliases'} && $tmpl->{'dom_aliases'} ne "none") {
		# Setup aliases from this domain based on the template
		&$first_print($text{'setup_domaliases'});
		local @aliases = split(/\t+/, $tmpl->{'dom_aliases'});
		local ($a, %acreate);
		foreach $a (@aliases) {
			local ($from, $to) = split(/=/, $a, 2);
			if ($config{'mail_system'} == 5 &&
			    lc($from) eq 'postmaster') {
				# Postmaster is created automatically
				# on vpopmail systems
				next;
				}
			$to = &substitute_domain_template($to, $_[0]);
			$from = $from eq "*" ? "\@$_[0]->{'dom'}" : "$from\@$_[0]->{'dom'}";
			if ($acreate{$from}) {
				push(@{$acreate{$from}->{'to'}}, $to);
				}
			else {
				$acreate{$from} = { 'from' => $from,
						    'to' => [ $to ] };
				}
			}
		foreach $a (values %acreate) {
			&create_virtuser($a) if (!$gotvirt{$a->{'from'}});
			}
		if ($tmpl->{'dom_aliases_bounce'} &&
		    !$acreate{"\@$_[0]->{'dom'}"} &&
		    !$gotvirt{'@'.$_[0]->{'dom'}}) {
			# Add bounce alias
			local $v = { 'from' => "\@$_[0]->{'dom'}",
				     'to' => [ 'BOUNCE' ] };
			&create_virtuser($v);
			}
		&$second_print($text{'setup_done'});
		}
	}

# Setup any secondary MX servers
if (!$_[0]->{'nosecondaries'}) {
	&setup_on_secondaries($_[0]);
	}
}

# delete_mail(&domain, [leave-aliases])
# Removes a domain from the list of those accepted by the mail system
sub delete_mail
{
&$first_print($text{'delete_doms'});
&require_mail();

if ($_[0]->{'alias'} && !$_[0]->{'aliascopy'}) {
        # Remove whole-domain alias
        local @virts = &list_virtusers();
        local ($catchall) = grep { lc($_->{'from'}) eq '@'.$_[0]->{'dom'} }
				 @virts;
        if ($catchall) {
                &delete_virtuser($catchall);
                }
        }
elsif ($_[0]->{'alias'} && $_[0]->{'aliascopy'}) {
	# Remove alias copy virtuals
	&delete_alias_virtuals($_[0]);
	}

if ($config{'mail_system'} == 1) {
	# Delete domain from sendmail local domains file
	local $conf = &sendmail::get_sendmailcf();
	local $cwfile;
	local @dlist = &sendmail::get_file_or_config($conf, "w", undef,
						     \$cwfile);
	&lock_file($cwfile) if ($cwfile);
	&lock_file($sendmail::config{'sendmail_cf'});
	&sendmail::delete_file_or_config($conf, "w", $_[0]->{'dom'});
	&flush_file_lines();
	&unlock_file($sendmail::config{'sendmail_cf'});
	&unlock_file($cwfile) if ($cwfile);
	if (!$no_restart_mail) {
		&sendmail::restart_sendmail();
		}
	}
elsif ($config{'mail_system'} == 0) {
	# Delete the special postfix virtuser
	local @virts = &list_virtusers();
	local ($lv) = grep { lc($_->{'from'}) eq $_[0]->{'dom'} } @virts;
	if ($lv) {
		&delete_virtuser($lv);
		}
	local @md = split(/[, ]+/,
			  lc(&postfix::get_current_value("mydestination")));
	local $idx = &indexof($_[0]->{'dom'}, @md);
	if ($idx >= 0) {
		# Delete old-style entry too
		&lock_file($postfix::config{'postfix_config_file'});
		splice(@md, $idx, 1);
		&postfix::set_current_value("mydestination", join(", ", @md));
		&unlock_file($postfix::config{'postfix_config_file'});
		if (!$no_restart_mail) {
			&shutdown_mail_server();
			&startup_mail_server();
			}
		}
	}
elsif ($config{'mail_system'} == 2 || $config{'mail_system'} == 4) {
	# Delete domain from qmail locals file, rcpthosts file and virtuals
	local $dlist = &qmailadmin::list_control_file("locals");
	$dlist = [ grep { lc($_) ne $_[0]->{'dom'} } @$dlist ];
	&qmailadmin::save_control_file("locals", $dlist);

	local $rlist = &qmailadmin::list_control_file("rcpthosts");
	$rlist = [ grep { lc($_) ne $_[0]->{'dom'} } @$rlist ];
	&qmailadmin::save_control_file("rcpthosts", $rlist);

	local ($virtmap) = grep { lc($_->{'domain'}) eq $_[0]->{'dom'} &&
				  !$_->{'user'} } &qmailadmin::list_virts();
	&qmailadmin::delete_virt($virtmap) if ($virtmap);
        if ($config{'mail_system'} == 4) {
                &execute_command("cd /etc/qmail && make");
                }
	if (!$no_restart_mail) {
		&qmailadmin::restart_qmail();
		}
	}
elsif ($config{'mail_system'} == 5) {
	# Call vpopmail domain deletion program
	local $qdom = quotemeta($_[0]->{'dom'});
	local $out = `$vpopbin/vdeldomain $qdom 2>&1`;
	if ($?) {
		&$second_print(&text('delete_evdeldomain', "<tt>$out</tt>"));
		return;
		}
	}
&$second_print($text{'setup_done'});

if ($config{'delete_virts'} && !$_[1]) {
	# Delete all email aliases
	&$first_print($text{'delete_aliases'});
	foreach my $v (&list_virtusers()) {
		if ($v->{'from'} =~ /\@(\S+)$/ &&
		    $1 eq $_[0]->{'dom'}) {
			&delete_virtuser($v);
			}
		}
	&$second_print($text{'setup_done'});
	}

# Remove any secondary MX servers
&delete_on_secondaries($_[0]);
}

# modify_mail(&domain, &olddomain)
# Deal with a change in domain name
sub modify_mail
{
local $tmpl = &get_template($_[0]->{'template'});
&require_useradmin();

# Need to update the home directory of all mail users .. but only
# in the Unix object, as their files will have already been moved
# as part of the domain's directory.
# No need to do this for VPOPMail users.
# Also, any users in the user@domain name format need to be renamed
if (($_[0]->{'home'} ne $_[1]->{'home'} ||
     $_[0]->{'dom'} ne $_[1]->{'dom'} ||
     $_[0]->{'gid'} != $_[1]->{'gid'} ||
     $_[0]->{'prefix'} ne $_[1]->{'prefix'}) && !$_[0]->{'alias'}) {
	&$first_print($text{'save_mailrename'});
	local $u;
	local $domhack = { %{$_[0]} };		# This hack is needed to find
	$domhack->{'home'} = $_[1]->{'home'};	# users under the old home dir
	$domhack->{'gid'} = $_[1]->{'gid'};	# and GID and parent
	$domhack->{'parent'} = $_[1]->{'parent'};
	foreach $u (&list_domain_users($domhack, 1)) {
		local %oldu = %$u;
		if ($_[0]->{'home'} ne $_[1]->{'home'} &&
		    $config{'mail_system'} != 5) {
			# Change home directory
			$u->{'home'} =~ s/$_[1]->{'home'}/$_[0]->{'home'}/;
			}
		local $olddom = $_[1]->{'dom'};
		if ($_[0]->{'dom'} ne $_[1]->{'dom'} &&
		    $tmpl->{'append_style'} == 6 &&
		    $u->{'user'} =~ /^(.*)\@\Q$olddom\E$/) {
			# Rename this guy, as he is using an @domain name
			local $pop3 = $1;
			$u->{'user'} = &userdom_name($pop3, $_[0]);
			if ($u->{'email'}) {
				$u->{'email'} = "$pop3\@$_[0]->{'dom'}";
				}
			}
		elsif ($_[0]->{'prefix'} ne $_[1]->{'prefix'}) {
			# Username prefix has changed, so user may need to be
			# renamed.
			$u->{'user'} =~ s/^\Q$_[1]->{'prefix'}\E([\.\-])/$_[0]->{'prefix'}$1/ ||
				$u->{'user'} =~ s/([\.\-])\Q$_[1]->{'prefix'}\E$/$1$_[0]->{'prefix'}/;
			}
		if ($_[0]->{'gid'} != $_[1]->{'gid'}) {
			# Domain owner has changed, so user's GID must too ..
			# and so must the GID on his files
			$u->{'gid'} = $_[0]->{'gid'};
			&useradmin::recursive_change($u->{'home'},
				$u->{'uid'}, $_[1]->{'gid'},
				$u->{'uid'}, $_[0]->{'gid'});
			}

		# Update email address attributes for the user, as these
		# are used in LDAP
		if ($u->{'email'}) {
			$u->{'email'} =~ s/\@\Q$_[1]->{'dom'}\E$/\@$_[0]->{'dom'}/;
			}
		local @newextra;
		foreach my $extra (@{$u->{'extraemail'}}) {
			my $newextra = $extra;
			$newextra =~ s/\@\Q$_[1]->{'dom'}\E$/\@$_[0]->{'dom'}/;
			push(@newextra, $newextra);
			}
		$u->{'extraemail'} = \@newextra;

		# Save the user
		&modify_user($u, \%oldu, $_[0], 1);
		if (!$u->{'nomailfile'}) {
			&rename_mail_file($u, \%oldu);
			}
		}
	&$second_print($text{'setup_done'});
	}
	
if ($_[0]->{'alias'} && $_[2] && $_[2]->{'dom'} ne $_[3]->{'dom'}) {
	# This is an alias, and the domain it is aliased to has changed ..
	# update the catchall alias or virtuser copies
	if (!$_[0]->{'aliascopy'}) {
		# Fixup dest in catchall
		local @virts = &list_virtusers();
		local ($catchall) = grep {
			$_->{'to'}->[0] eq '%1@'.$_[3]->{'dom'} } @virts;
		if ($catchall) {
			&$first_print($text{'save_mailalias'});
			$catchall->{'to'} = [ '%1@'.$_[2]->{'dom'} ];
			&modify_virtuser($catchall, $catchall);
			&$second_print($text{'setup_done'});
			}
		}
	else {
		# Re-write all copied virtuals
		&copy_alias_virtuals($_[0], $_[2]);
		}
	}
elsif ($_[0]->{'alias'} && $_[0]->{'dom'} ne $_[1]->{'dom'} &&
       $_[0]->{'aliascopy'}) {
	# This is an alias and the domain name has changed - fix all virtuals
	&delete_alias_virtuals($_[1]);
	local $alias = &get_domain($_[0]->{'alias'});
	&copy_alias_virtuals($_[0], $alias);
	}

if ($_[0]->{'dom'} ne $_[1]->{'dom'}) {
	# Delete the old mail domain and add the new
	local $no_restart_mail = 1;
	&delete_mail($_[1], 1);
	&setup_mail($_[0], 1);
	&require_mail();
	if (&is_mail_running()) {
		if ($config{'mail_system'} == 1) {
			&sendmail::restart_sendmail();
			}
		elsif ($config{'mail_system'} == 0) {
			&shutdown_mail_server();
			&startup_mail_server();
			}
		elsif ($config{'mail_system'} == 2 ||
		       $config{'mail_system'} == 4) {
			&qmailadmin::restart_qmail();
			}
		}

	if (!$_[0]->{'aliascopy'}) {
		# Update any virtusers with addresses in the old domain
		&$first_print($text{'save_fixvirts'});
		foreach $v (&list_virtusers()) {
			if ($v->{'from'} =~ /^(\S*)\@(\S+)$/ &&
			    lc($2) eq $_[1]->{'dom'}) {
				local $oldv = { %$v };
				local $u = $1;
				if ($u eq $_[1]->{'user'}) {
					# For admin user, who has changed
					$u = $_[0]->{'user'};
					}
				$v->{'from'} = "$u\@$_[0]->{'dom'}";
				&fix_alias_when_renaming($v, $_[0], $_[1]);
				&modify_virtuser($oldv, $v);
				}
			}
		}

	if (!$_[0]->{'alias'}) {
		# Update any generics/sender canonical entries in the old domain
		if ($config{'generics'}) {
			local %ghash = &get_generics_hash();
			foreach my $g (values %ghash) {
				if ($g->{'to'} =~ /^(.*)\@(\S+)$/ &&
				    $2 eq $_[1]->{'dom'}) {
					local $oldg = { %$g };
					local $u = $1;
					if ($u eq $_[1]->{'user'}) {
						# For admin user, who has
						# changed name
						$u = $_[0]->{'user'};
						}
					$g->{'to'} = "$u\@$_[0]->{'dom'}";
					&modify_generic($g, $oldg);
					}
				}
			}

		# Make a second pass through users to fix aliases
		&flush_virtualmin_caches();
		foreach my $u (&list_domain_users($_[0])) {
			local $oldu = { %$u };
			if (&fix_alias_when_renaming($u, $_[0], $_[1])) {
				&modify_user($u, $oldu, $_[0]);
				}
			}
		}

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

# fix_alias_when_renaming(&alias|&user, &dom, &olddom)
# When renaming a domain, fix up the destination addresses in the given
# alias or user.
sub fix_alias_when_renaming
{
local ($virt, $dom, $olddom) = @_;
local $changed = 0;
local @newto = ( );
foreach my $ot (@{$virt->{'to'}}) {
	local $t = $ot;
	if ($t =~ /^(\S*)\@(\S+)$/ &&
	    lc($2) eq $olddom->{'dom'}) {
		# Destination is an address in the
		# domain being renamed
		$t = "$1\@$dom->{'dom'}";
		}
	elsif ($t =~ /^\Q$olddom->{'prefix'}\E([\.\-].*)$/) {
		# Destination is a user being renamed,
		# with prefix at start
		$t = "$dom->{'prefix'}$1";
		}
	elsif ($t =~ /^(.*[\.\-])\Q$olddom->{'prefix'}\E$/) {
		# Destination is a user being renamed,
		# with prefix at end
		$t = "$1$dom->{'prefix'}";
		}

	# Change home directory references, for auto-
	# reply files.
	local $type = &alias_type($t);
	if ($type == 5) {
		$t =~ s/\Q$olddom->{'home'}\E/$dom->{'home'}/g;
		$t =~ s/\Q$olddom->{'dom'}\E /$dom->{'dom'} /g;
		}
	$changed++ if ($t ne $ot);
	push(@newto, $t);
	}
$virt->{'to'} = \@newto;
return $changed;
}

# validate_mail(&domain)
# Returns an error message if the server is not setup to receive mail for
# this domain, or if mail users have incorrect permissions.
sub validate_mail
{
local ($d) = @_;

# Check if this server is receiving email
return &text('validate_email', "<tt>$d->{'dom'}</tt>")
	if (!&is_local_domain($d->{'dom'}));

# Check any secondary MX servers
local %ids = map { $_, 1 } split(/\s+/, $d->{'mx_servers'});
local @servers = grep { $ids{$_->{'id'}} } &list_mx_servers();
foreach my $s (@servers) {
	next if (!$ids{$s->{'id'}});
	local $ok = &is_one_secondary($d, $s);
	if ($ok eq '0') {
		return &text('validate_emailmx', $s->{'host'});
		}
	elsif ($ok ne '1') {
		return &text('validate_emailmx2', $s->{'host'}, $ok);
		}
	}

# Check mailbox permissions
if ($config{'mail_system'} != 5) {	# skip for vpopmail
	local %doneuid;
	foreach my $user (&list_domain_users($d, 1)) {
		if (!$user->{'webowner'} && $doneuid{$user->{'uid'}}++) {
			return &text('validate_emailuid', $user->{'user'},
							  $user->{'uid'});
			}
		local @st = stat($user->{'home'});
		if (!@st) {
			return &text('validate_emailhome', $user->{'user'},
							   $user->{'home'});
			}
		if ($st[4] != $user->{'uid'}) {
			local $ru = getpwuid($st[4]) || $user->{'uid'};
			return &text('validate_emailhomeu',
				$user->{'user'}, $user->{'home'}, $ru);
			}
		if ($st[5] != $user->{'gid'}) {
			local $rg = getgrgid($st[5]) || $user->{'gid'};
			return &text('validate_emailhomeg',
				$user->{'user'}, $user->{'home'}, $rg);
			}
		}
	}
return undef;
}

# disable_mail(&domain)
# Turn off mail for the domain, and disable login for all users
sub disable_mail
{
&delete_mail($_[0], 1);

&$first_print($text{'disable_users'});
foreach my $user (&list_domain_users($_[0], 1)) {
	if (!$user->{'alwaysplain'}) {
		&set_pass_disable($user, 1);
		&modify_user($user, $user, $_[0]);
		}
	}
&$second_print($text{'setup_done'});
}

# enable_mail(&domain)
# Turn on mail for the domain, and re-enable login for all users
sub enable_mail
{
&setup_mail($_[0], 1);

&$first_print($text{'enable_users'});
foreach my $user (&list_domain_users($_[0], 1)) {
	if (!$user->{'alwaysplain'}) {
		&set_pass_disable($user, 0);
		&modify_user($user, $user, $_[0]);
		}
	}
&$second_print($text{'setup_done'});
}

# check_mail_clash()
# Does nothing, because no clash checking is needed
sub check_mail_clash
{
return 0;
}

# is_local_domain(domain)
# Returns 1 if some domain is used for mail on this system, 0 if not
sub is_local_domain
{
local $found = 0;
&require_mail();
if ($config{'mail_system'} == 1) {
	# Check Sendmail local domains file
	local $conf = &sendmail::get_sendmailcf();
        local @dlist = &sendmail::get_file_or_config($conf, "w");
	foreach my $d (@dlist) {
		$found++ if (lc($d) eq lc($_[0]));
		}
	}
elsif ($config{'mail_system'} == 0) {
	# Check Postfix virtusers and mydestination
	local @virts = &list_virtusers();
	local ($lv) = grep { lc($_->{'from'}) eq $_[0] } @virts;
	$found++ if ($lv);
	local @md = split(/[, ]+/,&postfix::get_current_value("mydestination"));
	local $hostname = lc(&get_system_hostname());
	foreach my $md (@md) {
		$found++ if (lc($md) eq lc($_[0]) ||
			     $md eq '$myhostname' && lc($_[0]) eq $hostname);
		}
	}
elsif ($config{'mail_system'} == 2) {
	# Check qmail rcpthosts and virtualdomains files
	local $rlist = &qmailadmin::list_control_file("rcpthosts");
	@$rlist = map { lc($_) } @$rlist;
	local ($virtmap) = grep { lc($_->{'domain'}) eq $_[0]->{'dom'} &&
				  !$_->{'user'} } &qmailadmin::list_virts();
	$found++ if (&indexof($_[0], @$rlist) >= 0 && $virtmap);
	}
elsif ($config{'mail_system'} == 4) {
	# Check qmail locals file
	local $rlist = &qmailadmin::list_control_file("locals");
	@$rlist = map { lc($_) } @$rlist;
	$found++ if (&indexof($_[0], @$rlist) >= 0);
	}
return $found;
}

# list_virtusers()
# Returns a list of a virtual mail address mappings. Each may actually have
# an alias as its destination, and is automatically expanded to the
# destinations for that alias.
sub list_virtusers
{
# Build list of unix users, to exclude aliases with same name as users
# (which are picked up by list_domain_users instead).
&require_mail();
if (!defined(%unix_user)) {
	&require_useradmin(1);
	foreach my $u (&list_all_users()) {
		$unix_user{&escape_alias($u->{'user'})}++;
		}
	}

# Build a list of copy-mode alias domains, as their Sendmail and Postfix
# virtusers shouldn't be included
local %alias_copy;
if ($supports_aliascopy) {
	foreach my $d (&get_domain_by("alias", "_ANY_")) {
		if ($d->{'aliascopy'}) {
			$alias_copy{$d->{'dom'}}++;
			}
		}
	}

if ($config{'mail_system'} == 1) {
	# Get from sendmail
	local @svirts = &sendmail::list_virtusers($sendmail_vfile);
	local %aliases = map { lc($_->{'name'}), $_ }
			 grep { $_->{'enabled'} && !$unix_user{$_->{'name'}} }
				&sendmail::list_aliases($sendmail_afiles);
	local ($v, $a, @virts);
	foreach $v (@svirts) {
		local %rv = ( 'virt' => $v,
			      'cmt' => $v->{'cmt'},
			      'from' => lc($v->{'from'}) );
		local ($mb, $dname) = split(/\@/, $rv{'from'});
		next if ($alias_copy{$dname});
		if ($v->{'to'} !~ /\@/ && ($a = $aliases{lc($v->{'to'})})) {
			# Points to an alias - use its values
			$rv{'to'} = $a->{'values'};
			$rv{'alias'} = $a;
			}
		else { 
			# Just the original value
			$rv{'to'} = [ $v->{'to'} ];
			if ($v->{'to'} eq "error:nouser User unknown") {
				# Default message
				$rv{'to'} = [ "BOUNCE" ];
				}
			elsif ($v->{'to'} =~ /^error:nouser\s+(.*)/i) {
				# Custom message
				$rv{'to'} = [ "BOUNCE $1" ];
				}
			elsif ($v->{'to'} eq "error:nouser") {
				# No message
				$rv{'to'} = [ "BOUNCE" ];
				}
			}
		push(@virts, \%rv);
		}
	return @virts;
	}
elsif ($config{'mail_system'} == 0) {
	# Get from postfix
	local $svirts = &postfix::get_maps($virtual_type);
	local %aliases = map { lc($_->{'name'}), $_ }
			 grep { $_->{'enabled'} && !$unix_user{$_->{'name'}} }
			     &$postfix_list_aliases($postfix_afiles);
	local ($v, $a, @virts);
	foreach $v (@$svirts) {
		local %rv = ( 'from' => lc($v->{'name'}),
			      'cmt' => $v->{'cmt'},
			      'virt' => $v );
		local ($mb, $dname) = split(/\@/, $rv{'from'});
		next if ($alias_copy{$dname});
		if ($v->{'value'} !~ /\@/ &&
		    ($a = $aliases{lc($v->{'value'})})) {
			$rv{'to'} = $a->{'values'};
			$rv{'alias'} = $a;
			}
		else {
			$rv{'to'} = [ $v->{'value'} ];
			}
		local $t;	# postfix format for catchall forward is
				# different from sendmail
		foreach $t (@{$rv{'to'}}) {
			$t =~ s/^\@(\S+)$/\%1\@$1/;
			}
		push(@virts, \%rv);
		}
	return @virts;
	}
elsif ($config{'mail_system'} == 2) {
	# Find all qmail aliases like .qmail-group-user
	local @virtmaps = grep { !$_->{'user'} } &qmailadmin::list_virts();
	local @aliases = &qmailadmin::list_aliases();
	local ($an, $v, @virts);
	foreach $an (@aliases) {
		# Find domain in virtual maps
		local $a = &qmailadmin::get_alias($an);
		local $name = $a->{'name'};
		foreach $v (@virtmaps) {
			if ($a->{'name'} =~ /^\Q$v->{'prepend'}\E\-(.*)$/) {
				$name = ($1 eq "default" ? "" : $1)
					."\@".$v->{'domain'};
				}
			}
		push(@virts, { 'from' => $name,
			       'alias' => $a,
			       'to' => [ map { s/^\&//; $_ }
					       @{$a->{'values'}} ] });
		}
	return @virts;
	}
elsif ($config{'mail_system'} == 4) {
	# Looks for psuedo qmail users with no mail store
	local $ldap = &connect_qmail_ldap();
	local $rv = $ldap->search(base => $config{'ldap_base'},
				  filter => "(objectClass=qmailUser)");
	&error($rv->error) if ($rv->code);
	local ($u, @virts);
	foreach $u ($rv->all_entries) {
		next if ($u->get_value("mailMessageStore"));	# skip user
		local $mail = $u->get_value("mail");
		if ($mail =~ /^catchall\@(.*)$/) {
			$mail = "\@$1";
			}
		local @to = $u->get_value("mailForwardingAddress");
		push(@virts, { 'from' => $mail,
			       'dn' => $u->dn(),
			       'ldap' => $u,
			       'to' => \@to });
		}
	$ldap->unbind();
	return @virts;
	}
elsif ($config{'mail_system'} == 5) {
	# Use the valias program to get aliases for all domains
	if (!defined(@vpopmail_aliases_cache)) {
		@vpopmail_aliases_cache = ( );
		opendir(DDIR, "$config{'vpopmail_dir'}/domains");
		local @doms = grep { $_ !~ /^\./ } readdir(DDIR);
		closedir(DDIR);
		local $dname;
		foreach $dname (@doms) {
			# Get aliases from .qmail files
			local %already;
			local $ddir = "$config{'vpopmail_dir'}/domains/$dname";
			opendir(DDIR, $ddir);
			while($qf = readdir(DDIR)) {
				next if ($qf !~ /^.qmail-(.*)$/);
				local $alias = { 'from' => $1 eq "default" ?
						    "\@$dname" : "$1\@$dname",
						 'to' => [ ] };
				local $_;
				open(QMAIL, "$ddir/$qf");
				while(<QMAIL>) {
					s/\r|\n//g;
					push(@{$alias->{'to'}},
					     &qmail_to_vpopmail($_, $dname));
					}
				close(QMAIL);
				$already{$alias->{'from'}} = $alias;
				push(@vpopmail_aliases_cache, $alias);
				}
			closedir(DDIR);

			# Add those from valias command (for sites using MySQL or some
			# other backend)
			local %aliases;
			local $_;
			open(ALIASES, "$vpopbin/valias -s $dname |");
			while(<ALIASES>) {
				s/\r|\n//g;
				if (/^(\S+)\s+\->\s+(.*)/) {
					local ($from, $to) = ($1, $2);
					next if ($already{$from});	# already above
					local $alias;
					$to = &qmail_to_vpopmail($to, $dname);
					if ($alias = $aliases{$from}) {
						push(@{$alias->{'to'}}, $to);
						}
					else {
						$alias = { 'from' => $from,
							   'to' => [ $to ] };
						$aliases{$from} = $alias;
						push(@vpopmail_aliases_cache,
						     $alias);
						}
					}
				}
			close(ALIASES);
			}
		}
	return @vpopmail_aliases_cache;
	}
}

# qmail_to_vpopmail(line, domain)
# Converts a line from a .qmail file created by vpopmail into the internal
# Virtualmin format. 
sub qmail_to_vpopmail
{
local $ddir = "$config{'vpopmail_dir'}/domains/$_[1]";
if ($_[0] =~ /^\|\s*$vpopbin\/vdelivermail\s+''\s+(\S+)\@(\S+)$/) {
	# External address
	return $2 eq $_[1] ? $1 : "$1\@$2";
	}
elsif ($_[0] =~ /^\|\s*$vpopbin\/vdelivermail\s+''\s+\Q$ddir\E\/(\S+)$/) {
	# Direct to user
	return "\\$1";
	}
elsif ($_[0] =~ /^\|\s*$vpopbin\/vdelivermail\s+''\s+bounce-no-mailbox$/) {
	# Bouncer
	return "BOUNCE";
	}
elsif ($_[0] =~ /^\|\s*$vpopbin\/vdelivermail\s+''\s+delete$/) {
	# Deleter
	return "/dev/null";
	}
else {
	# Some other line
	return $_[0];
	}
}

# vpopmail_to_qmail(alias, domain)
sub vpopmail_to_qmail
{
local $ddir = "$config{'vpopmail_dir'}/domains/$_[1]";
if ($_[0] =~ /^\S+\@\S+$/) {
	return $_[0];
	}
elsif ($_[0] eq "BOUNCE") {
	return "| $vpopbin/vdelivermail '' bounce-no-mailbox";
	}
elsif ($_[0] eq "/dev/null") {
	return "| $vpopbin/vdelivermail '' delete";
	}
elsif ($_[0] =~ /^\\(\S+)$/) {
	return "| $vpopbin/vdelivermail '' $ddir/$1";
	}
elsif ($_[0] =~ /^[a-z0-9\.\-\_]+$/) {
	return "| $vpopbin/vdelivermail '' $_[0]\@$_[1]";
	}
else {
	return $_[0];
	}
}

# delete_virtuser(&virtuser)
# Deletes a virtual mail user mapping
sub delete_virtuser
{
&require_mail();
&execute_before_virtuser($_[0], 'DELETE_ALIAS');
if ($config{'mail_system'} == 1) {
	# Delete from sendmail
	if ($_[0]->{'alias'}) {
		# Delete alias too
		&lock_file($_[0]->{'alias'}->{'file'});
		&sendmail::delete_alias($_[0]->{'alias'});
		&unlock_file($_[0]->{'alias'}->{'file'});
		}
	&lock_file($_[0]->{'virt'}->{'file'});
	&sendmail::delete_virtuser($_[0]->{'virt'}, $sendmail_vfile,
				   $sendmail_vdbm, $sendmail_vdbmtype);
	&unlock_file($_[0]->{'virt'}->{'file'});
	}
elsif ($config{'mail_system'} == 0) {
	# Delete from postfix file
	if ($_[0]->{'alias'}) {
		# Delete alias too
		&lock_file($_[0]->{'alias'}->{'file'});
		&$postfix_delete_alias($_[0]->{'alias'});
		&unlock_file($_[0]->{'alias'}->{'file'});
		&postfix::regenerate_aliases();
		}
	&lock_file($_[0]->{'virt'}->{'file'});
	&postfix::delete_mapping($virtual_type, $_[0]->{'virt'});
	&unlock_file($_[0]->{'virt'}->{'file'});
	&postfix::regenerate_virtual_table();
	}
elsif ($config{'mail_system'} == 2) {
	# Just delete the qmail alias
	&qmailadmin::delete_alias($_[0]->{'alias'});
	}
elsif ($config{'mail_system'} == 4) {
	# Remove pseudo Qmail user
	local $ldap = &connect_qmail_ldap();
	local $rv = $ldap->delete($_[0]->{'dn'});
	&error($rv->error) if ($rv->code);
	$ldap->unbind();
	}
elsif ($config{'mail_system'} == 5) {
	# Remove all vpopmail aliases
	$_[0]->{'from'} =~ /^(\S*)\@(\S+)$/;
	local ($box, $dom) = ($1 || "default", $2);
	local $qfrom = quotemeta("$box\@$dom");
	local $cmd = "$vpopbin/valias -d $qfrom";
	local $out = &backquote_logged("$cmd 2>&1");
	if ($?) {
		&error("<tt>$cmd</tt> failed : <pre>$out</pre>");
		}
	}
&execute_after_virtuser($_[0], 'DELETE_ALIAS');
}

# modify_virtuser(&old, &new)
# Update an email alias, which forwards mail from some address to multiple
# destinations (addresses, autoresponders, etc).
sub modify_virtuser
{
&require_mail();
&execute_before_virtuser($_[0], 'MODIFY_ALIAS');
local @to = @{$_[1]->{'to'}};
if ($config{'mail_system'} == 1) {
	# Modify in sendmail
	local $alias = $_[0]->{'alias'};
	local $oldalias = $alias ? { %$alias } : undef;
	local @smto = map { $_ eq "BOUNCE" ? "error:nouser User unknown" :
			    $_ =~ /^BOUNCE\s+(.*)$/ ? "error:nouser $1" :
			    $_ } @to;
	$_[1]->{'from'} =~ /^(\S*)\@(\S+)$/;
	local $an = ($1 || "default")."-".$2;
	if (&needs_alias(@smto) && !$alias) {
		# Alias needs to be created and virtuser updated
		local $alias = { "name" => $an,
				 "enabled" => 1,
				 "values" => \@smto };
		$_[1]->{'alias'} = $alias;
		&sendmail::lock_alias_files($sendmail_afiles);
		&sendmail::create_alias($alias, $sendmail_afiles);
		&sendmail::unlock_alias_files($sendmail_afiles);
		local $virt = { "from" => $_[1]->{'from'},
				"to" => $an,
				"cmt" => $_[1]->{'cmt'} };
		&lock_file($_[0]->{'virt'}->{'file'});
		&sendmail::modify_virtuser($_[0]->{'virt'}, $virt,
					   $sendmail_vfile, $sendmail_vdbm,
					   $sendmail_vdbmtype);
		&unlock_file($_[0]->{'virt'}->{'file'});
		$_[1]->{'virt'} = $virt;
		}
	elsif ($alias) {
		# Just update alias and maybe virtuser
		$alias->{'values'} = \@smto;
		&lock_file($alias->{'file'});
		$alias->{'name'} = $an if ($_[1]->{'from'} ne $_[0]->{'from'});
		&sendmail::modify_alias($oldalias, $alias);
		&unlock_file($alias->{'file'});
		if ($_[1]->{'from'} ne $_[0]->{'from'} ||
		    $_[1]->{'cmt'} ne $_[0]->{'cmt'}) {
			# Re-named .. need to change virtuser too
			local $virt = { "from" => $_[1]->{'from'},
					"to" => $an,
					"cmt" => $_[1]->{'cmt'} };
			&lock_file($_[0]->{'virt'}->{'file'});
			&sendmail::modify_virtuser($_[0]->{'virt'}, $virt,
						   $sendmail_vfile,
						   $sendmail_vdbm,
						   $sendmail_vdbmtype);
			&unlock_file($_[0]->{'virt'}->{'file'});
			$_[1]->{'virt'} = $virt;
			}
		}
	else {
		# Just update virtuser
		local $virt = { "from" => $_[1]->{'from'},
				"to" => $smto[0],
				"cmt" => $_[1]->{'cmt'} };
		&lock_file($_[0]->{'virt'}->{'file'});
		&sendmail::modify_virtuser($_[0]->{'virt'}, $virt,
					   $sendmail_vfile, $sendmail_vdbm,
					   $sendmail_vdbmtype);
		&unlock_file($_[0]->{'virt'}->{'file'});
		$_[1]->{'virt'} = $virt;
		}
	}
elsif ($config{'mail_system'} == 0) {
	# Modify in postfix file
	local $alias = $_[0]->{'alias'};
	local $oldalias = $alias ? { %$alias } : undef;
	local @psto = map { $_ =~ /^BOUNCE\s+(.*)$/ ? "BOUNCE" : $_ } @to;
	$_[0]->{'from'} =~ /^(\S*)\@(\S+)$/;
	local $an = ($1 || "default")."-".$2;
	if (&needs_alias(@psto) && !$alias) {
		# Alias needs to be created and virtuser updated
		local $alias = { "name" => $an,
				 "enabled" => 1,
				 "values" => \@psto };
		$_[1]->{'alias'} = $alias;
		&postfix::lock_alias_files($postfix_afiles);
		&$postfix_create_alias($alias, $postfix_afiles);
		&postfix::unlock_alias_files($postfix_afiles);
		&postfix::regenerate_aliases();
		local $virt = { "name" => $_[1]->{'from'},
				"value" => $an,
				"cmt" => $_[1]->{'cmt'} };
		&lock_file($_[0]->{'virt'}->{'file'});
		&postfix::modify_mapping($virtual_type, $_[0]->{'virt'}, $virt);
		&unlock_file($_[0]->{'virt'}->{'file'});
		$_[1]->{'virt'} = $virt;
		&postfix::regenerate_virtual_table();
		}
	elsif ($alias) {
		# Just update alias
		$alias->{'values'} = \@psto;
		&lock_file($alias->{'file'});
		$alias->{'name'} = $an if ($_[1]->{'from'} ne $_[0]->{'from'});
		&$postfix_modify_alias($oldalias, $alias);
		&unlock_file($alias->{'file'});
		&postfix::regenerate_aliases();
		if ($_[1]->{'from'} ne $_[0]->{'from'} ||
		    $_[1]->{'cmt'} ne $_[0]->{'cmt'}) {
			# Re-named .. need to change virtuser too
			local $virt = { "name" => $_[1]->{'from'},
					"value" => $an,
					"cmt" => $_[1]->{'cmt'} };
			&lock_file($_[0]->{'virt'}->{'file'});
			&postfix::modify_mapping($virtual_type, $_[0]->{'virt'},
						 $virt);
			&unlock_file($_[0]->{'virt'}->{'file'});
			$_[1]->{'virt'} = $virt;
			&postfix::regenerate_virtual_table();
			}
		}
	else {
		# Just update virtuser
		local $t = $psto[0];
		$t =~ s/^\%1\@/\@/;	# postfix format is different
		local $virt = { "name" => $_[1]->{'from'},
				"value" => $t,
				"cmt" => $_[1]->{'cmt'} };
		&lock_file($_[0]->{'virt'}->{'file'});
		&postfix::modify_mapping($virtual_type, $_[0]->{'virt'}, $virt);
		&unlock_file($_[0]->{'virt'}->{'file'});
		$_[1]->{'virt'} = $virt;
		&postfix::regenerate_virtual_table();
		}
	}
elsif ($config{'mail_system'} == 2) {
	# Just update the qmail alias
	$_[1]->{'from'} =~ /^(\S*)\@(\S+)$/;
	local ($box, $dom) = ($1 || "default", $2);
	local ($virtmap) = grep { $_->{'domain'} eq $dom && !$_->{'user'} }
			        &qmailadmin::list_virts();
	local $alias = { 'name' => "$virtmap->{'prepend'}-$box",
			 'values' => \@to };
	&qmailadmin::modify_alias($_[0]->{'alias'}, $alias);
	$_[1]->{'alias'} = $alias;
	}
elsif ($config{'mail_system'} == 4) {
	# Update the Qmail pseudo-user
	local $ldap = &connect_qmail_ldap();
	$_[1]->{'from'} =~ /^(\S*)\@(\S+)$/;
	local ($box, $dom) = ($1 || "catchall", $2);
	local $_[1]->{'dn'} = "uid=$box-$dom,$config{'ldap_base'}";
	local $attrs = [ "uid" => "$box-$dom",
			 "mail" => $box."\@".$dom,
			 "mailForwardingAddress" => $_[1]->{'to'} ];
	local $rv = $ldap->modify($_[0]->{'dn'},
				  replace => $attrs);
	&error($rv->error) if ($rv->code);
	if ($_[0]->{'dn'} ne $_[1]->{'dn'}) {
		# Re-named too!
		$rv = $ldap->moddn($_[0]->{'dn'},
				   newrdn => "uid=$box-$dom");
		&error($rv->error) if ($rv->code);
		}
	$ldap->unbind();
	}
elsif ($config{'mail_system'} == 5) {
	# Just delete the old vpopmail alias, and re-add!
	&delete_virtuser($_[0]);
	&create_virtuser($_[1]);
	}
&execute_after_virtuser($_[1], 'MODIFY_ALIAS');
}

# create_virtuser(&virtuser)
# Creates a new virtual mail mapping
sub create_virtuser
{
&require_mail();
local @to = @{$_[0]->{'to'}};
&execute_before_virtuser($_[0], 'CREATE_ALIAS');
if ($config{'mail_system'} == 1) {
	# Create in sendmail
	local $virt;
	local @smto = map { $_ eq "BOUNCE" ? "error:nouser User unknown" :
			    $_ =~ /^BOUNCE\s+(.*)$/ ? "error:nouser $1" :
			    $_ } @to;
	if (&needs_alias(@smto)) {
		# Need to create an alias, named address-domain
		$_[0]->{'from'} =~ /^(\S*)\@(\S+)$/;
		local $an = ($1 || "default")."-".$2;
		&check_alias_clash($an) && &error(&text('alias_eclash2', $an));
		local $alias = { "name" => $an,
				 "enabled" => 1,
				 "values" => \@smto };
		$_[0]->{'alias'} = $alias;
		&sendmail::lock_alias_files($sendmail_afiles);
		&sendmail::create_alias($alias, $sendmail_afiles);
		&sendmail::unlock_alias_files($sendmail_afiles);
		$virt = { "from" => $_[0]->{'from'},
			  "to" => $an,
			  "cmt" => $_[0]->{'cmt'} };
		}
	else {
		# A single virtuser will do
		$virt = { "from" => $_[0]->{'from'},
			  "to" => $smto[0],
			  "cmt" => $_[0]->{'cmt'} };
		}
	&lock_file($sendmail_vfile);
	&sendmail::create_virtuser($virt, $sendmail_vfile,
				   $sendmail_vdbm,
				   $sendmail_vdbmtype);
	&unlock_file($sendmail_vfile);
	$_[0]->{'virt'} = $virt;
	}
elsif ($config{'mail_system'} == 0) {
	# Create in postfix file
	local @psto = map { $_ =~ /^BOUNCE\s+(.*)$/ ? "BOUNCE" : $_ } @to;
	if (&needs_alias(@psto)) {
		# Need to create an alias, named address-domain
		$_[0]->{'from'} =~ /^(\S*)\@(\S+)$/;
		local $an = ($1 || "default")."-".$2;
		&check_alias_clash($an) && &error(&text('alias_eclash2', $an));
		local $alias = { "name" => $an,
				 "enabled" => 1,
				 "values" => \@psto };
		$_[0]->{'alias'} = $alias;
		&postfix::lock_alias_files($postfix_afiles);
		&$postfix_create_alias($alias, $postfix_afiles);
		&postfix::unlock_alias_files($postfix_afiles);
		&postfix::regenerate_aliases();
		$virt = { 'name' => $_[0]->{'from'},
			  'value' => $an,
			  'cmt' => $_[0]->{'cmt'} };
		}
	else {
		# A single virtuser will do
		local $t = $psto[0];
		$t =~ s/^\%1\@/\@/;	# postfix format is different
		$virt = { 'name' => $_[0]->{'from'},
			  'value' => $t,
			  'cmt' => $_[0]->{'cmt'} };
		}
	&lock_file($virtual_map_files[0]);
	&create_replace_mapping($virtual_type, $virt);
	&unlock_file($virtual_map_files[0]);
	&postfix::regenerate_virtual_table();
	$_[0]->{'virt'} = $virt;
	}
elsif ($config{'mail_system'} == 2) {
	# Create a single Qmail alias
	$_[0]->{'from'} =~ /^(\S*)\@(\S+)$/;
	local ($box, $dom) = ($1 || "default", $2);
	local ($virtmap) = grep { $_->{'domain'} eq $dom && !$_->{'user'} }
			        &qmailadmin::list_virts();
	local $alias = { 'name' => "$virtmap->{'prepend'}-$box",
			 'values' => \@to };
	&qmailadmin::create_alias($alias);
	$_[0]->{'alias'} = $alias;
	}
elsif ($config{'mail_system'} == 4) {
	# Create a psuedo Qmail user
	local $ldap = &connect_qmail_ldap();
	$_[0]->{'from'} =~ /^(\S*)\@(\S+)$/;
	local ($box, $dom) = ($1 || "catchall", $2);
	local $_[0]->{'dn'} = "uid=$box-$dom,$config{'ldap_base'}";
	local @oc = ( "qmailUser", split(/\s+/, $config{'ldap_aclasses'}) );
	local $attrs = [ "objectClass" => \@oc,
			 "uid" => "$box-$dom",
		 	 "deliveryMode" => "nolocal",
			 "mail" => $box."\@".$dom,
			 "mailForwardingAddress" => $_[0]->{'to'} ];
	local $rv = $ldap->add($_[0]->{'dn'}, attr => $attrs);
        &error($rv->error) if ($rv->code);
        $ldap->unbind();
	}
elsif ($config{'mail_system'} == 5) {
	# Add one vpopmail alias for each destination
	local $t;
	$_[0]->{'from'} =~ /^(\S*)\@(\S+)$/;
	local ($box, $dom) = ($1 || "default", $2);
	local $maxlen = 0;
	foreach $t (@{$_[0]->{'to'}}) {
		$maxlen = length($t) if (length($t) > $maxlen);
		}
	if ($box eq "default" || $maxlen > 160) {
		# Create .qmail file directly
		local $ddir = "$config{'vpopmail_dir'}/domains/$dom";
		local $qmf = "$ddir/.qmail-$box";
		&lock_file($qmf);
		&open_tempfile(QMAIL, ">$qmf");
		foreach $t (@{$_[0]->{'to'}}) {
			&print_tempfile(QMAIL, &vpopmail_to_qmail($t, $dom),"\n");
			}
		&close_tempfile(QMAIL);
		local @uinfo = getpwnam($config{'vpopmail_user'});
		local @ginfo = getgrnam($config{'vpopmail_group'});
		&set_ownership_permissions($uinfo[2], $ginfo[2], 0600, $qmf);
		&unlock_file($qmf);
		}
	else {
		# Create with valias command
		local $qfrom = quotemeta("$box\@$dom");
		foreach $t (@{$_[0]->{'to'}}) {
			local $qto = quotemeta(&vpopmail_to_qmail($t, $dom));
			local $cmd = "$vpopbin/valias -i $qto $qfrom";
			local $out = &backquote_logged("$cmd 2>&1");
			if ($?) {
				&error("<tt>$cmd</tt> failed: <pre>$out</pre>");
				}
			}
		}
	}
&execute_after_virtuser($_[0], 'CREATE_ALIAS');
}

# needs_alias(list..)
sub needs_alias
{
return 1 if (@_ != 1);
local $t;
foreach $t (@_) {
	return 1 if (&alias_type($t) != 1 && &alias_type($t) != 8 &&
		     &alias_type($t) != 9);
	}
return 0;
}

# join_alias(list..)
sub join_alias
{
return join(',', map { /\s/ ? "\"$_\"" : $_ } @_);
}

# is_mail_running()
# Returns 1 if the configured mail server is running, 0 if not
sub is_mail_running
{
&require_mail();
if ($config{'mail_system'} == 1) {
	# Call the sendmail module
	return &sendmail::is_sendmail_running();
	}
elsif ($config{'mail_system'} == 0) {
	# Call the postfix module 
	return &postfix::is_postfix_running();
	}
elsif ($config{'mail_system'} == 2 || $config{'mail_system'} == 4 ||
       $config{'mail_system'} == 5) {
	# Just look for qmail-send
	local ($pid) = &find_byname("qmail-send");
	return $pid ? 1 : 0;
	}
}

# shutdown_mail_server([return-error])
# Shuts down the mail server, or calls &error
sub shutdown_mail_server
{
&require_mail();
local $err;
if ($config{'mail_system'} == 1) {
	# Kill or stop sendmail
	$err = &sendmail::stop_sendmail();
	}
elsif ($config{'mail_system'} == 0) {
	# Run the postfix stop command
	$err = &postfix::stop_postfix();
	}
elsif ($config{'mail_system'} == 2 || $config{'mail_system'} == 4 ||
       $config{'mail_system'} == 5) {
	# Call the qmail stop function
	$err = &qmailadmin::stop_qmail();
	}
if ($_[0]) {
	return $err;
	}
elsif ($err) {
	&error($err);
	}
}

# startup_mail_server([return-error])
# Starts up the mail server, or calls &error
sub startup_mail_server
{
&require_mail();
local $err;
if ($config{'mail_system'} == 1) {
	# Run the sendmail start command
	$err = &sendmail::start_sendmail();
	}
elsif ($config{'mail_system'} == 0) {
	# Run the postfix start command
	$err = &postfix::start_postfix();
	}
elsif ($config{'mail_system'} == 2 || $config{'mail_system'} == 4 ||
       $config{'mail_system'} == 5) {
	# Call the qmail start function
	$err = &qmailadmin::start_qmail();
	}
if ($_[0]) {
	return $err;
	}
elsif ($err) {
	&error($err);
	}
}

# create_mail_file(&user)
# Creates a new empty mail file for a user, if necessary. Returns the path
# and type (0 for mbox, 1 for maildir)
sub create_mail_file
{
&require_mail();
local $mf;
local $md;
local ($uid, $gid) = ($_[0]->{'uid'}, $_[0]->{'gid'});
local @rv;
if ($config{'mail_system'} == 1) {
	# Sendmail always uses mail files
	$mf = &sendmail::user_mail_file($_[0]->{'user'});
	}
elsif ($config{'mail_system'} == 0) {
	# Postfix user
	local ($s, $d) = &postfix::postfix_mail_system();
	if ($s == 0 || $s == 1) {
		# A mail file
		$mf = &postfix::postfix_mail_file($_[0]->{'user'});
		}
	elsif ($s == 2) {
		# A mail directory
		$md = &postfix::postfix_mail_file($_[0]->{'user'});
		}
	}
elsif ($config{'mail_system'} == 2 ||
       $config{'mail_system'} == 4 && !$_[0]->{'qmail'}) {
	# Normal Qmail user
	if ($qmailadmin::config{'mail_system'} == 0) {
		$mf = &qmailadmin::user_mail_file($_[0]->{'user'});
		}
	elsif ($qmailadmin::config{'mail_system'} == 1) {
		$md = &qmailadmin::user_mail_dir($_[0]->{'user'});
		}
	}
elsif ($config{'mail_system'} == 4) {
	# Qmail+LDAP mail file comes from DB
	if ($_[0]->{'mailstore'} =~ /^(.*)\/$/) {
		$md = &add_ldapmessagestore("$1");
		}
	else {
		$mf = &add_ldapmessagestore($_[0]->{'mailstore'});
		}
	if (!$_[0]->{'unix'}) {
		# For non-NSS Qmail+LDAP users, set the ownership based on
		# LDAP control files
		local $cuid = &qmailadmin::get_control_file("ldapuid");
		local $cgid = &qmailadmin::get_control_file("ldapgid");
		$uid = $cuid if (defined($cuid));
		$gid = $cgid if (defined($cgid));
		}
	}
elsif ($config{'mail_system'} == 5) {
	# Nothing to do for VPOPMail, because it gets created automatically
	# by vadduser
	@rv = ( &user_mail_file($_[0]), 1 );
	}

if ($mf) {
	if (!-r $mf) {
		# Create the mailbox, owned by the user
		&open_tempfile(MF, ">$mf");
		&close_tempfile(MF);
		&set_ownership_permissions($uid, $gid, undef, $mf);
		}
	@rv = ( $mf, 0 );
	}
elsif ($md) {
	if (!-d $md) {
		# Create the Maildir, owned by the user
		local $d;
		foreach $d ($md, "$md/cur", "$md/tmp", "$md/new") {
			&make_dir($d, 0700, 1);
			&set_ownership_permissions($uid, $gid, undef, $d);
			}
		}
	@rv = ( $md, 1 );
	}

if (-d $_[0]->{'home'} && $_[0]->{'unix'}) {
	# Create Usermin ~/mail directory (if installed)
	if (&foreign_installed("usermin")) {
		local %uminiserv;
		&usermin::get_usermin_miniserv_config(\%uminiserv);
		local $mod = "mailbox";
		local %uconfig;
		&read_file("$uminiserv{'root'}/$mod/defaultuconfig",
			   \%uconfig);
		&read_file("$usermin::config{'usermin_dir'}/$mod/uconfig",
			   \%uconfig);
		local $umd = $uconfig{'mailbox_dir'} || "mail";
		local $umail = "$_[0]->{'home'}/$umd";
		if (!-d $umail) {
			&make_dir($umail, 0755);
			&set_ownership_permissions($uid, $gid, undef, $umail);
			}
		}
	}
return @rv;
}

# add_ldapmessagestore(path)
sub add_ldapmessagestore
{
if ($_[0] =~ /^\//) {
	return $_[0];
	}
else {
	local $pfx = &qmailadmin::get_control_file("ldapmessagestore");
	return $pfx."/".$_[0];
	}
}

# delete_mail_file(&user)
sub delete_mail_file
{
&require_mail();
local $umf = &user_mail_file($_[0]);
if ($umf) {
	&system_logged("rm -rf ".quotemeta($umf));
	}
&foreign_require("mailboxes", "mailboxes-lib.pl");
if (defined(&mailboxes::delete_user_index_files)) {
	&mailboxes::delete_user_index_files($_[0]->{'user'});
	}
}

# rename_mail_file(&user, &olduser)
sub rename_mail_file
{
return if (&mail_under_home());
&require_mail();
if ($config{'mail_system'} == 1) {
	# Just rename the Sendmail mail file (if necessary)
	local $of = &sendmail::user_mail_file($_[1]->{'user'});
	local $nf = &sendmail::user_mail_file($_[0]->{'user'});
	&rename_logged($of, $nf);
	}
elsif ($config{'mail_system'} == 0) {
	# Find out from Postfix which file to rename (if necessary)
	local $newumf = &postfix::postfix_mail_file($_[0]->{'user'});
	local $oldumf = &postfix::postfix_mail_file($_[1]->{'user'});
	&rename_logged($oldumf, $newumf);
	}
elsif ($config{'mail_system'} == 2 ||
       $config{'mail_system'} == 4 && !$_[0]->{'qmail'}) {
	# Just rename the Qmail mail file (if necessary)
	local $of = &qmailadmin::user_mail_file($_[1]->{'user'});
	local $nf = &qmailadmin::user_mail_file($_[0]->{'user'});
	&rename_logged($of, $nf);
	}
elsif ($config{'mail_system'} == 4) {
	# Rename from LDAP property
	&rename_logged($_[1]->{'mailstore'}, $_[0]->{'mailstore'});
	}
}

# mail_under_home()
# Returns 1 if mail is stored under user home directories
sub mail_under_home
{
&require_mail();
if ($config{'mail_system'} == 1) {
	return !$sconfig{'mail_dir'};
	}
elsif ($config{'mail_system'} == 0) {
	local $s = &postfix::postfix_mail_system();
	return $s != 0;
	}
elsif ($config{'mail_system'} == 2) {
	return $qmconfig{'mail_system'} != 0 || !$qmconfig{'mail_dir'};
	}
elsif ($config{'mail_system'} == 4) {
	return $config{'ldap_mailstore'} =~ /^(\$HOME|\$\{HOME\})/;
	}
elsif ($config{'mail_system'} == 5) {
	# VPOPMail users always have it under their homes
	return 1;
	}
}

# user_mail_file(&user)
# Returns the full path a user's mail file, and the type
sub user_mail_file
{
&require_mail();
local @rv;
if (!$_[0]->{'user'} || !$_[0]->{'home'}) {
	# User doesn't exist!
	@rv = ( );
	}
elsif ($config{'mail_system'} == 1) {
	# Just look at the Sendmail mail file
	@rv = ( &sendmail::user_mail_file($_[0]->{'user'}), 0 );
	}
elsif ($config{'mail_system'} == 0) {
	# Find out from Postfix which file to check
	local @pms = &postfix::postfix_mail_system();
	@rv = ( &postfix::postfix_mail_file($_[0]->{'user'}),
		$pms[0] == 2 ? 1 : 0 );
	}
elsif ($config{'mail_system'} == 2 ||
       $config{'mail_system'} == 4 && !$_[0]->{'qmail'}) {
	# Find out from Qmail which file or dir to check
	@rv = ( &qmailadmin::user_mail_dir($_[0]->{'user'}),
		$qmailadmin::config{'mail_system'} == 1 ? 1 : 0 );
	}
elsif ($config{'mail_system'} == 4) {
	# Mail file is an LDAP property
	local $rv = &add_ldapmessagestore($_[0]->{'mailstore'});
	if (-d "$rv/Maildir") {
		@rv = ( "$rv/Maildir", 1 );
		}
	else {
		@rv = ( $rv, 1 );
		}
	}
elsif ($config{'mail_system'} == 5) {
	# Mail dir is under VPOPMail home
	@rv = ( "$_[0]->{'home'}/Maildir", 1 );
	}
return wantarray ? @rv : $rv[0];
}

# get_mail_style()
# Returns a list containing the mail base directory, directory style,
# mail file in home dir, and maildir in home dir
sub get_mail_style
{
&require_mail();
if ($config{'mail_system'} == 1) {
	# Can get paths from Sendmail module config
	if ($sendmail::config{'mail_dir'}) {
		return ($sendmail::config{'mail_dir'},
			$sendmail::config{'mail_style'}, undef, undef);
		}
	else {
		return (undef, $sendmail::config{'mail_style'},
			$sendmail::config{'mail_file'}, undef);
		}
	}
elsif ($config{'mail_system'} == 0) {
	# Need to query Postfix module for paths
	local @s = &postfix::postfix_mail_system();
	$s[1] =~ s/\/$//;	# Remove / from Maildir/
	if ($s[0] == 0) {
		return ($s[1], 0, undef, undef);
		}
	elsif ($s[0] == 1) {
		return (undef, 0, $s[1], undef);
		}
	elsif ($s[0] == 2) {
		return (undef, 0, undef, $s[1]);
		}
	}
elsif ($config{'mail_system'} == 2) {
	# Need to check qmail module config for paths
	if ($qmailadmin::config{'mail_system'} == 1) {
		return (undef, 0, undef,
			$qmailadmin::config{'mail_dir_qmail'});
		}
	elsif ($qmailadmin::config{'mail_dir'}) {
		return ($qmailadmin::config{'mail_dir'},
			$qmailadmin::config{'mail_style'}, undef, undef);
		}
	else {
		return (undef, $qmailadmin::config{'mail_style'},
			$qmailadmin::config{'mail_file'}, undef);
		}
	}
elsif ($config{'mail_system'} == 4) {
	# Assume ~/Maildir for qmail+ldap
	return (undef, undef, undef, "Maildir");
	}
return ( );
}

# mail_file_size(&user)
# Returns the size in bytes (rounded to blocks), path to and last modified date
# of a user's mail file or directory
sub mail_file_size
{
&require_mail();
local $umf = &user_mail_file($_[0]);
if (-d $umf) {
	# Need to sum up a maildir-format directory, via a recursive search
	local ($sz, $maxmod) = &recursive_disk_usage_mtime($umf);
	return ( $sz, $umf, $maxmod );
	}
else {
	# Just the size of a single mail file
	local @st = stat($umf);
	return ( $st[12]*&quota_bsize("mail", 1) || $st[7], $umf, $st[9] );
	}
}

# recursive_disk_usage_mtime(directory, [only-gid], [levels])
# Returns the number of bytes taken up by all files in some directory,
# and the most recent modification time. The size is based on the filesystem's
# block size, not the file lengths in bytes.
sub recursive_disk_usage_mtime
{
local ($dir, $gid, $levels, $inodes) = @_;
local $dir = &translate_filename($dir);
local $bs = &quota_bsize("mail", 1);
$inodes ||= { };
if (-l $dir) {
	return (0, undef);
	}
elsif (!-d $dir) {
	local @st = stat($dir);
	if ($inodes{$st[1]}++) {
		# Already done this inode (ie. hard link)
		return ( 0, undef );
		}
	elsif (!defined($gid) || $st[5] == $gid) {
		return ( $st[12]*$bs, $st[9] );
		}
	else {
		return ( 0, undef );
		}
	}
else {
	local @st = stat($dir);
	local ($rv, $rt) = (0, undef);
	if (!defined($gid) || $st[5] == $gid) {
		$rv = $st[12]*$bs;
		$rt = $st[9];
		}
	if (!defined($levels) || $levels > 0) {
		opendir(DIR, $dir);
		local @files = readdir(DIR);
		closedir(DIR);
		foreach my $f (@files) {
			next if ($f eq "." || $f eq "..");
			local ($ss, $st) = &recursive_disk_usage_mtime(
				"$dir/$f", $gid,
				defined($levels) ? $levels - 1 : undef,
				$inodes);
			$rv += $ss;
			$rt = $st if ($st > $rt);
			}
		}
	return ($rv, $rt);
	}
}



# mail_system_base()
# Returns the base directory under which user mail files can be found
sub mail_system_base
{
&require_mail();
if ($config{'mail_system'} == 1) {
	# Find out from sendmail module config
	if ($sconfig{'mail_dir'}) {
		return $sconfig{'mail_dir'};
		}
	}
elsif ($config{'mail_system'} == 0) {
	# Find out from postfix
	local @s = &postfix::postfix_mail_system();
	if ($s[0] == 0) {
		return $s[1];
		}
	}
elsif ($config{'mail_system'} == 2) {
	# Find out from qmail module config
	if ($qmconfig{'mail_system'} == 0 && $qmconfig{'mail_dir'}) {
		return $qmconfig{'mail_dir'};
		}
	}
elsif ($config{'mail_system'} == 4) {
	# Need to look at template from module config
	local $pfx = &qmailadmin::get_control_file("ldapmessagestore");
	if ($config{'ldap_mailstore'} =~ /^(\$HOME|\$\{HOME\})/) {
		# Under home .. return it
		&require_useradmin();
		return $home_base;
		}
	elsif ($config{'ldap_mailstore'} !~ /^\//) {
		return $pfx;
		}
	else {
		# Get fixed directory
		local $dir = $config{'ldap_mailstore'};
		$dir =~ s/\$.*$//;
		$dir =~ s/\/[^\/]*$//;
		return $dir || "/";
		}
	}
# If we get here, assume that mail is under home dirs
local %uconfig = &foreign_config("useradmin");
return $home_base;
}

# mail_domain_base(&domain)
# Returns the directory under which user mail files are located for some
# domain, or undef
sub mail_domain_base
{
if ($config{'mail_system'} == 4) {
	# Guess base for domain from mailstore pattern
	local $guess = { 'user' => 'USER', 'home' => 'HOME' };
	&userdom_substitutions($guess, $_[0]);
	local $dir = &add_ldapmessagestore(
		&substitute_domain_template($config{'ldap_mailstore'}, $guess));
	if ($dir =~ /^(.*)\/\Q$_[0]->{'dom'}\E/) {
		return $1;
		}
	return undef;
	}
elsif ($config{'mail_system'} == 5) {
	# All mail for VPOPmail is under the domain's directory
	return "$config{'vpopmail_dir'}/domains/$_[0]->{'dom'}";
	}
elsif (&mail_under_home()) {
	return "$_[0]->{'home'}/homes";
	}
else {
	# There is no base directory
	return undef;
	}
}

# read_mail_link(&user, &domain)
sub read_mail_link
{
if (&foreign_available("mailboxes")) {
	# Use mailboxes module if possible
	local %mconfig = &foreign_config("mailboxes");
	local %minfo = &get_module_info("mailboxes");
	if ($mconfig{'mail_system'} == $config{'mail_system'}) {
		# Read a Unix user's mail
		if ($config{'mail_system'} == 5) {
			return "../mailboxes/list_mail.cgi?user=".
			       $_[0]->{'user'}."\@".$_[1]->{'dom'};
			}
		else {
			return "../mailboxes/list_mail.cgi?user=".
			       $_[0]->{'user'};
			}
		}
	else {
		# Access mail file directly
		return "../mailboxes/list_mail.cgi?user=".
			&urlize(user_mail_file($_[0]));
		}
	}
else {
	# No mail reading module available
	return undef;
	}
}

# postfix_installed()
# Returns 1 if postfix is installed
sub postfix_installed
{
return &foreign_installed("postfix", 1) == 2;
}

# sendmail_installed()
# Returns 1 if postfix is installed
sub sendmail_installed
{
return &foreign_installed("sendmail", 1) == 2;
}

# qmail_installed()
# Returns 1 if qmail is installed
sub qmail_installed
{
return &foreign_installed("qmailadmin", 1) == 2;
}

# qmail_ldap_installed()
# Returns 1 if qmail is installed, and supports LDAP
sub qmail_ldap_installed
{
return 0 if (!&qmail_installed());
local %qconfig = &foreign_config("qmailadmin");
return -r "$qconfig{'qmail_dir'}/control/ldapserver" ? 1 : 0;
}

# qmail_vpopmail_installed()
# Returns 1 if qmail is installed, and the VPOPMail extensions
sub qmail_vpopmail_installed
{
return 0 if (!&qmail_installed());
return -x "$config{'vpopmail_dir'}/bin/vadddomain";
}

# check_alias_clash(name)
# Checks if an alias with the given name already exists
sub check_alias_clash
{
&require_mail();
if ($config{'mail_system'} == 1) {
	local @aliases = &sendmail::list_aliases($sendmail_afiles);
	local ($clash) = grep { lc($_->{'name'}) eq lc($_[0]) &&
				$_->{'enabled'} } @aliases;
	return $clash;
	}
elsif ($config{'mail_system'} == 0) {
	local @aliases = &$postfix_list_aliases($postfix_afiles);
	local ($clash) = grep { lc($_->{'name'}) eq lc($_[0]) &&
				$_->{'enabled'} } @aliases;
	return $clash;
	}
return undef;
}

# backup_mail(&domain, file, &options)
# Saves all mail aliases and mailbox users for this domain
sub backup_mail
{
&require_mail();

# Create dummy file
&open_tempfile(FILE, ">$_[1]");
&close_tempfile(FILE);

# Build file of all virtusers. Each line contains one virtuser address and
# it's destinations, in alias-style format. Those used by some plugin (like
# Mailman) are not included
&$first_print($text{'backup_mailaliases'});
&open_tempfile(AFILE, ">$_[1]_aliases");
local $a;
foreach $a (&list_domain_aliases($_[0], 1)) {
	&print_tempfile(AFILE, $a->{'from'},": ");
	&print_tempfile(AFILE, join(",", @{$a->{'to'}}),"\n");
	}
&close_tempfile(AFILE);
&$second_print($text{'setup_done'});

# Build file of all mailboxes. Each user has a passwd-file style line with
# the email address and quotas appended, followed by a list of destination
# addresses.
&$first_print($text{'backup_mailusers'});
&open_tempfile(UFILE, ">$_[1]_users");
local $u;
foreach $u (&list_domain_users($_[0])) {
	&print_tempfile(UFILE, join(":", $u->{'user'}, $u->{'pass'},
			      $u->{'webowner'} ? 'w' : $u->{'uid'}, $u->{'gid'},
			      $u->{'real'}, $u->{'home'}, $u->{'shell'},
			      $u->{'email'}));

	# Add home and mail quotas
	if (&has_home_quotas() && $u->{'unix'}) {
		&print_tempfile(UFILE, ":$u->{'quota'}");
		if (&has_mail_quotas()) {
			&print_tempfile(UFILE, ":$u->{'mquota'}");
			}
		else {
			&print_tempfile(UFILE, ":-");
			}
		}
	elsif ($u->{'mailquota'}) {
		&print_tempfile(UFILE, ":$u->{'qquota'}");
		&print_tempfile(UFILE, ":-");
		}
	else {
		&print_tempfile(UFILE, ":-:-");
		}

	# Add databases
	local (@dbstr, %donetype);
	foreach my $db (@{$u->{'dbs'}}) {
		push(@dbstr, $db->{'type'}." ".$db->{'name'});
		$donetype{$db->{'type'}}++;
		}
	&print_tempfile(UFILE, ":".(join(";", @dbstr) || "-"));

	# Add database-type passwords
	local (@passstr);
	foreach my $t (keys %donetype) {
		push(@passstr, $t." ".$u->{$t."_pass"});
		}
	&print_tempfile(UFILE, ":".(join(";", @passstr) || "-"));

	# Add secondary groups
	&print_tempfile(UFILE, ":".(join(";", @{$u->{'secs'}}) || "-"));

	&print_tempfile(UFILE, "\n");
	&print_tempfile(UFILE, join(",", @{$u->{'to'}}),"\n");
	}
&close_tempfile(UFILE);

# Copy plain text passwords file too
if (-r "$plainpass_dir/$_[0]->{'id'}") {
	&copy_source_dest("$plainpass_dir/$_[0]->{'id'}", "$_[1]_plainpass");
	}

# Copy no-spam flags file too
if (-r "$nospam_dir/$_[0]->{'id'}") {
	&copy_source_dest("$nospam_dir/$_[0]->{'id'}", "$_[1]_nospam");
	}

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

if (!&mail_under_home() && $_[2]->{'mailfiles'}) {
	# Backup actual mail files too..
	local $mbase = &mail_system_base();
	local @mfiles;
	&$first_print($text{'backup_mailfiles'});
	foreach $u (&list_domain_users($_[0])) {
		local $umf = &user_mail_file($u);
		if ($umf =~ s/^$mbase\///) {
			push(@mfiles, $umf) if (-r "$mbase/$umf");
			}
		}
	if (!@mfiles) {
		&$second_print($text{'backup_mailfilesnone'});
		}
	else {
		local $mfiles = join(" ", map { quotemeta($_) } @mfiles);
		local $out;
		&execute_command("cd '$mbase'; tar cf '$_[1]_files' $mfiles",
				 undef, \$out, \$out);
		if ($?) {
			&$second_print(&text('backup_mailfilesfailed',
					     "<pre>$out</pre>"));
			return 0;
			}
		else {
			&$second_print($text{'setup_done'});
			}
		}
	}

# Backup all user cron jobs
&foreign_require("cron", "cron-lib.pl");
&$first_print($text{'backup_mailcrons'});
local $croncount = 0;
foreach $u (&list_domain_users($_[0], 1)) {
	local $cronfile = &cron::cron_file({ 'user' => $u->{'user'} });
	if (-r $cronfile) {
		&copy_source_dest($cronfile, $_[1]."_cron_".$u->{'user'});
		$croncount++;
		}
	}
&open_tempfile(COUNT, ">$_[1]_cron");
&print_tempfile(COUNT, $croncount,"\n");
&close_tempfile(COUNT);
if ($croncount) {
	&$second_print($text{'setup_done'});
	}
else {
	&$second_print($text{'backup_mailfilesnone'});
	}

return 1;
}

# restore_mail(&domain, file, &options, &all-options)
sub restore_mail
{
local ($u, %olduid, @errs);
if ($_[2]->{'mailuser'}) {
	# Just doing a single user .. delete him first if he exists
	&$first_print(&text('restore_mailusers2', $_[2]->{'mailuser'}));
	($u) = grep { $_->{'user'} eq $_[2]->{'mailuser'} ||
	      &remove_userdom($_->{'user'}, $_[0]) eq $_[2]->{'mailuser'} }
	      &list_domain_users($_[0], 1);
	if ($u) {
		$olduid{$u->{'user'}} = $u->{'uid'};
		&delete_user($u, $_[0]);
		}
	}
else {
	# Delete all mailboxes (but not home dirs) and re-create
	&$first_print($text{'restore_mailusers'});
	foreach $u (&list_domain_users($_[0], 1)) {
		$olduid{$u->{'user'}} = $u->{'uid'};
		&delete_user($u, $_[0]);
		}
	}
local %exists;
foreach $u (&list_all_users()) {
	$exists{$u->{'name'},$u->{'unix'}} = $u;
	}
local $foundmailuser;
local $_;
open(UFILE, "$_[1]_users");
while(<UFILE>) {
	s/\r|\n//g;
	local @user = split(/:/, $_);
	$_ = <UFILE>;
	s/\r|\n//g;
	if ($_[2]->{'mailuser'}) {
		# Skip all users except the specified one
		if ($user[0] eq $_[2]->{'mailuser'} ||
		    &remove_userdom($user[0], $_[0]) eq $_[2]->{'mailuser'}) {
			$foundmailuser = $user[0];
			}
		else {
			next;
			}
		}
	local @to = split(/,/, $_);
	if ($user[0] eq $_[0]->{'user'}) {
		# Domain owner, just update alias list
		local @users = &list_domain_users($_[0]);
		local ($uinfo) = grep { $_->{'user'} eq $_[0]->{'user'}} @users;
		local %old = %$uinfo;
		$uinfo->{'email'} = $user[7];
		$uinfo->{'to'} = \@to;
		&modify_user($uinfo, \%old, $_[0]);
		}
	else {
		# Need to create user
		local $uinfo = &create_initial_user($_[0], 0, $user[2] eq 'w');
		if ($exists{$user[0],$uinfo->{'unix'}}) {
			push(@errs, &text('restore_mailexists', $user[0]));
			next;
			}
		$uinfo->{'user'} = $user[0];
		$uinfo->{'pass'} = $user[1];
		if ($user[2] eq 'w') {
			# Web management user, so user same UID as server
			$uinfo->{'uid'} = $_[0]->{'uid'};
			}
		elsif ($olduid{$user[0]}) {
			# Use old UID
			$uinfo->{'uid'} = $olduid{$user[0]};
			}
		elsif ($_[3]->{'reuid'}) {
			# Re-allocate UID
			local %taken;
			&build_taken(\%taken);
			$uinfo->{'uid'} = &allocate_uid(\%taken);
			}
		else {
			# Stick with original
			$uinfo->{'uid'} = $user[2];
			}
		$uinfo->{'gid'} = $_[0]->{'gid'};
		$uinfo->{'real'} = $user[4];
		if ($uinfo->{'fixedhome'}) {
			# Home directory is fixed, so don't set
			}
		elsif ($_[5]->{'home'} && $_[5]->{'home'} ne $_[0]->{'home'}) {
			# Restoring under different domain home, so need to fix
			# user's home
			$uinfo->{'home'} = $user[5];
			$uinfo->{'home'} =~s/^$_[5]->{'home'}/$_[0]->{'home'}/g;
			}
		else {
			# Use home from original
			$uinfo->{'home'} = $user[5];
			}
		$uinfo->{'shell'} = $user[6];
		$uinfo->{'email'} = $user[7];
		$uinfo->{'to'} = \@to;
		if ($uinfo->{'mailquota'}) {
			$uinfo->{'qquota'} = $user[8];
			}
		elsif ($uinfo->{'unix'} && !$uinfo->{'noquota'}) {
			$uinfo->{'quota'} = $user[8];
			$uinfo->{'mquota'} = $user[9];
			}

		# Restore databases
		if ($user[10] && $user[10] ne "-") {
			local @dbs = split(/;/, $user[10]);
			foreach my $db (@dbs) {
				my ($dbtype, $dbname) = split(/\s+/, $db, 2);
				push(@{$uinfo->{'dbs'}}, { 'type' => $dbtype,
							   'name' => $dbname });
				}
			}

		# Restore database passwords
		if ($user[11] && $user[11] ne "-") {
			local @dbpass = split(/;/, $user[11]);
			foreach my $db (@dbpass) {
				my ($dbtype, $dbpass) = split(/\s+/, $db, 2);
				$uinfo->{$dbtype."_pass"} = $dbpass;
				}
			}

		# Restore secondary groups
		if ($user[12] && $user[12] ne "-") {
			$uinfo->{'secs'} = [ split(/;/, $user[12]) ];
			}

		&create_user($uinfo, $_[0]);
		}
	}
close(UFILE);

# Restore plain-text password file too
if (-r "$_[1]_plainpass") {
	if ($_[2]->{'mailuser'}) {
		# Just copy one plain password
		local (%oldplain, %newplain);
		&read_file("$_[1]_plainpass", \%oldplain);
		&read_file("$plainpass_dir/$_[0]->{'id'}", \%newplain);
		$newplain{$_[2]->{'mailuser'}} = $oldplain{$_[2]->{'mailuser'}};
		$newplain{$_[2]->{'mailuser'}." encrypted"} =
			$oldplain{$_[2]->{'mailuser'}." encrypted"};
		&write_file("$plainpass_dir/$_[0]->{'id'}", \%newplain);
		}
	else {
		# Copy the whole file
		&copy_source_dest("$_[1]_plainpass",
				  "$plainpass_dir/$_[0]->{'id'}");
		}
	}

# Restore no-spam flags file too
if (-r "$_[1]_nospam") {
	if ($_[2]->{'mailuser'}) {
		# Just copy one flag
		local (%oldspam, %newspam);
		&read_file("$_[1]_nospam", \%oldspam);
		&read_file("$nospam_dir/$_[0]->{'id'}", \%newspam);
		$newspam{$_[2]->{'mailuser'}} = $oldspam{$_[2]->{'mailuser'}};
		&write_file("$nospam_dir/$_[0]->{'id'}", \%newspam);
		}
	else {
		# Copy the whole file
		&copy_source_dest("$_[1]_nospam",
				  "$nospam_dir/$_[0]->{'id'}");
		}
	}

if (@errs) {
	&$second_print(&text('restore_mailerrs', join(" ", @errs)));
	}
elsif ($_[2]->{'mailuser'} && !$foundmailuser) {
	&$second_print(&text('restore_mailnosuch', $_[2]->{'mailuser'}));
	}
else {
	&$second_print($text{'setup_done'});
	}

if (!$_[2]->{'mailuser'}) {
	# Delete all aliases and re-create (except for those used by plugins
	# such as mailman)
	&$first_print($text{'restore_mailaliases'});
	local $a;
	foreach $a (&list_domain_aliases($_[0], 1)) {
		&delete_virtuser($a);
		}
	local $_;
	open(AFILE, "$_[1]_aliases");
	while(<AFILE>) {
		if (/^(\S+):\s*(.*)/) {
			local $virt = { 'from' => $1,
					'to' => [ split(/,/, $2) ] };
			if ($virt->{'to'}->[0] =~ /^(\S+)\\@(\S+)$/ &&
			    $config{'mail_system'} == 0) {
				# Virtusers is to a local user with an @ in
				# the name, like foo\@bar.com. But on Postfix
				# this won't work - instead, we need to use the
				# alternate foo-bar.com format.
				$virt->{'to'}->[0] = $1."-".$2;
				}
			&create_virtuser($virt);
			}
		}
	close(AFILE);
	&sync_alias_virtuals($_[0]);
	&$second_print($text{'setup_done'});
	}

if (-r "$_[1]_files" && $_[2]->{'mailfiles'} &&
    (!$_[2]->{'mailuser'} || $foundmailuser)) {
	local $xtract;
	if (!&mail_under_home()) {
		# Can just extract all mail files in /var/mail
		$xtract = &mail_system_base();
		}
	else {
		# This system puts mail files under homes, but the source
		# system used /var/mail ! So we need to extract to a temp
		# location and then move mail files.
		$xtract = &transname();
		&make_dir($xtract, 0700);
		}
	local $out;
	if ($_[2]->{'mailuser'}) {
		# Just do one user
		&$first_print(&text('restore_mailfiles3', $_[2]->{'mailuser'}));
		&execute_command("cd '$xtract' && tar xf '$_[1]_files' '$foundmailuser' 2>&1", undef, \$out, \$out);
		}
	else {
		# Do all users
		&$first_print($text{'restore_mailfiles'});
		&execute_command("cd '$xtract' && tar xf '$_[1]_files' 2>&1",
				 undef, \$out, \$out);
		}
	if ($?) {
		&$second_print(&text('backup_mailfilesfailed',
				     "<pre>$out</pre>"));
		return 0;
		}
	else {
		&$second_print($text{'setup_done'});
		}

	if (&mail_under_home()) {
		# Move mail from temp directory to homes
		&foreign_require("mailboxes", "mailboxes-lib.pl");
		local @users = &list_domain_users($_[0]);
		if ($_[2]->{'mailuser'}) {
			@users = grep { $_->{'user'} eq $foundmailuser } @users;
			}
		foreach my $u (@users) {
			local $path = "$xtract/$u->{'user'}";
			local $sf = { 'type' => -d $path ? 1 : 0,
				      'file' => $path };
			local ($df) =
				&mailboxes::list_user_folders($u->{'user'});
			&mailboxes::mailbox_empty_folder($df);
			&mailboxes::mailbox_copy_folder($sf, $df);
			}
		}
	}
# XXX deal with case where old system used ~/Maildir and this one uses /var/mail

# Restore Cron job files
if (-r "$_[1]_cron") {
	&$first_print($text{'restore_mailcrons'});
	&foreign_require("cron", "cron-lib.pl");
	foreach $u (&list_domain_users($_[0], 1)) {
		next if ($_[2]->{'mailuser'} && $u->{'user'} ne $foundmailuser);
		local $cf = $_[1]."_cron_".$u->{'user'};
		$cf = "/dev/null" if (!-r $cf);
		&copy_source_dest($cf, $cron::cron_temp_file);
		&cron::copy_crontab($u->{'user'});
		}
	&$second_print($text{'setup_done'});
	}

# Set mailbox user home directory permissions
local $hb = "$_[0]->{'home'}/$config{'homes_dir'}";
foreach $u (&list_domain_users($_[0], 1)) {
	if (-d $u->{'home'} && &is_under_directory($hb, $u->{'home'}) &&
	    (!$_[2]->{'mailuser'} || $u->{'user'} eq $foundmailuser)) {
		&execute_command("chown -R $u->{'uid'}:$u->{'gid'} ".
		       quotemeta($u->{'home'}));
		}
	}

# Create autoreply file links
if (defined(&create_autoreply_alias_links)) {
	&create_autoreply_alias_links($_[0]);
	}

return 1;
}

# show_backup_mail(&options)
# Returns HTML for mail backup option inputs
sub show_backup_mail
{
if (&mail_under_home()) {
	# Option makes no sense in this case, as the home directories backup
	# will catch it
	return "<input type=hidden name=mail_mailfiles value='$opts{'mailfiles'}'>";
	}
else {
	# Offer to backup mail files
	return sprintf
		"(<input type=checkbox name=mail_mailfiles value=1 %s> %s)",
		$opts{'mailfiles'} ? "checked" : "", $text{'backup_mailfiles2'};
	}
}

# parse_backup_mail(&in)
# Parses the inputs for mail backup options
sub parse_backup_mail
{
local %in = %{$_[0]};
return { 'mailfiles' => $in{'mail_mailfiles'} };
}

# show_restore_mail(&options, &domain)
# Returns HTML for mail restore option inputs
sub show_restore_mail
{
local $rv;
if (&mail_under_home()) {
	# Option makes no sense in this case, as the home directories backup
	# will catch it
	$rv = &ui_hidden("mail_mailfiles", $_[0]->{'mailfiles'});
	}
else {
	# Offer to restore mail files
	$rv = &ui_checkbox("mail_mailfiles", 1, $text{'restore_mailfiles2'},
			   $_[0]->{'mailfiles'});
	}
if ($_[1]) {
	$rv .= "<br>".$text{'restore_mailuser'}." ".
		&ui_textbox("mail_mailuser", $_[0]->{'mailuser'}, 15);
	}
return $rv;
}

# parse_restore_mail(&in, &domain)
# Parses the inputs for mail backup options
sub parse_restore_mail
{
local %in = %{$_[0]};
return { 'mailfiles' => $in{'mail_mailfiles'},
	 'mailuser' => $in{'mail_mailuser'} };
}

# check_clash(name, dom)
# Returns 1 if a virtuser or user with the name already exists.
# Returns 2 if an alias with the same mailbox name already exists.
sub check_clash
{
&require_mail();
local @virts = &list_virtusers();
local ($clash) = grep { $_->{'from'} eq $_[0]."\@".$_[1] } @virts;
return 1 if ($clash);
if ($config{'mail_system'} == 1) {
	# Check for a Sendmail alias with the same name as the user
	local @aliases = &sendmail::list_aliases($sendmail_afiles);
	local $an = $_[0] ? "$_[0]-$_[1]" : "default-$_[1]";
	($clash) = grep { ($config{'alias_clash'} &&
			   $_[0] && $_->{'name'} eq $_[0]) ||
			  $_->{'name'} eq $an } @aliases;
	return 2 if ($clash);
	}
elsif ($config{'mail_system'} == 0) {
	# Check for a Postfix alias with the same name as the user
	local @aliases = &$postfix_list_aliases($postfix_afiles);
	local $an = $_[0] ? "$_[0]-$_[1]" : "default-$_[1]";
	($clash) = grep { ($config{'alias_clash'} &&
			   $_[0] && $_->{'name'} eq $_[0]) ||
			  $_->{'name'} eq $an } @aliases;
	return 2 if ($clash);
	}
elsif ($config{'mail_system'} == 2) {
	# Check for a Qmail .qmail file with the same name as the user
	local @aliases = &qmailadmin::list_aliases();
	($clash) = grep { $config{'alias_clash'} && $_[0] && $_ eq $_[0] }
			@aliases;
	return 2 if ($clash);
	}
return 0;
}

# check_depends_mail(&dom)
# Ensure that a mail domain has a home directory and Unix group
sub check_depends_mail
{
# Check for virtusers file
&require_mail();
if ($config{'mail_system'} == 1) {
	$sendmail_vfile || return $text{'setup_esendmailvfile'};
	@$sendmail_afiles || return $text{'setup_esendmailafile'};
	!$config{'generics'} || $sendmail_gdbm ||
		return $text{'setup_esendmailgfile'};
	}
elsif ($config{'mail_system'} == 0) {
	@virtual_map_files || $virtual_map_backends[0] eq "mysql" ||
	    $virtual_map_backends[0] eq "ldap" ||
		return $text{'setup_epostfixvfile'};
	@$postfix_afiles || $alias_backends[0] eq "mysql" ||
	    $alias_backends[0] eq "ldap" || return $text{'setup_epostfixafile'};
	!$config{'generics'} || $canonical_maps ||
		return $text{'setup_epostfixgfile'};
	}
if ($_[0]->{'alias'}) {
	# If this is an alias domain, then no home is needed
	return undef;
	}
elsif ($_[0]->{'parent'}) {
	# If this is a sub-domain, then the parent needs a Unix user
	local $parent = &get_domain($_[0]->{'parent'});
	return $parent->{'unix'} ? undef : $text{'setup_edepmail'};
	}
elsif ($config{'mail_system'} == 5) {
	# For a VPOPMail domain, there are no dependencies!
	return undef;
	}
else {
	# For a top-level domain, it needs a Unix user
	return $_[0]->{'unix'} ? undef : $text{'setup_edepmail'};
	}
}

# mail_system_name([num])
sub mail_system_name
{
local $num = defined($_[0]) ? $_[0] : $config{'mail_system'};
return $text{'mail_system_'.$num} || "???";
}

# create_replace_mapping(mapname, &map, [&force-file])
# Add or replace a Postfix mapping
sub create_replace_mapping
{
local $maps = &postfix::get_maps($_[0], $_[2]);
local ($clash) = grep { $_->{'name'} eq $_[1]->{'name'} } @$maps;
if ($clash) {
	&postfix::modify_mapping($_[0], $clash, $_[1]);
	}
else {
	&postfix::create_mapping($_[0], $_[1], $_[2]);
	}
}

# mail_system_needs_group()
# Returns 1 if the current mail system needs a Unix group for mailboxes
sub mail_system_needs_group
{
return 0 if ($config{'mail_system'} == 5);	# never for vpopmail
#return 0 if ($config{'mail_system'} == 4 &&	# not for Qmail+LDAP,
#	     !$config{'ldap_unix'});		# if users are non-unix
return 1;
}

# bandwidth_all_mail(&domains-list, &starts-hash, &bw-hash-hash)
# Scans through the mail log, and updates all domains at once. Returns a new
# hash reference of start times.
sub bandwidth_all_mail
{
local ($doms, $starts, $bws) = @_;
local %max_ltime = %$starts;
local %max_updated;
local $maillog = $config{'bw_maillog'};
$maillog = &get_mail_log() if ($maillog eq "auto");
return $starts if (!$maillog);
require 'timelocal.pl';

# Build a map from domain names to objects
local %maildoms;
foreach my $d (@$doms) {
	$maildoms{$d->{'dom'}} = $d;
	foreach my $md (split(/\s+/, $d->{'bw_maildoms'})) {
		$maildoms{$md} = $d;
		}
	}

# Find the minimum last activity time
local $start_now = time();
local $min_ltime = $start_now+24*60*60;
foreach my $lt (values %$starts) {
	$min_ltime = $lt if ($lt && $lt < $min_ltime);
	}

local $f;
foreach $f ($config{'bw_maillog_rotated'} ?
	    &all_log_files($maillog, $min_ltime) :
	    ( $maillog )) {
	local $_;
	&open_uncompress_file(LOG, $f);

	# Scan the log, looking for entries for various mail systems
	local %sizes;
	local $now = time();
	local @tm = localtime($now);
	while(<LOG>) {
		# Sendmail / postfix formats
		s/\r|\n//g;
		if (/^(\S+)\s+(\d+)\s+(\d+):(\d+):(\d+)\s+(\S+)\s+(\S+):\s+(\S+):\s+from=(\S+),\s+size=(\d+)/) {
			# The initial From: line that contains the size
			$sizes{$8} = $10;
			}
		elsif (/^(\S+)\s+(\d+)\s+(\d+):(\d+):(\d+)\s+(\S+)\s+(\S+):\s+(\S+):\s+to=(\S+),(\s+orig_to=(\S+))?/) {
			# A To: line that has the local recipient
			local $ltime = timelocal($5, $4, $3, $2,
			    $apache_mmap{lc($1)}, $tm[5]);
			if ($ltime > $now+(24*60*60)) {
				# Must have been last year!
				$ltime = timelocal($5, $4, $3, $2,
				     $apache_mmap{lc($1)}, $tm[5]-1);
				}
			local $user = $11 || $9;
			local $sz = $sizes{$8};
			$user =~ s/^<(.*)>/$1/;
			$user =~ s/,$//;
			local ($mb, $dom) = split(/\@/, $user);
			local $md = $maildoms{$dom};
			if ($md) {
				if ($ltime > $max_ltime{$md->{'id'}}) {
					# Update most recent seen time for
					# this domain.
					$max_ltime{$md->{'id'}} = $ltime;
					$max_updated{$md->{'id'}} = 1;
					}
				if ($ltime > $starts->{$md->{'id'}} && $sz) {
					# To a user in a hosted domain
					local $day =
					    int($ltime / (24*60*60));
					$bws->{$md->{'id'}}->
						{"mail_".$day} += $sz;
					}
				}
			}

		# Qmail format
		elsif (/^\@(\S+)\s+info\s+msg\s+(\S+):\s+bytes\s+(\d+)\s+from/ ||
		       /(\d+\.\d+)\s+info\s+msg\s+(\S+):\s+bytes\s+(\d+)\s+from/) {
			# From: line with size
			$sizes{$2} = $3;
			}
		elsif (/^\@(\S+)\s+starting\s+delivery\s+(\S+):\s+msg\s+(\S+)\s+to\s+local\s+(\S+)/ ||
		       /(\d+\.\d+)\s+starting\s+delivery\s+(\S+):\s+msg\s+(\S+)\s+to\s+local\s+(\S+)/) {
			# To: line with actual address
			local $sz = $sizes{$3};
			local $user = $4;
			local $ltime = &tai64_time($1);
			$user =~ s/^<(.*)>/$1/;
			local ($mb, $dom) = split(/\@/, $user);
			local $md = $maildoms{$dom};
			if ($md) {
				if ($ltime > $max_ltime{$md->{'id'}}) {
					# Update most recent seen time for
					# this domain.
					$max_ltime{$md->{'id'}} = $ltime;
					$max_updated{$md->{'id'}} = 1;
					}
				if ($ltime > $starts->{$md->{'id'}} && $sz) {
					# To a user in this domain
					local $day =
					    int($ltime / (24*60*60));
					$bws->{$md->{'id'}}->
						{"mail_".$day} += $sz;
					}
				}

			}
		}
	close(LOG);
	}

# For any domain for which max_ltime was not updated (because we didn't see
# any email), set it to the current time
foreach my $did (keys %max_ltime) {
	if (!$max_updated{$did}) {
		$max_ltime{$did} = $start_now;
		}
	}

return \%max_ltime;
}

sub tai64_time
{
if ($_[0] =~ /^40000000(\S{8})/) {
	return hex($1);
	}
elsif ($_[0] =~ /^(\d+)\.(\d+)$/) {
	return $1;
	}
return undef;
}

# can_users_without_mail(&domain)
# Returns 1 if some domain can have users without mail enabled. Not allowed
# when using VPOPMail and Qmail+LDAP
sub can_users_without_mail
{
return $config{'mail_system'} != 4 && $config{'mail_system'} != 5;
}

# sysinfo_mail()
# Returns the mail server version and path
sub sysinfo_mail
{
&require_mail();
if ($config{'mail_system'} == 0) {
	# Postfix
	local $prog = -x "/usr/lib/sendmail" ? "/usr/lib/sendmail" :
			&has_command("sendmail");
	if (!$postfix::postfix_version) {
		local $out = &backquote_command("$postfix::config{'postfix_config_command'} mail_version 2>&1", 1);
		$postfix_version = $1 if ($out =~ /mail_version\s*=\s*(.*)/);
		}
	return ( [ $text{'sysinfo_postfix'}, $postfix::postfix_version ],
		 [ $text{'sysinfo_mailprog'}, $prog." -t" ] );
	}
elsif ($config{'mail_system'} == 1) {
	# Sendmail
	local $ever = &sendmail::get_sendmail_version();
	return ( [ $text{'sysinfo_sendmail'}, $ever ],
		 [ $text{'sysinfo_mailprog'},
			$sendmail::config{'sendmail_path'}." -t" ] );
	}
elsif ($config{'mail_system'} == 2 || $config{'mail_system'} == 4 ||
       $config{'mail_system'} == 5) {
	# Some Qmail variant
	return ( [ $text{'sysinfo_qmail'}, "Unknown" ],
		 [ $text{'sysinfo_mailprog'},
			"$qmailadmin::config{'qmail_dir'}/bin/qmail-inject" ] );
	}
else {
	return ( );
	}
}

# mail_system_has_procmail()
# Returns 1 if the current mail system is configured to use procmail
sub mail_system_has_procmail
{
&require_mail();
if ($config{'mail_system'} == 0) {
	# Check postfix delivery command
	local $cmd = &postfix::get_real_value("mailbox_command");
	return $cmd =~ /procmail/;
	}
elsif ($config{'mail_system'} == 1) {
	# See if sendmail's local mailer is procmail
	local $conf = &sendmail::get_sendmailcf();
	foreach my $m (&sendmail::find_type("M", $conf)) {
		if ($m->{'value'} =~ /^local.*procmail/) {
			return 1;
			}
		}
	return 0;
	}
elsif ($config{'mail_system'} == 2 || $config{'mail_system'} == 4) {
	# Check Qmail rc script for use of procmail as default delivery
	local $got;
	local $_;
	open(RC, "$qmailadmin::config{'qmail_dir'}/rc");
	while(<RC>) {
		s/#.*$//;
		$got = 1 if (/procmail/);
		}
	close(RC);
	return $got;
	}
elsif ($config{'mail_system'} == 5) {
	# I don't think vpopmail supports procmail
	return 0;
	}
return 0;
}

# get_mail_log()
# Returns the default mail log file for this system
sub get_mail_log
{
if (&foreign_installed("syslog")) {
	# Try syslog first
	&foreign_require("syslog", "syslog-lib.pl");
	local $conf = &syslog::get_config();
	foreach my $c (@$conf) {
		next if (!$c->{'active'});
		next if (!$c->{'file'});
		foreach my $s (@{$c->{'sel'}}) {
			local ($fac,$level) = split(/\./, $s);
			return $c->{'file'} if ($fac =~ /mail/ &&
						$level ne "none");
			}
		}
	}
elsif (&foreign_installed("syslog-ng")) {
	# Try syslog-ng (by looking for a d_mail destination, or any dest
	# with mail in the name)
	&foreign_require("syslog-ng", "syslog-ng-lib.pl");
	local $conf = &syslog_ng::get_config();
	local @dests = &syslog_ng::find("destination", $conf);
	local ($dest) = grep { $_->{'name'} eq 'd_mail' } @dests;
	if (!$dest) {
		($dest) = grep { $_->{'name'} =~ /mail/ } @dests;
		}
	if ($dest) {
		return &find_value("file", $dest->{'members'});
		}
	}
# Fall back to common files
foreach my $f ("/var/log/mail", "/var/log/maillog", "/var/log/mail.log") {
	return $f if (-r $f);
	}
return undef;
}

sub startstop_mail
{
local ($typestatus) = @_;
local $msn = $config{'mail_system'} == 0 ? "postfix" :
	     $config{'mail_system'} == 1 ? "sendmail" : "qmailadmin";
local $ms = $text{'mail_system_'.$config{'mail_system'}};
local @rv;
if (defined($typestatus->{$msn}) ? $typestatus->{$msn} == 1
				 : &is_mail_running()) {
	push(@rv,{ 'status' => 1,
		   'name' => &text('index_mname', $ms),
		   'desc' => $text{'index_mstop'},
		   'restartdesc' => $text{'index_mrestart'},
		   'longdesc' => $text{'index_mstopdesc'} } );
	}
else {
	push(@rv,{ 'status' => 0,
		   'name' => &text('index_mname', $ms),
		   'desc' => $text{'index_mstart'},
		   'longdesc' => $text{'index_mstartdesc'} } );
	}
if (&foreign_installed("dovecot")) {
	# Add status for Dovecot
	&foreign_require("dovecot", "dovecot-lib.pl");
	if (&dovecot::is_dovecot_running()) {
		push(@rv,{ 'status' => 1,
			   'feature' => 'dovecot',
			   'name' => &text('index_dname', $ms),
			   'desc' => $text{'index_dstop'},
			   'restartdesc' => $text{'index_drestart'},
			   'longdesc' => $text{'index_dstopdesc'} } );
		}
	else {
		push(@rv,{ 'status' => 0,
			   'feature' => 'dovecot',
			   'name' => &text('index_dname', $ms),
			   'desc' => $text{'index_dstart'},
			   'longdesc' => $text{'index_dstartdesc'} } );
		}
	}
return @rv;
}

sub start_service_mail
{
return &startup_mail_server(1);
}

sub stop_service_mail
{
return &shutdown_mail_server(1);
}

sub start_service_dovecot
{
&foreign_require("dovecot", "dovecot-lib.pl");
return &dovecot::start_dovecot();
}

sub stop_service_dovecot
{
&foreign_require("dovecot", "dovecot-lib.pl");
return &dovecot::stop_dovecot();
}

# check_secondary_mx()
# Returns undef if this system can be a secondary MX, or an error message if not
sub check_secondary_mx
{
local $ms = $config{'mail_system'};
if (!$config{'mail'}) {
	return $text{'newmxs_email'};
	}
elsif ($ms == 3) {
	return $text{'newmxs_emailsystem'};
	}
elsif ($ms == 1 && !&sendmail_installed() ||
       $ms == 0 && !&postfix_installed() ||
       $ms == 2 && !&qmail_installed() ||
       $ms == 4 && !&qmail_ldap_installed() ||
       $ms == 5 && !&qmail_vpopmail_installed()) {
	return &text('newmxs_esystem', $text{'mail_system_'.$ms});
	}
else {
	return undef;
	}
}

# setup_secondary_mx(domain)
# Set up this system as a secondary MX for the given domain. Returns undef on
# success, or an error message on failure.
sub setup_secondary_mx
{
local ($dom) = @_;
&require_mail();
if ($config{'mail_system'} == 1) {
	# Just add to sendmail relay domains file
	local $conf = &sendmail::get_sendmailcf();
	local $cwfile;
	local @dlist = &sendmail::get_file_or_config($conf, "R", undef,
						     \$cwfile);
	if (&indexof(lc($dom), (map { lc($_) } @dlist)) >= 0) {
		return $text{'newmxs_already'};
		}
	&lock_file($cwfile) if ($cwfile);
	&lock_file($sendmail::config{'sendmail_cf'});
	&sendmail::add_file_or_config($conf, "R", $dom);
	&flush_file_lines();
	&unlock_file($sendmail::config{'sendmail_cf'});
	&unlock_file($cwfile) if ($cwfile);
	&sendmail::restart_sendmail();
	}
elsif ($config{'mail_system'} == 0) {
	# Add to Postfix relay domains
	local @rd = split(/[, ]+/,&postfix::get_current_value("relay_domains"));
	if (&indexof(lc($dom), (map { lc($_) } @rd)) >= 0) {
		return $text{'newmxs_already'};
		}
	@rd = &unique(@rd, $dom);
	&lock_file($postfix::config{'postfix_config_file'});
	&postfix::set_current_value("relay_domains", join(", ", @rd));
	&unlock_file($postfix::config{'postfix_config_file'});
	}
elsif ($config{'mail_system'} == 2 || $config{'mail_system'} == 4 ||
       $config{'mail_system'} == 5) {
	# Add to Qmail rcpthosts file
	local $rlist = &qmailadmin::list_control_file("rcpthosts");
	if (&indexof(lc($dom), (map { lc($_) } @$rlist)) >= 0) {
		return $text{'newmxs_already'};
		}
	push(@$rlist, $dom);
	&qmailadmin::save_control_file("rcpthosts", $rlist);
	}
return undef;
}

# delete_secondary_mx(domain)
# Removes the given domain from the secondary MX list for this server. Returns
# an error message or undef.
sub delete_secondary_mx
{
local ($dom) = @_;
&require_mail();
if ($config{'mail_system'} == 1) {
	# Just remove from sendmail relay domains file
	local $conf = &sendmail::get_sendmailcf();
	local $cwfile;
	local @dlist = &sendmail::get_file_or_config($conf, "R", undef,
						     \$cwfile);
	local $idx = &indexof(lc($dom), (map { lc($_) } @dlist));
	if ($idx < 0) {
		return $text{'newmxs_missing'};
		}
	&lock_file($cwfile) if ($cwfile);
	&lock_file($sendmail::config{'sendmail_cf'});
	&sendmail::delete_file_or_config($conf, "R", $dom);
	&flush_file_lines();
	&unlock_file($sendmail::config{'sendmail_cf'});
	&unlock_file($cwfile) if ($cwfile);
	&sendmail::restart_sendmail();
	}
elsif ($config{'mail_system'} == 0) {
	# Add to Postfix relay domains
	local @rd = split(/[, ]+/,&postfix::get_current_value("relay_domains"));
	local $idx = &indexof(lc($dom), (map { lc($_) } @rd));
	if ($idx < 0) {
		return $text{'newmxs_missing'};
		}
	splice(@rd, $idx, 1);
	&lock_file($postfix::config{'postfix_config_file'});
	&postfix::set_current_value("relay_domains", join(", ", @rd));
	&unlock_file($postfix::config{'postfix_config_file'});
	}
elsif ($config{'mail_system'} == 2 || $config{'mail_system'} == 4 ||
       $config{'mail_system'} == 5) {
	# Add to Qmail rcpthosts file
	local $rlist = &qmailadmin::list_control_file("rcpthosts");
	local $idx = &indexof(lc($dom), (map { lc($_) } @$rlist));
	if ($idx < 0) {
		return $text{'newmxs_missing'};
		}
	splice(@$rlist, $idx, 1);
	&qmailadmin::save_control_file("rcpthosts", $rlist);
	}
return undef;
}

# is_secondary_mx(domain)
# Returns 1 if this server is a secondary MX for the given domain
sub is_secondary_mx
{
local ($dom) = @_;
&require_mail();
if ($config{'mail_system'} == 1) {
	# Check sendmail relay domains file
	local $conf = &sendmail::get_sendmailcf();
	local $cwfile;
	local @dlist = &sendmail::get_file_or_config($conf, "R", undef,
						     \$cwfile);
	local $idx = &indexof(lc($dom), (map { lc($_) } @dlist));
	return $idx < 0 ? 0 : 1;
	}
elsif ($config{'mail_system'} == 0) {
	# Add to Postfix relay domains
	local @rd = split(/[, ]+/,&postfix::get_current_value("relay_domains"));
	local $idx = &indexof(lc($dom), (map { lc($_) } @rd));
	return $idx < 0 ? 0 : 1;
	}
elsif ($config{'mail_system'} == 2 || $config{'mail_system'} == 4 ||
       $config{'mail_system'} == 5) {
	# Add to Qmail rcpthosts file
	local $rlist = &qmailadmin::list_control_file("rcpthosts");
	local $idx = &indexof(lc($dom), (map { lc($_) } @$rlist));
	return $idx < 0 ? 0 : 1;
	}
return 0;
}

sub secondary_error_handler
{
$secondary_error = join("", @_);
}

# setup_on_secondaries(&dom)
# Add this domain to all secondary MX servers
sub setup_on_secondaries
{
local ($dom) = @_;
local @servers = &list_mx_servers();
return if (!@servers);
local @okservers;
&$first_print(&text('setup_mxs',
		join(", ", map { "<tt>$_->{'host'}</tt>" } @servers)));
local @errs;
foreach my $s (@servers) {
	local $err = &setup_one_secondary($dom, $s);
	if ($err) {
		push(@errs, "$s->{'host'} : $err");
		}
	else {
		push(@okservers, $s);
		}
	}
$dom->{'mx_servers'} = join(" ", map { $_->{'id'} } @okservers);
if (@errs) {
	&$second_print($text{'setup_mxserrs'}."<br>\n".
			join("<br>\n", @errs));
	}
else {
	&$second_print($text{'setup_done'});
	}
}

# setup_one_secondary(&domain, &server)
# Add a secondary mail domain on one Webmin server. Returns undef on success or
# an error message on failure.
sub setup_one_secondary
{
local ($dom, $s) = @_;
&remote_error_setup(\&secondary_error_handler);
$secondary_error = undef;
&remote_foreign_require($s, "virtual-server", "virtual-server-lib.pl");
if ($secondary_error) {
	&remote_error_setup(undef);
	return $secondary_error;
	}
local $err = &remote_foreign_call($s, "virtual-server",
				  "setup_secondary_mx", $dom->{'dom'});
&remote_error_setup(undef);
return $err;
}

# delete_on_secondaries(&dom)
# Remove this domain from all secondary MX servers
sub delete_on_secondaries
{
local ($dom) = @_;
local %ids = map { $_, 1 } split(/\s+/, $dom->{'mx_servers'});
local @servers = grep { $ids{$_->{'id'}} } &list_mx_servers();
return if (!@servers);
&$first_print(&text('delete_mxs',
		join(", ", map { "<tt>$_->{'host'}</tt>" } @servers)));
local @errs;
foreach my $s (@servers) {
	local $err = &delete_one_secondary($dom, $s);
	if ($err) {
		push(@errs, "$s->{'host'} : $err");
		}
	}
if (@errs) {
	&$second_print($text{'setup_mxserrs'}."<br>\n".
			join("<br>\n", @errs));
	}
else {
	&$second_print($text{'setup_done'});
	}
delete($dom->{'mx_servers'});
}

# delete_one_secondary(&domain, &server)
# Remove a secondary mail domain on one Webmin server. Returns undef on success
# or an error message on failure.
sub delete_one_secondary
{
local ($dom, $s) = @_;
&remote_error_setup(\&secondary_error_handler);
$secondary_error = undef;
&remote_foreign_require($s, "virtual-server", "virtual-server-lib.pl");
if ($secondary_error) {
	&remote_error_setup(undef);
	return $secondary_error;
	}
local $err = &remote_foreign_call($s, "virtual-server",
				  "delete_secondary_mx", $dom->{'dom'});
&remote_error_setup(undef);
return $err;
}

# is_one_secondary(&domain, &server)
# Checks if some secondary MX server is relaying a domain. Returns '1' if OK,
# '0' if not, or an error message
sub is_one_secondary
{
local ($dom, $s) = @_;
&remote_error_setup(\&secondary_error_handler);
$secondary_error = undef;
&remote_foreign_require($s, "virtual-server", "virtual-server-lib.pl");
if ($secondary_error) {
	&remote_error_setup(undef);
	return $secondary_error;
	}
local $ok = &remote_foreign_call($s, "virtual-server",
				 "is_secondary_mx", $dom->{'dom'});
&remote_error_setup(undef);
return $secondary_error ? $secondary_error : $ok ? 1 : 0;
}

# execute_after_virtuser(&alias, action)
# Runs any command configured to be run after an alias is changed
sub execute_after_virtuser
{
local ($alias, $action) = @_;
return if (!$config{'alias_post_command'});
local %OLDENV = %ENV;
$ENV{'ALIAS_ACTION'} = $action;
$ENV{'ALIAS_FROM'} = $alias->{'from'};
$ENV{'ALIAS_TO'} = join(",", @{$alias->{'to'}});
$ENV{'ALIAS_CMT'} = $alias->{'cmt'};
&system_logged("($config{'alias_post_command'}) 2>&1 </dev/null");
}

# execute_before_virtuser(&alias, action)
# Runs any command configured to be run before an alias is changed
sub execute_before_virtuser
{
local ($alias, $action) = @_;
return if (!$config{'alias_pre_command'});
local %OLDENV = %ENV;
$ENV{'ALIAS_ACTION'} = $action;
$ENV{'ALIAS_FROM'} = $alias->{'from'};
$ENV{'ALIAS_TO'} = join(",", @{$alias->{'to'}});
$ENV{'ALIAS_CMT'} = $alias->{'cmt'};
local $out =&backquote_logged("($config{'alias_pre_command'}) 2>&1 </dev/null");
if ($?) {
	&error(&text('alias_ebefore', "<tt>$out</tt>"));
	}
}

# show_template_mail(&tmpl)
# Outputs HTML for editing email and mailbox related template options
sub show_template_mail
{
local ($tmpl) = @_;

# Email message for server creation
print &ui_table_row(&hlink($text{'tmpl_mail'}, "template_mail"),
	&none_def_input("mail", $tmpl->{'mail_on'}, $text{'tmpl_mailbelow'},
			0, 0, undef, [ "mail", "subject", "cc", "bcc" ]).
	"<br>\n".
	&ui_textarea("mail", $tmpl->{'mail'} eq "none" ? "" :
				join("\n", split(/\t/, $tmpl->{'mail'})),
		     10, 60)."\n".
	&email_template_input(undef, $tmpl->{'mail_subject'},
			      $tmpl->{'mail_cc'}, $tmpl->{'mail_bcc'})
	);

print &ui_table_hr();

# Aliases for new users
local @aliases = $tmpl->{'user_aliases'} eq "none" ? ( ) :
		split(/\t+/, $tmpl->{'user_aliases'});
local @afields = map { ("type_".$_, "val_".$_) } (0..scalar(@aliases)+2);
print &ui_table_row(&hlink($text{'tmpl_aliases'}, "template_aliases_mode"),
	&none_def_input("aliases", $tmpl->{'user_aliases'},
			$text{'tmpl_aliasbelow'}, 0, 0, undef,
			\@afields)."<br>");
&alias_form(\@aliases, " ", undef, "user", "NEWUSER");

# Aliases for new domains
local @aliases = $tmpl->{'dom_aliases'} eq "none" ? ( ) :
			split(/\t+/, $tmpl->{'dom_aliases'});
local $atable = &ui_columns_start([ $text{'tmpl_aliasfrom'}, $text{'tmpl_aliasto'} ]);
local $i = 0;
local @dafields;
foreach my $a (@aliases, undef, undef) {
	local ($from, $to) = split(/=/, $a, 2);
	$atable .= &ui_columns_row([
		&ui_textbox("alias_from_$i", $from, 20),
		&ui_textbox("alias_to_$i", $to, 40) ]);
	push(@dafields, "alias_from_$i", "alias_to_$i");
	$i++;
	}
$atable .= &ui_columns_end();
$atable .= &ui_checkbox("bouncealias", 1,
		        &hlink("<b>$text{'tmpl_bouncealias'}</b>",
		               "template_bouncealias"),
		        $tmpl->{'dom_aliases_bounce'});
push(@dafields, "bouncealias");
print &ui_table_row(&hlink($text{'tmpl_domaliases'},
                           "template_domaliases_mode"),
		    &none_def_input("domaliases", $tmpl->{'dom_aliases'},
				    $text{'tmpl_aliasbelow'}, 0, 0, undef,
				    \@dafields)."\n".$atable);

# Virtusers mode for alias domains
if ($supports_aliascopy) {
	print &ui_table_row(
		&hlink($text{'tmpl_aliascopy'}, "template_aliascopy"),
		&ui_radio("aliascopy", $tmpl->{'aliascopy'},
			  [ $tmpl->{'default'} ? ( )
					       : ( [ "", $text{'default'} ] ),
			    [ 1, $text{'tmpl_aliascopy1'} ],
			    [ 0, $text{'tmpl_aliascopy0'} ] ]));
	}

# Unix groups for mail, FTP and DB users
print &ui_table_hr();
foreach $g ("mailgroup", "ftpgroup", "dbgroup") {
	print &ui_table_row(&hlink($text{'tmpl_'.$g}, "template_".$g),
		    &none_def_input($g, $tmpl->{$g},
			    $text{'tmpl_setgroup'}, 0, 0, undef, [ $g ]).
		    &ui_textbox($g, $tmpl->{$g} eq 'none' ? undef :
					      $tmpl->{$g}, 15));
	}

# Other groups to which users can be assigned
print &ui_table_row(&hlink($text{'tmpl_othergroups'}, "template_othergroups"),
	    &none_def_input("othergroups", $tmpl->{'othergroups'},
		    $text{'tmpl_setgroups'}, 0, 0, undef, [ "othergroups" ]).
	    &ui_textbox("othergroups", $tmpl->{"othergroups"} eq 'none' ?
				undef : $tmpl->{'othergroups'}, 40));

# Prefix/suffix mode
print &ui_table_row(&hlink($text{'tmpl_append'}, "template_append"),
	    &none_def_input("append", $tmpl->{'append_style'}, undef, 1)."\n".
	    &ui_select("append", $tmpl->{'append_style'},
		       [ [ 0, "username.domain" ],
			 [ 2, "domain.username" ],
			 [ 1, "username-domain" ],
			 [ 3, "domain-username" ],
			 [ 4, "username_domain" ],
			 [ 5, "domain_username" ],
			 [ 6, "username\@domain" ] ]));
}

# parse_template_mail(&tmpl)
# Updates email and mailbox related template options from %in
sub parse_template_mail
{
local ($tmpl) = @_;
&require_mail();

# Save mail settings
$tmpl->{'mail_on'} = $in{'mail_mode'} == 0 ? "none" :
		     $in{'mail_mode'} == 1 ? "" : "yes";
$in{'mail'} =~ s/\r//g;
$tmpl->{'mail'} = $in{'mail'};
$tmpl->{'mail_subject'} = $in{'subject'};
$tmpl->{'mail_cc'} = $in{'cc'};
$tmpl->{'mail_bcc'} = $in{'bcc'};

# Save new user aliases
if ($in{'aliases_mode'} == 0) {
	$tmpl->{'user_aliases'} = "none";
	}
elsif ($in{'aliases_mode'} == 1) {
	$tmpl->{'user_aliases'} = "";
	}
else {
	@aliases = &parse_alias(0, "NEWUSER");
	$tmpl->{'user_aliases'} = join("\t", @aliases);
	}

# Save new domain aliases
if ($in{'domaliases_mode'} == 0) {
	$tmpl->{'dom_aliases'} = "none";
	}
elsif ($in{'domaliases_mode'} == 1) {
	$tmpl->{'dom_aliases'} = undef;
	}
else {
	@aliases = ( );
	for($i=0; defined($from = $in{"alias_from_$i"}); $i++) {
		$to = $in{"alias_to_$i"};
		next if (!$from);
		$from =~ /^\S+$/ ||
			&error(&text('tmpl_ealiasfrom', $i+1));
		$to =~ /\S/ || &error(&text('tmpl_ealiasto', $i+1));
		if ($from eq "*" && $in{'bouncealias'}) {
			&error(&text('tmpl_ealiasfrombounce', $i+1));
			}
		push(@aliases, "$from=$to");
		}
	@aliases || &error(&text('tmpl_ealiases'));
	$tmpl->{'dom_aliases'} = join("\t", @aliases);
	}
if ($in{'domaliases_mode'} != 1) {
	$tmpl->{'dom_aliases_bounce'} = $in{'bouncealias'};
	}
if ($supports_aliascopy) {
	$tmpl->{'aliascopy'} = $in{'aliascopy'};
	}

# Save secondary groups
foreach $g ("mailgroup", "ftpgroup", "dbgroup") {
	if ($in{$g.'_mode'} == 2) {
		$in{$g} =~ /^\S+$/ || &error($text{'tmpl_e'.$g});
		}
	$tmpl->{$g} = &parse_none_def($g);
	}
if ($in{'othergroups_mode'} == 2) {
	foreach my $g (split(/\s+/, $in{'othergroups'})) {
		defined(getgrnam($g)) || &error(&text('tmpl_eothergroup', $g));
		}
	}
$tmpl->{'othergroups'} = &parse_none_def('othergroups');
$tmpl->{'append_style'} = &parse_none_def('append');
if ($in{'append_mode'} == 2 && $in{'append'} == 6 &&
    $config{'mail_system'} == 2) {
	# user@domain style is not allowed for Qmail
	&error($text{'tmpl_eappend'});
	}

}

# postsave_template_mail(&template)
# Called after a template is saved
sub postsave_template_mail
{
local ($tmpl) = @_;

if (!$in{'new'}) {
	# Update the secondary group lists for all domains in this template
	local @secdons;
	if ($tmpl->{'id'} == 0) {
		@secdoms = &list_domains();
		}
	else {
		@secdoms = &get_domain_by("template", $tmpl->{'id'});
		}
	foreach my $sd (@secdoms) {
		&update_secondary_groups($sd);
		}
	}
}

# get_generics_hash()
# Returns a hash of all username to outgoing address mappings
sub get_generics_hash
{
&require_mail();
if ($config{'mail_system'} == 1) {
	return map { $_->{'from'}, $_ }
		   &sendmail::list_generics($sendmail_gfile);
	}
elsif ($config{'mail_system'} == 0) {
	local $cans = &postfix::get_maps($canonical_type);
	return map { $_->{'name'}, $_ } @$cans;
	}
else {
	return ( );
	}
}

# create_generic(user, email)
# Adds an entry to the systems outgoing addresses file, if active
sub create_generic
{
local ($user, $email) = @_;
if ($config{'mail_system'} == 1) {
	local $gen = { 'from' => $user, 'to' => $email };
	&lock_file($sendmail_gfile);
	&sendmail::create_generic($gen, $sendmail_gfile,
				  $sendmail_gdbm, $sendmail_gdbmtype);
	&unlock_file($sendmail_gfile);
	}
elsif ($config{'mail_system'} == 0) {
	local $gen = { 'name' => $user,
		       'value' => $email };
	&lock_file($canonical_map_files[0]);
	&create_replace_mapping($canonical_type, $gen);
	&unlock_file($canonical_map_files[0]);
	&postfix::regenerate_canonical_table();
	}
}

# delete_generic(&generic)
# Removes one outgoing addresses table entry
sub delete_generic
{
local ($generic) = @_;
if ($config{'mail_system'} == 1) {
	# For sendmail
	&lock_file($sendmail_gfile);
	&sendmail::delete_generic($generic, $sendmail_gfile,
			$sendmail_gdbm, $sendmail_gdbmtype);
	&unlock_file($sendmail_gfile);
	}
elsif ($config{'mail_system'} == 0) {
	# For postfix
	&lock_file($canonical_map_files[0]);
	&postfix::delete_mapping($canonical_type, $generic);
	&unlock_file($canonical_map_files[0]);
	}
}

# modify_generic(&generic, &old-generic)
# Updates one outgoing addresses table entry
sub modify_generic
{
local ($generic, $oldgeneric) = @_;
if ($config{'mail_system'} == 1) {
	# For sendmail
	&lock_file($sendmail_gfile);
	&sendmail::modify_generic($oldgeneric, $generic, $sendmail_gfile,
			$sendmail_gdbm, $sendmail_gdbmtype);
	&unlock_file($sendmail_gfile);
	}
elsif ($config{'mail_system'} == 0) {
	# For postfix
	&lock_file($canonical_map_files[0]);
	&postfix::modify_mapping($canonical_type, $oldgeneric, $generic);
	&unlock_file($canonical_map_files[0]);
	}
}

# create_domain_forward(&dom, fwdto)
# Adds or updates a virtuser to forward all email sent to this domain
sub create_domain_forward
{
local ($d, $fwdto) = @_;
local $virt = { 'from' => "\@$d->{'dom'}",
		'to' => [ $fwdto ] };
local ($clash) = grep { $_->{'from'} eq $virt->{'from'} } &list_virtusers();
if ($clash) {
	&delete_virtuser($clash);
	}
&create_virtuser($virt);
&sync_alias_virtuals($d);
}

# get_mail_virtusertable()
# Returns the path to a file mapping email addresses to usernames, suitable
# for the mail server in use.
sub get_mail_virtusertable
{
&require_mail();
return $config{'mail_system'} == 1 ? $sendmail_vfile :
       $config{'mail_system'} == 0 ? $virtual_map_files[0] : undef;
}

# get_mail_genericstable()
# Returns the path to a file mapping usernames to email addresses, suitable
# for the mail server in use, if one exists.
sub get_mail_genericstable
{
&require_mail();
return $config{'mail_system'} == 1 ? $sendmail_gfile :
       $config{'mail_system'} == 0 ? $canonical_map_files[0] : undef;
}

# count_domain_aliases([ignore-plugins]
# Return a hash ref from domain ID to a count of aliases.
sub count_domain_aliases
{
local ($ignore) = @_;
local %rv;

# Find local users, so we can skip aliases from user@domain -> user.domain
local %users;
foreach my $u (&list_all_users_quotas(1)) {
	$users{$u->{'user'}} = 1;
	}

local %ignore;
if ($ignore) {
	# Get a list to ignore from each plugin
	foreach my $f (@feature_plugins) {
		foreach my $i (&plugin_call($f, "virtusers_ignore", undef)) {
			$ignore{lc($i)} = 1;
			}
		}
	}

# Map each virtuser to a domain, except for those owned by plugins or for
# which the destination is a user
local %dmap = map { $_->{'dom'}, $_ } &list_domains();
foreach my $v (&list_virtusers()) {
	if (!$ignore{$v->{'from'}} && $v->{'from'} =~ /\@(\S+)$/) {
		local $d = $dmap{$1};
		if ($d) {
			if (@{$v->{'to'}} == 1 && $users{$v->{'to'}->[0]}) {
				# Points to a user .. skip only if the 
				# email addresss is for that user
				local $user = &remove_userdom(
						$v->{'to'}->[0], $d);
				if ($v->{'from'} eq $user."\@".$d->{'dom'}) {
					next;
					}
				}
			$rv{$d->{'id'}}++;
			}
		}
	}

return \%rv;
}

# copy_alias_virtuals(&dom, &sourcedom)
# Copy all virtual/virtuser entries from some source domain into the alias
sub copy_alias_virtuals
{
local ($d, $aliasdom) = @_;
if ($config{'mail_system'} == 1) {
	# Find existing Sendmail virtusers in the alias domain
	foreach my $virt (&sendmail::list_virtusers($sendmail_vfile)) {
		local ($mb, $dname) = split(/\@/, $virt->{'from'});
		if ($dname eq $d->{'dom'}) {
			$already{$mb} = $virt;
			}
		elsif ($dname eq $aliasdom->{'dom'}) {
			$need{$mb} = { 'from' => $mb."\@".$d->{'dom'},
				       'to' => $virt->{'to'} };
			}
		}
	# Add those that are missing, update existing
	local @sargs = ( $sendmail_vfile, $sendmail_vdbm, $sendmail_vdbmtype );
	foreach my $mb (keys %need) {
		local $virt = $already{$mb};
		if ($virt) {
			if ($virt->{'to'} ne $need{$mb}->{'to'}) {
				&sendmail::modify_virtuser($virt, $need{$mb},
							   @sargs);
				}
			}
		else {
			&sendmail::create_virtuser($need{$mb}, @sargs);
			}
		delete($already{$mb});
		}
	# Delete any leftovers
	foreach my $virt (values %already) {
		&sendmail::delete_virtuser($virt, @sargs);
		}
	}
elsif ($config{'mail_system'} == 0) {
	# Find existing Postfix virtuals in the alias domain
	local $alreadyvirts = &postfix::get_maps($virtual_type);
	foreach my $virt (@$alreadyvirts) {
		local ($mb, $dname) = split(/\@/, $virt->{'name'});
		if ($dname eq $d->{'dom'}) {
			$already{$mb} = $virt;
			}
		elsif ($dname eq $aliasdom->{'dom'}) {
			$need{$mb} = { 'name' => $mb."\@".$d->{'dom'},
				       'value' => $virt->{'value'} };
			}
		}
	# Add those that are missing, update existing
	foreach my $mb (keys %need) {
		local $virt = $already{$mb};
		if ($virt) {
			if ($virt->{'value'} ne $need{$mb}->{'value'}) {
				&postfix::modify_mapping($virtual_type,
							 $virt, $need{$mb});
				}
			}
		else {
			&postfix::create_mapping($virtual_type, $need{$mb});
			}
		delete($already{$mb});
		}
	# Delete any leftovers
	foreach my $virt (values %already) {
		&postfix::delete_mapping($virtual_type, $virt);
		}
	}
}

# delete_alias_virtuals(&dom)
# Removes all virtusers for some domain, typically for conversion away from
# alias copy mode.
sub delete_alias_virtuals
{
if ($config{'mail_system'} == 1) {
	# Remove virtusers in Sendmail
	foreach my $virt (&sendmail::list_virtusers($sendmail_vfile)) {
		local ($mb, $dname) = split(/\@/, $virt->{'from'});
		if ($dname eq $d->{'dom'}) {
			&sendmail::delete_virtuser($virt,
			   $sendmail_vfile, $sendmail_vdbm, $sendmail_vdbmtype);
			}
		}
	}
elsif ($config{'mail_system'} == 0) {
	# Remove Postfix virtuals
	local $virts = &postfix::get_maps($virtual_type);
	local @origvirts = @$virts;	# Needed as $virts gets modified!
	foreach my $virt (@origvirts) {
		local ($mb, $dname) = split(/\@/, $virt->{'name'});
		if ($dname eq $d->{'dom'}) {
			&postfix::delete_mapping($virtual_type, $virt);
			}
		}
	}
}

# sync_alias_virtuals(&domain)
# This is called after making any changes to mail aliases, to update the
# copied virtusers in any alias domains that point to it.
sub sync_alias_virtuals
{
local ($d) = @_;
foreach my $ad (&get_domain_by("alias", $d->{'id'})) {
	if ($ad->{'aliascopy'}) {
		&copy_alias_virtuals($ad, $d);
		}
	}
}

$done_feature_script{'mail'} = 1;

1;



syntax highlighted by Code2HTML, v. 0.9.1