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', "$out")); 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 ©_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', "$out")); 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 ©_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'}); ©_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', "$d->{'dom'}") 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() { 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() { 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("$cmd failed :
$out
"); } } &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("$cmd failed:
$out
"); } } } } &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]*"a_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 = "a_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'}") { ©_source_dest("$plainpass_dir/$_[0]->{'id'}", "$_[1]_plainpass"); } # Copy no-spam flags file too if (-r "$nospam_dir/$_[0]->{'id'}") { ©_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', "
$out
")); 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) { ©_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() { s/\r|\n//g; local @user = split(/:/, $_); $_ = ; 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 ©_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 ©_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() { 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', "
$out
")); 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); ©_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 ""; } else { # Offer to backup mail files return sprintf "( %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 .= "
".$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() { # 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() { 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 { "$_->{'host'}" } @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'}."
\n". join("
\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 { "$_->{'host'}" } @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'}."
\n". join("
\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 {'from'}; $ENV{'ALIAS_TO'} = join(",", @{$alias->{'to'}}); $ENV{'ALIAS_CMT'} = $alias->{'cmt'}; local $out =&backquote_logged("($config{'alias_pre_command'}) 2>&1 $out")); } } # 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" ]). "
\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)."
"); &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("$text{'tmpl_bouncealias'}", "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'}) { ©_alias_virtuals($ad, $d); } } } $done_feature_script{'mail'} = 1; 1;