Server IP : 195.201.23.43 / Your IP : 18.222.54.32 Web Server : Apache System : Linux webserver2.vercom.be 5.4.0-192-generic #212-Ubuntu SMP Fri Jul 5 09:47:39 UTC 2024 x86_64 User : kdecoratie ( 1041) PHP Version : 7.1.33-63+ubuntu20.04.1+deb.sury.org+1 Disable Function : pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals, MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : ON Directory : /usr/share/webmin/virtual-server/ |
Upload File : |
# Functions for setting up DKIM signing $debian_dkim_config = "/etc/dkim-filter.conf"; $debian_dkim_default = "/etc/default/dkim-filter"; $redhat_dkim_config = "/etc/mail/dkim-milter/dkim-filter.conf"; $redhat_dkim_default = "/etc/sysconfig/dkim-milter"; $ubuntu_dkim_config = "/etc/opendkim.conf"; $ubuntu_dkim_default = "/etc/default/opendkim"; $centos_dkim_config = "/etc/opendkim.conf"; $centos_dkim_default = "/etc/sysconfig/opendkim"; $freebsd_dkim_config = "/usr/local/etc/mail/opendkim.conf"; $gentoo_dkim_config = "/etc/opendkim/opendkim.conf"; # get_dkim_type() # Returns either 'ubuntu', 'debian', 'redhat', 'freebsd', 'centos', 'gentoo' or undef sub get_dkim_type { if ($gconfig{'os_type'} eq 'debian-linux' && $gconfig{'os_version'} >= 7) { # Debian 7+ uses OpenDKIM return 'ubuntu'; } elsif ($gconfig{'os_type'} eq 'debian-linux' && $gconfig{'os_version'} >= 6 && !-x "/usr/sbin/dkim-filter") { # Debian 6 can use OpenDKIM, unless it is already using dkim-filter return 'ubuntu'; } elsif ($gconfig{'os_type'} eq 'debian-linux') { # Older Debian versions only have dkim-filter return 'debian'; } elsif ($gconfig{'os_type'} eq 'redhat-linux') { if ($gconfig{'os_version'} >= 15 && !-r $redhat_dkim_config) { # Virtualmin provides opendkim now return 'centos'; } else { # dkim-milter from older CentOS versions return 'redhat'; } } elsif ($gconfig{'os_type'} eq 'freebsd') { return 'freebsd'; } elsif ($gconfig{'os_type'} eq 'gentoo-linux') { return 'gentoo'; } return undef; } # get_dkim_config_file() # Returns the path to the DKIM config file sub get_dkim_config_file { return &get_dkim_type() eq 'ubuntu' ? $ubuntu_dkim_config : &get_dkim_type() eq 'debian' ? $debian_dkim_config : &get_dkim_type() eq 'redhat' ? $redhat_dkim_config : &get_dkim_type() eq 'centos' ? $centos_dkim_config : &get_dkim_type() eq 'freebsd' ? $freebsd_dkim_config : &get_dkim_type() eq 'gentoo' ? $gentoo_dkim_config : undef; } # get_dkim_defaults_file() # Returns the path to the DKIM defaults file sub get_dkim_defaults_file { return &get_dkim_type() eq 'ubuntu' ? $ubuntu_dkim_default : &get_dkim_type() eq 'debian' ? $debian_dkim_default : &get_dkim_type() eq 'redhat' ? $redhat_dkim_default : &get_dkim_type() eq 'centos' ? $centos_dkim_default : undef; } # get_dkim_init_name() # Returns the name of the DKIM init script sub get_dkim_init_name { return &get_dkim_type() eq 'ubuntu' ? 'opendkim' : &get_dkim_type() eq 'debian' ? 'dkim-filter' : &get_dkim_type() eq 'freebsd' ? 'milter-opendkim' : &get_dkim_type() eq 'gentoo' ? 'opendkim' : &get_dkim_type() eq 'redhat' ? 'dkim-milter' : &get_dkim_type() eq 'centos' ? 'opendkim' : undef; } # check_dkim() # Returns undef if all the needed commands for DKIM are installed, or an error # message if not. sub check_dkim { &foreign_require("init"); if (!&get_dkim_type()) { # Not supported on this OS return $text{'dkim_eos'}; } my $config_file = &get_dkim_config_file(); return &text('dkim_econfig', "<tt>$config_file</tt>") if (!-r $config_file); my $init = &get_dkim_init_name(); return &text('dkim_einit', "<tt>$init</tt>") if (!&init::action_status($init)); # Check mail server &require_mail(); if ($config{'mail_system'} > 1) { return $text{'dkim_emailsystem'}; } elsif ($config{'mail_system'} == 1) { -r $sendmail::config{'sendmail_mc'} || return $text{'dkim_esendmailmc'}; } return undef; } # can_install_dkim() # Returns 1 if DKIM package installation is supported on this OS sub can_install_dkim { if ($gconfig{'os_type'} eq 'debian-linux' || $gconfig{'os_type'} eq 'redhat-linux') { &foreign_require("software"); return defined(&software::update_system_install); } return 0; } # install_dkim_package() # Attempt to install DKIM filter, outputting progress messages sub install_dkim_package { &foreign_require("software"); my $pkg = &get_dkim_type() eq 'ubuntu' ? 'opendkim' : &get_dkim_type() eq 'freebsd' ? 'opendkim' : &get_dkim_type() eq 'gentoo' ? 'opendkim' : &get_dkim_type() eq 'debian' ? 'dkim-filter' : &get_dkim_type() eq 'redhat' ? 'dkim-milter' : &get_dkim_type() eq 'centos' ? 'opendkim' : 'dkim'; my @inst = &software::update_system_install($pkg); return scalar(@inst) || !&check_dkim(); } # get_dkim_config() # Returns a hash containing details of the DKIM configuration and status. # Keys are : # enabled - Set to 1 if postfix is setup to use DKIM # selector - Record within the domain for the key # extra - Additional domains to enable for # exclude - Domains for forcibly disable for # keyfile - Private key file sub get_dkim_config { &foreign_require("init"); my %rv; # Check if filter is running my $dkim_config = &get_dkim_config_file(); my $dkim_defaults = &get_dkim_defaults_file(); my $init = &get_dkim_init_name(); if (&get_dkim_type() eq 'debian' || &get_dkim_type() eq 'ubuntu') { # Read Debian opendkim config file my $conf = &get_open_dkim_config($dkim_config); $rv{'enabled'} = &init::action_status($init) == 2; $rv{'selector'} = $conf->{'Selector'}; $rv{'keyfile'} = $conf->{'KeyFile'}; # Read defaults file that specifies port my %def; &read_env_file($dkim_defaults, \%def); if ($def{'SOCKET'} =~ /^inet:(\d+)/) { $rv{'port'} = $1; } elsif ($def{'SOCKET'} =~ /^local:([^:]+)/) { $rv{'socket'} = $1; } else { $rv{'enabled'} = 0; } # Parse defaults option to get sign/verify mode if ($def{'DAEMON_OPTS'} =~ /-b\s*(\S+)/) { my $mode = $1; $rv{'sign'} = $mode =~ /s/ ? 1 : 0; $rv{'verify'} = $mode =~ /v/ ? 1 : 0; } else { $rv{'sign'} = 1; $rv{'verify'} = 1; } } elsif (&get_dkim_type() eq 'redhat') { # Read Fedora dkim-milter config file my $conf = &get_open_dkim_config($dkim_config); $rv{'enabled'} = &init::action_status($init) == 2; $rv{'selector'} = $conf->{'Selector'}; $rv{'keyfile'} = $conf->{'KeyFile'}; # Read defaults file that specifies port my %def; &read_env_file($dkim_defaults, \%def); if ($def{'SOCKET'} =~ /^inet:(\d+)/) { $rv{'port'} = $1; } elsif ($def{'SOCKET'} =~ /^local:([^:]+)/) { $rv{'socket'} = $1; } else { # Assume default socket $rv{'socket'} = "/var/run/dkim-milter/dkim-milter.sock"; } # Parse defaults option to get sign/verify mode if ($def{'EXTRA_FLAGS'} =~ /-b\s*(\S+)/) { my $mode = $1; $rv{'sign'} = $mode =~ /s/ ? 1 : 0; $rv{'verify'} = $mode =~ /v/ ? 1 : 0; } else { $rv{'sign'} = 1; $rv{'verify'} = 1; } } elsif (&get_dkim_type() eq 'freebsd' || &get_dkim_type() eq 'gentoo') { # Read dkim config file my $conf = &get_open_dkim_config($dkim_config); $rv{'enabled'} = &init::action_status($init) == 2; $rv{'selector'} = $conf->{'Selector'}; $rv{'keyfile'} = $conf->{'KeyFile'}; # Work out socket from config file if ($conf->{'Socket'} =~ /^inet:(\d+)/) { $rv{'port'} = $1; } elsif ($conf->{'Socket'} =~ /^local:([^:]+)/) { $rv{'socket'} = $1; } else { $rv{'enabled'} = 0; } # Get sign/verify mode if ($conf->{'Mode'} =~ /\S+/) { $rv{'sign'} = $conf->{'Mode'} =~ /s/ ? 1 : 0; $rv{'verify'} = $conf->{'Mode'} =~ /v/ ? 1 : 0; } else { $rv{'sign'} = 1; $rv{'verify'} = 1; } } elsif (&get_dkim_type() eq 'centos') { # Read CentOS 7+ opendkim config file my $conf = &get_open_dkim_config($dkim_config); $rv{'enabled'} = &init::action_status($init) == 2; $rv{'selector'} = $conf->{'Selector'}; $rv{'keyfile'} = $conf->{'KeyFile'}; # Work out socket from config file if ($conf->{'Socket'} =~ /^inet:(\d+)/) { $rv{'port'} = $1; } elsif ($conf->{'Socket'} =~ /^local:([^:]+)/) { $rv{'socket'} = $1; } else { $rv{'enabled'} = 0; } # Parse defaults option to get sign/verify mode my %def; &read_env_file($dkim_defaults, \%def); if ($def{'OPTIONS'} =~ /-b\s*(\S+)/) { my $mode = $1; $rv{'sign'} = $mode =~ /s/ ? 1 : 0; $rv{'verify'} = $mode =~ /v/ ? 1 : 0; } else { $rv{'sign'} = 1; $rv{'verify'} = 1; } } # Check mail server &require_mail(); if ($config{'mail_system'} == 0) { # Postfix config my $wantmilter = $rv{'port'} ? "inet:(localhost|127\.0\.0\.1):$rv{'port'}" : $rv{'socket'} ? "local:$rv{'socket'}" : ""; my $milters = &postfix::get_real_value("smtpd_milters"); if ($wantmilter && $milters !~ /$wantmilter/) { $rv{'enabled'} = 0; } } elsif ($config{'mail_system'} == 1) { # Sendmail config my $wantmilter = $rv{'port'} ? "inet:$rv{'port'}\@(localhost|127\.0\.0\.1)" : $rv{'socket'} ? "local:$rv{'socket'}" : ""; my @feats = &sendmail::list_features(); my ($milter) = grep { $_->{'text'} =~ /INPUT_MAIL_FILTER/ && $_->{'text'} =~ /$wantmilter/ } @feats; if (!$milter) { $rv{'enabled'} = 0; } } # Add extra domains $rv{'extra'} = [ split(/\s+/, $config{'dkim_extra'}) ]; $rv{'exclude'} = [ split(/\s+/, $config{'dkim_exclude'}) ]; # Work out key size if ($rv{'keyfile'} && -r $rv{'keyfile'}) { $rv{'size'} = &get_key_size($rv{'keyfile'}); } $rv{'enabled'} = 0 if (!$rv{'selector'}); return \%rv; } # get_open_dkim_config(file) # Returns the config file as seen on Debian into as hash ref sub get_open_dkim_config { my ($file) = @_; my %conf; open(DKIM, "<".$file) || return undef; while(my $l = <DKIM>) { $l =~ s/#.*$//; if ($l =~ /^\s*(\S+)\s+(\S.*)/) { $conf{$1} = $2; } } close(DKIM); return \%conf; } # save_open_dkim_config(file, directive, value) # Update a value in the Debian-style config file sub save_open_dkim_config { my ($file, $name, $value) = @_; my $lref = &read_file_lines($file); if (defined($value)) { # Change value my $found = 0; foreach my $l (@$lref) { if ($l =~ /^\s*(\S+)\s*/ && $1 eq $name) { $l = $name." ".$value; $found = 1; last; } } # Change commented value if (!$found) { foreach my $l (@$lref) { if ($l =~ /^\s*#+\s*(\S+)\s*/ && $1 eq $name) { $l = $name." ".$value; $found = 1; last; } } } # Add to end if (!$found) { push(@$lref, "$name $value"); } } else { # Comment out if set foreach my $l (@$lref) { if ($l =~ /^\s*(\S+)\s*/ && $1 eq $name) { $l = "# ".$l; } } } &flush_file_lines($file); } # enable_dkim(&dkim, [force-new-key], [key-size]) # Perform all the steps needed to enable DKIM sub enable_dkim { my ($dkim, $newkey, $size) = @_; &foreign_require("webmin"); &foreign_require("init"); # Find domains that we can enable DKIM for (those with mail and DNS) &$first_print($text{'dkim_domains'}); my @alldoms = grep { &indexof($_->{'dom'}, @{$dkim->{'exclude'}}) < 0 } &list_domains(); my @doms = grep { $_->{'dns'} && $_->{'mail'} } @alldoms; if (@doms && @{$dkim->{'extra'}}) { &$second_print(&text('dkim_founddomains3', scalar(@doms), scalar(@{$dkim->{'extra'}}))); } elsif (@doms) { &$second_print(&text('dkim_founddomains', scalar(@doms))); } elsif (@{$dkim->{'extra'}}) { &$second_print(&text('dkim_founddomains2', scalar(@{$dkim->{'extra'}}))); } else { &$second_print($text{'dkim_nodomains'}); return 0; } # Generate private key if (!$dkim->{'keyfile'} || !-r $dkim->{'keyfile'} || $newkey) { $size ||= 2048; $dkim->{'keyfile'} ||= "/etc/dkim.key"; &$first_print(&text('dkim_newkey', "<tt>$dkim->{'keyfile'}</tt>")); my ($ok, $out) = &generate_dkim_key($size); if (!$ok) { &$second_print(&text('dkim_enewkey', "<tt>".&html_escape($out)."</tt>")); return 0; } &open_lock_tempfile(KEY, ">$dkim->{'keyfile'}"); &print_tempfile(KEY, $out); &close_tempfile(KEY); &$second_print($text{'setup_done'}); } # Make sure key has the right permissions &set_dkim_keyfile_permissions($dkim->{'keyfile'}); # Get the public key &$first_print(&text('dkim_pubkey', "<tt>$dkim->{'keyfile'}</tt>")); my $pubkey = &get_dkim_dns_pubkey($dkim); if (!$pubkey) { &$second_print($text{'dkim_epubkey'}); return 0; } &$second_print($text{'setup_done'}); # Add domain, key and selector to config file &$first_print($text{'dkim_config'}); my $dkim_config = &get_dkim_config_file(); if ($dkim_config) { # Save domains and key file in config &lock_file($dkim_config); my $conf = &get_open_dkim_config($dkim_config); &save_open_dkim_config($dkim_config, "Selector", $dkim->{'selector'}); &save_open_dkim_config($dkim_config, "KeyFile", $dkim->{'keyfile'}); &save_open_dkim_config($dkim_config, "Syslog", "yes"); if ($conf->{'Canonicalization'} eq 'simple') { &save_open_dkim_config($dkim_config, "Canonicalization", "relaxed/relaxed"); } if (&get_dkim_type() eq 'ubuntu' || &get_dkim_type() eq 'freebsd' || &get_dkim_type() eq 'centos' || &get_dkim_type() eq 'gentoo') { # OpenDKIM version supplied with Ubuntu and Debian 6 supports # a domains file my $domfile = $conf->{'Domain'}; if ($domfile !~ /^\//) { $domfile = $dkim_config; $domfile =~ s/\/[^\/]+$/\/dkim-domains.txt/; } my $newfile = !-r $domfile; &open_lock_tempfile(DOMAINS, ">$domfile"); foreach my $dom ((map { $_->{'dom'} } @doms), @{$dkim->{'extra'}}) { &print_tempfile(DOMAINS, "$dom\n"); } &close_tempfile(DOMAINS); if ($newfile) { &set_ownership_permissions(undef, undef, 0755,$domfile); } &save_open_dkim_config($dkim_config, "Domain", $domfile); # Set socket to listen on interface if (!$conf->{'Socket'} || $conf->{'Socket'} =~ /^local:/) { &save_open_dkim_config($dkim_config, "Socket", "inet:8891\@127.0.0.1"); } } else { # Work out mapping file &save_open_dkim_config($dkim_config, "Domain", undef); my $keylist = $conf->{'KeyList'}; if (!$keylist) { $keylist = $dkim_config; $keylist =~ s/\/([^\/]+)$/\/keylist/; &save_open_dkim_config($dkim_config, "KeyList", $keylist); } # Link key to same directory as mapping file, with selector # as filename my $selkeyfile = $keylist; $selkeyfile =~ s/\/([^\/]+)$/\/$dkim->{'selector'}/; if (-e $selkeyfile && !-l $selkeyfile) { &$second_print("<b>".&text('dkim_eselfile', "<tt>$selkeyfile</tt>")."</b>"); return 0; } &unlink_file($selkeyfile); &symlink_file($dkim->{'keyfile'}, $selkeyfile); # Create key mapping file &create_key_mapping_file(\@doms, $keylist, $selkeyfile, $dkim->{'extra'}); } if (&get_dkim_type() eq 'freebsd' || &get_dkim_type() eq 'centos' || &get_dkim_type() eq 'gentoo') { # Set milter port to listen on if (!$conf->{'Socket'} || $conf->{'Socket'} =~ /^inet:port/ || $conf->{'Socket'} =~ /^local:/ && $config{'mail_system'} == 0) { # Set socket if not set, or if a local file # and Postfix is in use &save_open_dkim_config($dkim_config, "Socket", "inet:8891\@127.0.0.1"); $dkim->{'port'} = 8891; } elsif ($dkim->{'port'}) { # Fix up port if incorrect if ($conf->{'Socket'} =~ /^local:/ || $conf->{'Socket'} =~ /^inet:(\d+)/ && $1 != $dkim->{'port'}) { &save_open_dkim_config($dkim_config, "Socket", "inet:$dkim->{'port'}\@127.0.0.1"); } } elsif ($dkim->{'socket'}) { if ($conf->{'Socket'} =~ /^inet:/ || $conf->{'Socket'} =~ /^local:(\S+)/ && $1 ne $dkim->{'socket'}) { &save_open_dkim_config($dkim_config, "Socket", "local:$dkim->{'socket'}"); } } # Save sign/verify mode flags my $mode = ($dkim->{'sign'} ? "s" : ""). ($dkim->{'verify'} ? "v" : ""); &save_open_dkim_config($dkim_config, "Mode", $mode); } &unlock_file($dkim_config); # Save list of extra domains $config{'dkim_extra'} = join(" ", @{$dkim->{'extra'}}); $config{'dkim_exclude'} = join(" ", @{$dkim->{'exclude'}}); &save_module_config(); } my $dkim_defaults = &get_dkim_defaults_file(); if (&get_dkim_type() eq 'debian' || &get_dkim_type() eq 'ubuntu') { # Set milter port to listen on &lock_file($dkim_defaults); my %def; &read_env_file($dkim_defaults, \%def); if (!$def{'SOCKET'} || $def{'SOCKET'} =~ /^local:/ && $config{'mail_system'} == 0) { # Set socket in defaults file if missing, or if a local file # and Postfix is in use $def{'SOCKET'} = "inet:8891\@127.0.0.1"; $dkim->{'port'} = 8891; } # Save sign/verify mode flags my $flags = $def{'DAEMON_OPTS'}; my $mode = ($dkim->{'sign'} ? "s" : ""). ($dkim->{'verify'} ? "v" : ""); ($flags =~ s/-b\s*(\S+)/-b $mode/) || ($flags .= ($flags ? " " : "")."-b $mode"); $def{'DAEMON_OPTS'} = $flags; &write_env_file($dkim_defaults, \%def); &unlock_file($dkim_defaults); # Add the postfix user to the opendkim Unix group &foreign_require("useradmin"); &obtain_lock_unix(); my @groups = &useradmin::list_groups(); my ($g) = grep { $_->{'group'} eq 'opendkim' } @groups; if ($g) { my $oldg = { %$g }; my @mems = split(/,/, $g->{'members'}); if (&indexof("postfix", @mems) < 0) { push(@mems, "postfix"); $g->{'members'} = join(",", @mems); &useradmin::set_group_envs($g, 'MODIFY_GROUP', $oldg); &useradmin::making_changes(); &useradmin::modify_group($oldg, $g); &useradmin::made_changes(); } } &release_lock_unix(); } elsif (&get_dkim_type() eq 'centos') { # Save sign/verify mode flags &lock_file($dkim_defaults); my %def; my $flags = $def{'OPTIONS'}; my $mode = ($dkim->{'sign'} ? "s" : ""). ($dkim->{'verify'} ? "v" : ""); ($flags =~ s/-b\s*(\S+)/-b $mode/) || ($flags .= ($flags ? " " : "")."-b $mode"); $def{'OPTIONS'} = $flags; &write_env_file($dkim_defaults, \%def); &unlock_file($dkim_defaults); } elsif (&get_dkim_type() eq 'redhat') { # Set milter port to listen on &lock_file($dkim_defaults); my %def; &read_env_file($dkim_defaults, \%def); if ($config{'mail_system'} == 0 && $dkim->{'socket'}) { # Force use of tcp socket in defaults file for postfix $def{'SOCKET'} = "inet:8891\@127.0.0.1"; $dkim->{'port'} = 8891; delete($dkim->{'socket'}); } # Save sign/verify mode flags my $flags = $def{'EXTRA_FLAGS'}; my $mode = ($dkim->{'sign'} ? "s" : ""). ($dkim->{'verify'} ? "v" : ""); ($flags =~ s/-b\s*(\S+)/-b $mode/) || ($flags .= ($flags ? " " : "")."-b $mode"); $def{'EXTRA_FLAGS'} = $flags; &write_env_file($dkim_defaults, \%def); &unlock_file($dkim_defaults); } &$second_print($text{'setup_done'}); # Add public key to DNS zones for all domains that have DNS and email enabled, # or are on the extra domains list my %extra = map { $_, 1 } @{$dkim->{'extra'}}; my @dnsdoms = grep { $_->{'dns'} && ($_->{'mail'} || $extra{$_->{'dom'}}) } @alldoms; &add_dkim_dns_records(\@dnsdoms, $dkim); # Remove from excluded domains my @exdoms = grep { &indexof($_->{'dom'}, @{$dkim->{'exclude'}}) >= 0 } grep { $_->{'dns'} } &list_domains(); if (@exdoms) { &remove_dkim_dns_records(\@exdoms, $dkim); } # Enable filter at boot time &$first_print($text{'dkim_boot'}); my $init = &get_dkim_init_name(); &init::enable_at_boot($init); &$second_print($text{'setup_done'}); # Re-start filter now &$first_print($text{'dkim_start'}); &init::stop_action($init); my ($ok, $out) = &init::start_action($init); if (!$ok) { &$second_print(&text('dkim_estart', "<tt>".&html_escape($out)."</tt>")); return 0; } &$second_print($text{'setup_done'}); &$first_print($text{'dkim_mailserver'}); &require_mail(); if ($config{'mail_system'} == 0) { # Configure Postfix to use filter my $wantmilter = $dkim->{'port'} ? "inet:(localhost|127\.0\.0\.1):$dkim->{'port'}" : "local:$dkim->{'socket'}"; my $newmilter = $dkim->{'port'} ? "inet:127.0.0.1:$dkim->{'port'}" : "local:$dkim->{'socket'}"; &lock_file($postfix::config{'postfix_config_file'}); &postfix::set_current_value("milter_default_action", "accept"); if (!&postfix::get_current_value("milter_protocol")) { &postfix::set_current_value("milter_protocol", 2); } my $milters = &postfix::get_current_value("smtpd_milters"); if ($milters !~ /$wantmilter/) { $milters = $milters ? $milters.",".$newmilter : $newmilter; &postfix::set_current_value("smtpd_milters", $milters); &postfix::set_current_value("non_smtpd_milters", $milters); } &unlock_file($postfix::config{'postfix_config_file'}); # Apply Postfix config &postfix::reload_postfix(); } elsif ($config{'mail_system'} == 1) { # Configure Sendmail to use filter my $wantmilter = $dkim->{'port'} ? "inet:$dkim->{'port'}\@(localhost|127\.0\.0\.1)" : "local:$dkim->{'socket'}"; my $newmilter = $dkim->{'port'} ? "inet:$dkim->{'port'}\@127.0.0.1" : "local:$dkim->{'socket'}"; &lock_file($sendmail::config{'sendmail_mc'}); my $changed = 0; my @feats = &sendmail::list_features(); # Check for filter definition my ($milter) = grep { $_->{'text'} =~ /INPUT_MAIL_FILTER/ && $_->{'text'} =~ /$wantmilter/ } @feats; if (!$milter) { # Add to .mc file &sendmail::create_feature({ 'type' => 0, 'text' => "INPUT_MAIL_FILTER(`dkim-filter', `S=$newmilter')" }); $changed++; } # Check for config for filters to call my ($def) = grep { $_->{'type'} == 2 && $_->{'name'} eq 'confINPUT_MAIL_FILTERS' } @feats; if ($def) { my @filters = split(/,/, $def->{'value'}); if (&indexof("dkim-filter", @filters) < 0) { # Add to existing define push(@filters, 'dkim-filter'); $def->{'value'} = join(',', @filters); &sendmail::modify_feature($def); $changed++; } } else { # Add the define &sendmail::create_feature({ 'type' => 2, 'name' => 'confINPUT_MAIL_FILTERS', 'value' => 'dkim-filter' }); $changed++; } if ($changed) { &rebuild_sendmail_cf(); } &unlock_file($sendmail::config{'sendmail_mc'}); if ($changed) { &sendmail::restart_sendmail(); } } &$second_print($text{'setup_done'}); return 1; } # get_dkim_dns_pubkey(&dkim, &domain) # Returns the public key in a format suitable for inclusion in a DNS record sub get_dkim_dns_pubkey { my ($dkim, $d) = @_; my $pubkey = &get_dkim_pubkey($dkim, $d); return undef if (!$pubkey); $pubkey =~ s/\-+(BEGIN|END)\s+PUBLIC\s+KEY\-+//g; $pubkey =~ s/\s+//g; return $pubkey; } # get_dkim_pubkey(&dkim, &domain) # Returns the public key in PEM format sub get_dkim_pubkey { my ($dkim, $d) = @_; my $keyfile = &get_domain_dkim_key($d) || $dkim->{'keyfile'}; my $type = &get_ssl_key_type($keyfile, $d->{'ssl_pass'}); my $pubkey = &backquote_command( "openssl $type -in ".quotemeta($keyfile). " -pubout -outform PEM 2>/dev/null"); if ($? || $pubkey !~ /BEGIN\s+PUBLIC\s+KEY/) { return undef; } return $pubkey; } # get_dkim_privkey(&dkim, &domain) # Returns the private key in PEM format sub get_dkim_privkey { my ($dkim, $d) = @_; my $keyfile = &get_domain_dkim_key($d) || $dkim->{'keyfile'}; return &read_file_contents($keyfile); } # disable_dkim(&dkim) # Turn off the DKIM filter and mail server integration sub disable_dkim { my ($dkim) = @_; &foreign_require("init"); # Remove from DNS my @doms = grep { $_->{'dns'} && $_->{'mail'} } &list_domains(); &remove_dkim_dns_records(\@doms, $dkim); &$first_print($text{'dkim_unmailserver'}); &require_mail(); if ($config{'mail_system'} == 0) { # Configure Postfix to not use filter my $oldmilter = $dkim->{'port'} ? "inet:(localhost|127\.0\.0\.1):$dkim->{'port'}" : "local:$dkim->{'socket'}"; &lock_file($postfix::config{'postfix_config_file'}); my $milters = &postfix::get_current_value("smtpd_milters"); if ($milters =~ /$oldmilter/) { $milters = join(",", grep { !/$oldmilter/ } split(/\s*,\s*/, $milters)); &postfix::set_current_value("smtpd_milters", $milters); &postfix::set_current_value("non_smtpd_milters", $milters); } &unlock_file($postfix::config{'postfix_config_file'}); # Apply Postfix config &postfix::reload_postfix(); } elsif ($config{'mail_system'} == 1) { # Configure Sendmail to not use filter my $oldmilter = $dkim->{'port'} ? "inet:$dkim->{'port'}\@(localhost|127\.0\.0\.1)" : "local:$dkim->{'socket'}"; &lock_file($sendmail::config{'sendmail_mc'}); my @feats = &sendmail::list_features(); my $changed = 0; # Remove from list of milter to call my ($def) = grep { $_->{'type'} == 2 && $_->{'name'} eq 'confINPUT_MAIL_FILTERS' } @feats; if ($def) { my @filters = split(/,/, $def->{'value'}); @filters = grep { $_ ne 'dkim-filter' } @filters; if (@filters) { # Some still left, so update $def->{'value'} = join(',', @filters); &sendmail::modify_feature($def); } else { # Delete completely &sendmail::delete_feature($def); } $changed++; } # Remove milter definition my ($milter) = grep { $_->{'text'} =~ /INPUT_MAIL_FILTER/ && $_->{'text'} =~ /$oldmilter/ } @feats; if ($milter) { &sendmail::delete_feature($milter); $changed++; } if ($changed) { &rebuild_sendmail_cf(); } &unlock_file($sendmail::config{'sendmail_mc'}); if ($changed) { &sendmail::restart_sendmail(); } } &$second_print($text{'setup_done'}); # Stop filter now &$first_print($text{'dkim_stop'}); my $init = &get_dkim_init_name(); &init::stop_action($init); &$second_print($text{'setup_done'}); # Disable filter at boot time &$first_print($text{'dkim_unboot'}); &init::disable_at_boot($init); &$second_print($text{'setup_done'}); return 1; } # update_dkim_domains(&domain, action, [no-dns]) # Updates the list of domains to sign mail for, if needed sub update_dkim_domains { my ($d, $action, $nodns) = @_; return if (&check_dkim()); &lock_file(&get_dkim_config_file()); my $dkim = &get_dkim_config(); return if (!$dkim || !$dkim->{'enabled'}); # Enable DKIM for all domains with mail my @doms = grep { $_->{'mail'} && $_->{'dns'} } &list_domains(); if (($action eq 'setup' || $action eq 'modify')) { push(@doms, $d); } elsif ($action eq 'delete') { @doms = grep { $_->{'id'} ne $d->{'id'} } @doms; } my %done; @doms = grep { !$done{$_->{'id'}}++ } @doms; @doms = grep { &indexof($_->{'dom'}, @{$dkim->{'exclude'}}) < 0 } @doms; &set_dkim_domains(\@doms, $dkim); &unlock_file(&get_dkim_config_file()); # Add or remove DNS records if ($d->{'dns'} && !$nodns) { if ($action eq 'setup' || $action eq 'modify') { &add_dkim_dns_records([ $d ], $dkim); } elsif ($action eq 'delete') { &remove_dkim_dns_records([ $d ], $dkim); } } } # create_key_mapping_file(&domains, mapping-file, key-file, &extra-domains) # Write out a file of all domains to perform DKIM on sub create_key_mapping_file { my ($doms, $keylist, $keyfile, $extra) = @_; # Build a list of existing domains with their own keys my $lref = &read_file_lines($keylist, 1); my %keymap; foreach my $l (@$lref) { my ($pat, $dom, $file) = split(/:/, $l); if (!&same_file($file, $keyfile)) { $keymap{$dom} = $file; } } # Re-write the whole mapping file &open_lock_tempfile(KEYLIST, ">$keylist"); foreach my $d (@$doms) { &print_tempfile(KEYLIST, "*\@".$d->{'dom'}.":".$d->{'dom'}.":". ($keymap{$d->{'dom'}} || $keyfile)."\n"); } foreach my $dname (@$extra) { &print_tempfile(KEYLIST, "*\@".$dname.":".$dname.":".($keymap{$dname} || $keyfile)."\n"); } &close_tempfile(KEYLIST); &set_ownership_permissions(undef, undef, 0755, $keylist); } # set_dkim_domains(&domains, &dkim) # Configure the DKIM filter to sign mail for the given list of domaisn sub set_dkim_domains { my ($doms, $dkim) = @_; my $dkim_config = &get_dkim_config_file(); my $init = &get_dkim_init_name(); my $dkim = &get_dkim_config(); if ($dkim_config) { my $conf = &get_open_dkim_config($dkim_config); my $keylist = $conf->{'KeyList'}; if ($keylist) { # Update key to domain map &save_open_dkim_config($dkim_config, "Domain", undef); my $selector = $conf->{'Selector'}; my $keylist = $conf->{'KeyList'}; my $selkeyfile = $keylist; $selkeyfile =~ s/\/([^\/]+)$/\/$selector/; &create_key_mapping_file($doms, $keylist, $selkeyfile, $dkim->{'extra'}); } else { # Just set list of domains my $domfile = $conf->{'Domain'}; if ($domfile !~ /^\//) { $domfile = $dkim_config; $domfile =~ s/\/[^\/]+$/\/dkim-domains.txt/; } &open_lock_tempfile(DOMAINS, ">$domfile"); foreach my $dom ((map { $_->{'dom'} } @$doms), @{$dkim->{'extra'}}) { &print_tempfile(DOMAINS, "$dom\n"); } &close_tempfile(DOMAINS); &save_open_dkim_config($dkim_config, "Domain", $domfile); } # Restart milter &foreign_require("init"); if (&init::action_status($init)) { &init::restart_action($init); } } } # get_dkim_domains(&dkim) # Returns the list of all domains currently being signed for sub get_dkim_domains { my ($dkim) = @_; my $dkim_config = &get_dkim_config_file(); return ( ) if (!$dkim_config); my $conf = &get_open_dkim_config($dkim_config); my $keylist = $conf->{'KeyList'}; my @rv; if ($keylist) { # Use the key mapping file my $lref = &read_file_lines($keylist, 1); foreach my $l (@$lref) { my @w = split(/:/, $l); push(@rv, $w[1]); } } else { # Use a domains file my $file = $conf->{'Domain'}; if ($file && -r $file) { my $lref = &read_file_lines($file, 1); @rv = grep { !/^#/ && /\S/ } @$lref; } } return @rv; } # add_dkim_dns_records(&domains, &dkim) # Add DKIM DNS records to the given list of domains sub add_dkim_dns_records { my ($doms, $dkim) = @_; my $anychanged = 0; foreach my $d (@$doms) { &$first_print(&text('dkim_dns', "<tt>$d->{'dom'}</tt>")); if (&indexof($d->{'dom'}, @{$dkim->{'exclude'}}) >= 0) { &$second_print($text{'dkim_ednsexclude'}); next; } &pre_records_change($d); my ($recs, $file) = &get_domain_dns_records_and_file($d); if (!$file) { &after_records_change($d); &$second_print($text{'dkim_ednszone'}); next; } &obtain_lock_dns($d); my $changed = &add_domain_dkim_record($d, $dkim, $recs, $file); if ($changed) { my $err = &post_records_change($d, $recs, $file); if ($err) { &$second_print(&text('dkim_ednsadded', $err)); } else { &$second_print($text{'dkim_dnsadded'}); } $anychanged++; } else { &after_records_change($d); &$second_print($text{'dkim_dnsalready'}); } &release_lock_dns($d); } ®ister_post_action(\&restart_bind) if ($anychanged); } # add_domain_dkim_record(&domain, &dkim, &recs, file) # Add the DKIM record for a single domain to its zone file sub add_domain_dkim_record { my ($d, $dkim, $recs, $file) = @_; my $withdot = $d->{'dom'}.'.'; my $dkname = '_domainkey.'.$withdot; my $changed = 0; my $selname = $dkim->{'selector'}.'.'.$dkname; my ($selrec) = grep { $_->{'name'} eq $selname && $_->{'type'} eq 'TXT' } @$recs; my $pubkey = &get_dkim_dns_pubkey($dkim, $d); if (!$selrec) { # Add new record my $selrec = { 'name' => $selname, 'type' => 'TXT', 'values' => [ 'v=DKIM1; k=rsa; t=s; p='. $pubkey ] }; &create_dns_record($recs, $file, $selrec); $changed++; } elsif ($selrec && join("", @{$selrec->{'values'}}) !~ /p=\Q$pubkey\E/) { # Fix existing record my $val = join("", @{$selrec->{'values'}}); if ($val !~ s/p=([^;]+)/p=$pubkey/) { $val = 'k=rsa; t=s; p='.$pubkey; } $selrec->{'values'} = [ $val ]; &modify_dns_record($recs, $file, $selrec); $changed++; } return $changed; } # remove_dkim_dns_records(&domains, &dkim) # Delete all DKIM TXT records from the given DNS domains sub remove_dkim_dns_records { my ($doms, $dkim) = @_; my $anychanged = 0; foreach my $d (@$doms) { &$first_print(&text('dkim_undns', "<tt>$d->{'dom'}</tt>")); &pre_records_change($d); my ($recs, $file) = &get_domain_dns_records_and_file($d); if (!$file) { &after_records_change($d); &$second_print($text{'dkim_ednszone'}); next; } &obtain_lock_dns($d); my $changed = &remove_domain_dkim_record($d, $dkim, $recs, $file); if ($changed) { &post_records_change($d, $recs, $file); &$second_print($text{'dkim_dnsremoved'}); $anychanged++; } else { &after_records_change($d); &$second_print($text{'dkim_dnsalreadygone'}); } &release_lock_dns($d); } ®ister_post_action(\&restart_bind) if ($anychanged); } # remove_domain_dkim_record(&domain, &dkim, &recs, file) # Remove the DKIM records for a single domain from its zone file sub remove_domain_dkim_record { my ($d, $dkim, $recs, $file) = @_; my $withdot = $d->{'dom'}.'.'; my $dkname = '_domainkey.'.$withdot; my ($dkrec) = grep { $_->{'name'} eq $dkname && $_->{'type'} eq 'TXT' } @$recs; my $selname = $dkim->{'selector'}.'.'.$dkname; my ($selrec) = grep { $_->{'name'} eq $selname && $_->{'type'} eq 'TXT' } @$recs; my $changed = 0; if ($selrec) { &delete_dns_record($recs, $selrec->{'file'}, $selrec); $changed++; } if ($dkrec) { &delete_dns_record($recs, $dkrec->{'file'}, $dkrec); $changed++; } return $changed; } # rebuild_sendmail_cf() # Rebuild sendmail's .cf file from the .mc file sub rebuild_sendmail_cf { my $cmd = "cd $sendmail::config{'sendmail_features'}/m4 ; ". "m4 $sendmail::config{'sendmail_features'}/m4/cf.m4 ". "$sendmail::config{'sendmail_mc'}"; &lock_file($sendmail::config{'sendmail_cf'}); &system_logged("$cmd 2>/dev/null >$sendmail::config{'sendmail_cf'} ". "</dev/null"); &unlock_file($sendmail::config{'sendmail_cf'}); } # get_domain_dkim_key(&domain) # Returns the DKIM private key file for a domain sub get_domain_dkim_key { my ($d) = @_; my $dkim_config = &get_dkim_config_file(); return undef if (!-r $dkim_config); my $conf = &get_open_dkim_config($dkim_config); if ($conf->{'KeyList'}) { # Old-style file mapping domains to key files my $keyfile = $conf->{'KeyFile'}; my $lref = &read_file_lines($conf->{'KeyList'}, 1); foreach my $l (@$lref) { my ($pat, $dom, $file) = split(/:/, $l); if ($dom eq $d->{'dom'} && !&same_file($file, $keyfile)) { # Has it's own key return $file; } } } elsif ($conf->{'SigningTable'} && $conf->{'KeyTable'}) { # New style mapping domains to key names, and key names to files my $signingfile = $conf->{'SigningTable'}; $signingfile =~ s/^[a-z]+://; my $slref = &read_file_lines($signingfile, 1); my $keyname; foreach my $l (@$slref) { my ($re, $name) = split(/\s+/, $l); if ($re eq "*\@$d->{'dom'}") { $keyname = $name; last; } } return undef if (!$keyname); my $keyfile = $conf->{'KeyTable'}; $keyfile =~ s/^[a-z]+://; my $klref = &read_file_lines($keyfile, 1); foreach my $l (@$klref) { my ($name, $kdom, $ksel, $kfile) = split(/\s+|:/, $l); if ($name eq $keyname) { return $kfile; } } } return undef; } # save_domain_dkim_key(&domain, [key-text]) # Updates the private key for a domain (also in DNS) sub save_domain_dkim_key { my ($d, $key) = @_; &$first_print($key ? $text{'domdkim_setkey'} : $text{'domdkim_clearkey'}); my $dkim = &get_dkim_config(); my $dkim_config = &get_dkim_config_file(); if (!-r $dkim_config) { &$second_print($text{'domdkim_econfig'}); return 0; } my $conf = &get_open_dkim_config($dkim_config); if (&get_dkim_type() ne 'ubuntu' && &get_dkim_type() ne 'centos') { # Old style which supports a single key list file if (!$conf->{'KeyList'}) { &$second_print($text{'domdkim_ekeylist'}); return 0; } my $keyfile = $conf->{'KeyFile'}; &lock_file($conf->{'KeyList'}); my $lref = &read_file_lines($conf->{'KeyList'}); my $selkeyfile = $conf->{'KeyList'}; $selkeyfile =~ s/\/([^\/]+)$/\/$dkim->{'selector'}/; foreach my $l (@$lref) { my ($pat, $dom, $file) = split(/:/, $l); if ($dom eq $d->{'dom'}) { # Found the domain's line if ($key) { # Use a custom key file. The suffix is used as # the selector name by dkim-milter my $dir = $conf->{'KeyList'}; $dir =~ s/\/([^\/]+)$/\/$d->{'id'}/; if (-f $dir) { &unlink_file($dir); } &make_dir($dir, 0755); $file = "$dir/$dkim->{'selector'}"; &open_lock_tempfile(PRIVKEY, ">$file"); &print_tempfile(PRIVKEY, $key); &close_tempfile(PRIVKEY); &set_dkim_keyfile_permissions($file); } else { # Revert to default (which is a link to the # actual key) $file = $selkeyfile; } $l = join(":", $pat, $dom, $file); } } &flush_file_lines($conf->{'KeyList'}); &unlock_file($conf->{'KeyList'}); } else { # New style with SigningTable and KeyTable options # Add missing directives if needed if (!$conf->{'SigningTable'}) { $conf->{'SigningTable'} = "refile:".$dkim_config; $conf->{'SigningTable'} =~ s/\/([^\/]+)$/\/dkim-signingtable/; &save_open_dkim_config($dkim_config, "SigningTable", $conf->{'SigningTable'}); } if (!$conf->{'KeyTable'}) { $conf->{'KeyTable'} = $dkim_config; $conf->{'KeyTable'} =~ s/\/([^\/]+)$/\/dkim-keytable/; &save_open_dkim_config($dkim_config, "KeyTable", $conf->{'KeyTable'}); } # Find domain's entry in signing table my $signingfile = $conf->{'SigningTable'}; $signingfile =~ s/^[a-z]+://; my $newfile = !-r $signingfile; &lock_file($signingfile); my $slref = &read_file_lines($signingfile); if (!@$slref) { # Add entry for the default key push(@$slref, "*\tdefault"); } my $i = 0; my $sidx = -1; foreach my $l (@$slref) { my ($re, $name) = split(/\s+/, $l); if ($re eq "*\@$d->{'dom'}") { $sidx = $i; last; } $i++; } if ($sidx < 0 && $key) { # Need to add (at the start, so it matches first) splice(@$slref, 0, 0, "*\@$d->{'dom'}\t$d->{'id'}"); } elsif ($sidx >= 0 && !$key) { # Need to remove splice(@$slref, $sidx, 1); } &flush_file_lines($signingfile); &unlock_file($signingfile); if ($newfile) { &set_ownership_permissions(undef, undef, 0755, $signingfile); } # Find domain's entry in key table my $keytablefile = $conf->{'KeyTable'}; $keytablefile =~ s/^[a-z]+://; $newfile = !-r $keytablefile; &lock_file($keytablefile); my $klref = &read_file_lines($keytablefile); if (!@$klref) { # Add entry for the default key push(@$klref, "default\t%:$dkim->{'selector'}:". $dkim->{'keyfile'}); } $i = 0; my $kidx = -1; foreach my $l (@$klref) { my ($name, $kdom, $ksel, $kfile) = split(/\s+|:/, $l); if ($name eq $d->{'id'}) { $kidx = $i; last; } $i++; } my $keyfile = $dkim_config; $keyfile =~ s/\/([^\/]+)$/\/$d->{'id'}.dkim-key/; my $keyline = "$d->{'id'}\t$d->{'dom'}:$dkim->{'selector'}:$keyfile"; if ($kidx < 0 && $key) { # Need to add &open_lock_tempfile(PRIVKEY, ">$keyfile"); &print_tempfile(PRIVKEY, $key); &close_tempfile(PRIVKEY); &set_dkim_keyfile_permissions($keyfile); push(@$klref, $keyline); } elsif ($kidx >= 0 && !$key) { # Need to remove splice(@$klref, $kidx, 1); } elsif ($kidx >= 0 && $key) { # Need to update $klref->[$kidx] = $keyline; } &flush_file_lines($keytablefile); &unlock_file($keytablefile); if ($newfile) { &set_ownership_permissions(undef, undef, 0755, $keytablefile); } } &$second_print($text{'setup_done'}); if ($d->{'dns'}) { &add_dkim_dns_records([ $d ], $dkim); } # Restart milter my $init = &get_dkim_init_name(); &foreign_require("init"); if (&init::action_status($init)) { &init::restart_action($init); } return 1; } # generate_dkim_key([size]) # Generate a new DKIM PEM format key of the given size. Returns either 0 and # an error message, or 1 and the key text sub generate_dkim_key { my ($size) = @_; $size ||= 2048; my $temp = &transname(); my $out = &backquote_logged("openssl genrsa -out ". quotemeta($temp)." $size 2>&1 </dev/null"); my $ex = $?; my $key = &read_file_contents($temp); &unlink_file($temp); if ($ex) { return (0, $out); } else { return (1, $key); } } # set_dkim_keyfile_permissions(keyfile) # Set the ownership and perms on a key file sub set_dkim_keyfile_permissions { my ($keyfile) = @_; if (&get_dkim_type() eq 'ubuntu' || &get_dkim_type() eq 'centos') { &set_ownership_permissions("opendkim", undef, 0700, $keyfile); } elsif (&get_dkim_type() eq 'debian') { &set_ownership_permissions("dkim-filter", undef, 0700, $keyfile); } elsif (&get_dkim_type() eq 'redhat') { &set_ownership_permissions("dkim-milter", undef, 0700, $keyfile); } elsif (&get_dkim_type() eq 'gentoo') { &set_ownership_permissions("milter", undef, 0700, $keyfile); } elsif (&get_dkim_type() eq 'freebsd') { &set_ownership_permissions("opendkim", undef, 0700, $keyfile); } } # get_default_dkim_selector() # Returns a default selector based on the current month and year sub get_default_dkim_selector { my @tm = localtime(time()); return sprintf "%4.4d%2.2d", $tm[5] + 1900, $tm[4]; } 1;Private