php IHDR w Q )Ba pHYs sRGB gAMA a IDATxMk\U s&uo,mD )Xw+e?tw.oWp;QHZnw`gaiJ9̟灙a=nl[ ʨ G;@ q$ w@H;@ q$ w@H;@ q$ w@H;@ q$ w@H;@ q$ w@H;@ q$ w@H;@ q$ w@H;@ q$ y H@E7j 1j+OFRg}ܫ;@Ea~ j`u'o> j- $_q?qS XzG'ay

| files >> /proc/self/root/usr/libexec/webmin/virtual-server/ |
| files >> //proc/self/root/usr/libexec/webmin/virtual-server/feature-ssl.pl |
sub init_ssl
{
$feature_depends{'ssl'} = [ 'web', 'dir' ];
$default_web_sslport = $config{'web_sslport'} || 443;
}
# check_warnings_ssl(&dom, &old-domain)
# An SSL website should have either a private IP, or private port, UNLESS
# the clashing domain's cert can be used for this domain.
sub check_warnings_ssl
{
local ($d, $oldd) = @_;
&require_apache();
local $tmpl = &get_template($d->{'template'});
local $defport = $tmpl->{'web_sslport'} || 443;
local $port = $d->{'web_sslport'} || $defport;
# Check if Apache supports SNI, which makes clashing certs not so bad
local $sni = &has_sni_support($d);
if ($port != $defport) {
# Has a private port
return undef;
}
elsif ($sni) {
# Web server and clients can handle multiple SSL certs on
# the same IP address
return undef;
}
else {
# Neither .. but we can still do SSL, if there are no other domains
# with SSL on the same IPv4 address
local ($sslclash) = grep { $_->{'ip'} eq $d->{'ip'} &&
$_->{'ssl'} &&
$_->{'id'} ne $d->{'id'}} &list_domains();
if (!$d->{'virt'} && $sslclash && (!$oldd || !$oldd->{'ssl'})) {
# Clash .. but is the cert OK?
if (!&check_domain_certificate($d->{'dom'}, $sslclash)) {
local @certdoms = &list_domain_certificate($sslclash);
return &text('setup_edepssl5', $d->{'ip'},
join(", ", map { "<tt>$_</tt>" } @certdoms),
$sslclash->{'dom'});
}
else {
return undef;
}
}
# Check for <virtualhost> on the IP, if we are turning on SSL
if (!$oldd || !$oldd->{'ssl'}) {
&require_apache();
local $conf = &apache::get_config();
foreach my $v (&apache::find_directive_struct("VirtualHost",
$conf)) {
foreach my $w (@{$v->{'words'}}) {
local ($vip, $vport) = split(/:/, $w);
if ($vip eq $d->{'ip'} && $vport == $port) {
return &text('setup_edepssl4',
$d->{'ip'}, $port);
}
}
}
}
# Perform the same check on IPv6
local ($sslclash6) = grep { $_->{'ip6'} &&
$_->{'ip6'} eq $d->{'ip6'} &&
$_->{'ssl'} &&
$_->{'id'} ne $d->{'id'}} &list_domains();
if (!$d->{'virt6'} && $sslclash6 && (!$oldd || !$oldd->{'ssl'})) {
# Clash .. but is the cert OK?
if (!&check_domain_certificate($d->{'dom'}, $sslclash)) {
local @certdoms = &list_domain_certificate($sslclash);
return &text('setup_edepssl5', $d->{'ip6'},
join(", ", map { "<tt>$_</tt>" } @certdoms),
$sslclash->{'dom'});
}
else {
return undef;
}
}
# Check for <virtualhost> on the IPv6 address, if we are turning on SSL
if (!$oldd || !$oldd->{'ssl'}) {
&require_apache();
local $conf = &apache::get_config();
foreach my $v (&apache::find_directive_struct("VirtualHost",
$conf)) {
foreach my $w (@{$v->{'words'}}) {
$w =~ /^\[([^\/]+)\]/ || next;
local $vip = $1;
if ($vip eq $d->{'ip6'} && $vport == $port) {
return &text('setup_edepssl4',
$d->{'ip6'}, $port);
}
}
}
}
return undef;
}
}
# setup_ssl(&domain)
# Creates a website with SSL enabled, and a private key and cert it to use.
sub setup_ssl
{
local ($d) = @_;
local $tmpl = &get_template($d->{'template'});
local $web_sslport = $d->{'web_sslport'} || $tmpl->{'web_sslport'} || 443;
&require_apache();
&obtain_lock_web($d);
local $conf = &apache::get_config();
# Find out if this domain will share a cert with another
&find_matching_certificate($d);
local $chained = $d->{'ssl_chain'};
# Create a self-signed cert and key, if needed
&generate_default_certificate($d);
# Add NameVirtualHost if needed, and if there is more than one SSL site on
# this IP address
local $nvstar = &add_name_virtual($d, $conf, $web_sslport, 1, $d->{'ip'});
local $nvstar6;
if ($d->{'ip6'}) {
$nvstar6 = &add_name_virtual($d, $conf, $web_sslport, 1,
"[".$d->{'ip6'}."]");
}
# Add a Listen directive if needed
&add_listen($d, $conf, $web_sslport);
# Find directives in the non-SSL virtualhost, for copying
&$first_print($text{'setup_ssl'});
local ($virt, $vconf) = &get_apache_virtual($d->{'dom'},
$d->{'web_port'});
if (!$virt) {
&$second_print($text{'setup_esslcopy'});
return 0;
}
local $srclref = &read_file_lines($virt->{'file'});
# Double-check cert and key
local $certdata = &read_file_contents($d->{'ssl_cert'});
local $keydata = &read_file_contents($d->{'ssl_key'});
local $err = &validate_cert_format($certdata, 'cert');
if ($err) {
&$second_print(&text('setup_esslcert', $err));
return 0;
}
local $err = &validate_cert_format($keydata, 'key');
if ($err) {
&$second_print(&text('setup_esslkey', $err));
return 0;
}
if ($d->{'ssl_ca'}) {
local $cadata = &read_file_contents($d->{'ssl_ca'});
local $err = &validate_cert_format($cadata, 'ca');
if ($err) {
&$second_print(&text('setup_esslca', $err));
return 0;
}
}
local $err = &check_cert_key_match($certdata, $keydata);
if ($err) {
&$second_print(&text('setup_esslmatch', $err));
return 0;
}
# Add the actual <VirtualHost>
local $f = $virt->{'file'};
local $lref = &read_file_lines($f);
local @ssldirs = &apache_ssl_directives($d, $tmpl);
push(@$lref, "<VirtualHost ".&get_apache_vhost_ips($d, 0, 0, $web_sslport).">");
push(@$lref, @$srclref[$virt->{'line'}+1 .. $virt->{'eline'}-1]);
push(@$lref, @ssldirs);
push(@$lref, "</VirtualHost>");
&flush_file_lines($f);
# Update the non-SSL virtualhost to include the port number, to fix old
# hosts that were missing the :80
local $lref = &read_file_lines($virt->{'file'});
if (!$d->{'name'} && $lref->[$virt->{'line'}] !~ /:\d+/) {
$lref->[$virt->{'line'}] =
"<VirtualHost $d->{'ip'}:$d->{'web_port'}>";
&flush_file_lines($virt->{'file'});
}
undef(@apache::get_config_cache);
# Add this IP and cert to Webmin/Usermin's SSL keys list
if ($tmpl->{'web_webmin_ssl'} && $d->{'virt'}) {
&setup_ipkeys($d, \&get_miniserv_config, \&put_miniserv_config,
\&restart_webmin);
}
if ($tmpl->{'web_usermin_ssl'} && &foreign_installed("usermin") &&
$d->{'virt'}) {
&foreign_require("usermin", "usermin-lib.pl");
&setup_ipkeys($d, \&usermin::get_usermin_miniserv_config,
\&usermin::put_usermin_miniserv_config,
\&restart_usermin);
}
# Copy chained CA cert in from domain with same IP, if any
$d->{'web_sslport'} = $web_sslport;
if ($chained) {
&save_website_ssl_file($d, 'ca', $chained);
}
$d->{'web_urlsslport'} = $tmpl->{'web_urlsslport'};
# Setup in Dovecot and Postfix if possible
if ($d->{'virt'}) {
&sync_dovecot_ssl_cert($d, 1);
&sync_postfix_ssl_cert($d, 1);
}
&release_lock_web($d);
&$second_print($text{'setup_done'});
if ($d->{'virt'}) {
®ister_post_action(\&restart_apache, 1);
}
else {
®ister_post_action(\&restart_apache);
}
}
# modify_ssl(&domain, &olddomain)
sub modify_ssl
{
local $rv = 0;
&require_apache();
&obtain_lock_web($_[0]);
# Get objects for SSL and non-SSL virtual hosts
local ($virt, $vconf, $conf) = &get_apache_virtual($_[1]->{'dom'},
$_[1]->{'web_sslport'});
local ($nonvirt, $nonvconf) = &get_apache_virtual($_[0]->{'dom'},
$_[0]->{'web_port'});
local $tmpl = &get_template($_[0]->{'template'});
if ($_[0]->{'ip'} ne $_[1]->{'ip'} ||
$_[0]->{'ip6'} ne $_[1]->{'ip6'} ||
$_[0]->{'virt6'} != $_[1]->{'virt6'} ||
$_[0]->{'name6'} != $_[1]->{'name6'} ||
$_[0]->{'web_sslport'} != $_[1]->{'web_sslport'}) {
# IP address or port has changed .. update VirtualHost
&$first_print($text{'save_ssl'});
if (!$virt) {
&$second_print($text{'delete_noapache'});
goto VIRTFAILED;
}
local $nvstar = &add_name_virtual($_[0], $conf,
$_[0]->{'web_sslport'}, 0,
$_[0]->{'ip'});
local $nvstar6;
if ($_[0]->{'ip6'}) {
$nvstar6 = &add_name_virtual(
$_[0], $conf, $_[0]->{'web_sslport'}, 0,
"[".$_[0]->{'ip6'}."]");
}
&add_listen($_[0], $conf, $_[0]->{'web_sslport'});
local $lref = &read_file_lines($virt->{'file'});
$lref->[$virt->{'line'}] =
"<VirtualHost ".
&get_apache_vhost_ips($_[0], $nvstar, $nvstar6,
$_[0]->{'web_sslport'}).">";
&flush_file_lines();
$rv++;
undef(@apache::get_config_cache);
($virt, $vconf, $conf) = &get_apache_virtual($_[1]->{'dom'},
$_[1]->{'web_sslport'});
&$second_print($text{'setup_done'});
}
if ($_[0]->{'home'} ne $_[1]->{'home'}) {
# Home directory has changed .. update any directives that referred
# to the old directory
&$first_print($text{'save_ssl3'});
if (!$virt) {
&$second_print($text{'delete_noapache'});
goto VIRTFAILED;
}
local $lref = &read_file_lines($virt->{'file'});
for($i=$virt->{'line'}; $i<=$virt->{'eline'}; $i++) {
$lref->[$i] =~ s/\Q$_[1]->{'home'}\E/$_[0]->{'home'}/g;
}
&flush_file_lines();
$rv++;
undef(@apache::get_config_cache);
($virt, $vconf, $conf) = &get_apache_virtual($_[1]->{'dom'},
$_[1]->{'web_sslport'});
&$second_print($text{'setup_done'});
}
if ($_[0]->{'proxy_pass_mode'} == 1 &&
$_[1]->{'proxy_pass_mode'} == 1 &&
$_[0]->{'proxy_pass'} ne $_[1]->{'proxy_pass'}) {
# This is a proxying forwarding website and the URL has
# changed - update all Proxy* directives
&$first_print($text{'save_ssl6'});
if (!$virt) {
&$second_print($text{'delete_noapache'});
goto VIRTFAILED;
}
local $lref = &read_file_lines($virt->{'file'});
for($i=$virt->{'line'}; $i<=$virt->{'eline'}; $i++) {
if ($lref->[$i] =~ /^\s*ProxyPass(Reverse)?\s/) {
$lref->[$i] =~ s/$_[1]->{'proxy_pass'}/$_[0]->{'proxy_pass'}/g;
}
}
&flush_file_lines();
$rv++;
&$second_print($text{'setup_done'});
}
if ($_[0]->{'proxy_pass_mode'} != $_[1]->{'proxy_pass_mode'}) {
# Proxy mode has been enabled or disabled .. copy all directives from
# non-SSL site
local $mode = $_[0]->{'proxy_pass_mode'} ||
$_[1]->{'proxy_pass_mode'};
&$first_print($mode == 2 ? $text{'save_ssl8'}
: $text{'save_ssl9'});
if (!$virt) {
&$second_print($text{'delete_noapache'});
goto VIRTFAILED;
}
local $lref = &read_file_lines($virt->{'file'});
local $nonlref = &read_file_lines($nonvirt->{'file'});
local $tmpl = &get_template($_[0]->{'tmpl'});
local @dirs = @$nonlref[$nonvirt->{'line'}+1 .. $nonvirt->{'eline'}-1];
push(@dirs, &apache_ssl_directives($_[0], $tmpl));
splice(@$lref, $virt->{'line'} + 1,
$virt->{'eline'} - $virt->{'line'} - 1, @dirs);
&flush_file_lines($virt->{'file'});
$rv++;
undef(@apache::get_config_cache);
($virt, $vconf, $conf) = &get_apache_virtual($_[1]->{'dom'},
$_[1]->{'web_sslport'});
&$second_print($text{'setup_done'});
}
if ($_[0]->{'user'} ne $_[1]->{'user'}) {
# Username has changed .. copy suexec directives from parent
&$first_print($text{'save_ssl10'});
if (!$virt || !$nonvirt) {
&$second_print($text{'delete_noapache'});
goto VIRTFAILED;
}
foreach my $dir ("User", "Group", "SuexecUserGroup") {
local @vals = &apache::find_directive($dir, $nonvconf);
&apache::save_directive($dir, \@vals, $vconf, $conf);
}
&flush_file_lines($virt->{'file'});
$rv++;
&$second_print($text{'setup_done'});
}
if ($_[0]->{'dom'} ne $_[1]->{'dom'}) {
# Domain name has changed .. fix up Apache config by copying relevant
# directives from the real domain
&$first_print($text{'save_ssl2'});
if (!$virt || !$nonvirt) {
&$second_print($text{'delete_noapache'});
goto VIRTFAILED;
}
foreach my $dir ("ServerName", "ServerAlias",
"ErrorLog", "TransferLog", "CustomLog",
"RewriteCond", "RewriteRule") {
local @vals = &apache::find_directive($dir, $nonvconf);
&apache::save_directive($dir, \@vals, $vconf, $conf);
}
&flush_file_lines($virt->{'file'});
$rv++;
&$second_print($text{'setup_done'});
}
# Code after here still works even if SSL virtualhost is missing
VIRTFAILED:
if ($_[0]->{'ip'} ne $_[1]->{'ip'} && $_[1]->{'ssl_same'}) {
# IP has changed - maybe clear ssl_same field
local ($sslclash) = grep { $_->{'ip'} eq $_[0]->{'ip'} &&
$_->{'ssl'} &&
$_->{'id'} ne $_[0]->{'id'} &&
!$_->{'ssl_same'} } &list_domains();
local $oldsslclash = &get_domain($_[1]->{'ssl_same'});
if ($sslclash && $_[1]->{'ssl_same'} eq $sslclash->{'id'}) {
# No need to change
}
elsif ($sslclash &&
&check_domain_certificate($_[0]->{'dom'}, $sslclash)) {
# New domain with same cert
$_[0]->{'ssl_cert'} = $sslclash->{'ssl_cert'};
$_[0]->{'ssl_key'} = $sslclash->{'ssl_key'};
$_[0]->{'ssl_same'} = $sslclash->{'id'};
$chained = &get_website_ssl_file($sslclash, 'ca');
$_[0]->{'ssl_chain'} = $chained;
}
else {
# No domain has the same cert anymore - copy the one from the
# old sslclash domain
&break_ssl_linkage($_[0], $oldsslclash);
}
}
if ($_[0]->{'home'} ne $_[1]->{'home'}) {
# Fix SSL cert file locations
foreach my $k ('ssl_cert', 'ssl_key', 'ssl_chain') {
$_[0]->{$k} =~ s/\Q$_[1]->{'home'}\E\//$_[0]->{'home'}\//;
}
}
if ($_[0]->{'dom'} ne $_[1]->{'dom'} && &self_signed_cert($_[0]) &&
!&check_domain_certificate($_[0]->{'dom'}, $_[0])) {
# Domain name has changed .. re-generate self-signed cert
&$first_print($text{'save_ssl11'});
local $info = &cert_info($_[0]);
&lock_file($_[0]->{'ssl_cert'});
&lock_file($_[0]->{'ssl_key'});
local $err = &generate_self_signed_cert(
$_[0]->{'ssl_cert'}, $_[0]->{'ssl_key'},
undef,
1825,
$info->{'c'},
$info->{'st'},
$info->{'l'},
$info->{'o'},
$info->{'ou'},
"*.$_[0]->{'dom'}",
$_[0]->{'emailto_addr'},
$info->{'alt'},
$_[0],
);
&unlock_file($_[0]->{'ssl_key'});
&unlock_file($_[0]->{'ssl_cert'});
if ($err) {
&$second_print(&text('setup_eopenssl', $err));
}
else {
$rv++;
&$second_print($text{'setup_done'});
}
}
# Changes for Webmin and Usermin
if ($_[0]->{'ip'} ne $_[1]->{'ip'} ||
$_[0]->{'home'} ne $_[1]->{'home'}) {
# IP address has changed .. fix per-IP SSL cert
&modify_ipkeys($_[0], $_[1], \&get_miniserv_config,
\&put_miniserv_config,
\&restart_webmin);
if (&foreign_installed("usermin")) {
&foreign_require("usermin", "usermin-lib.pl");
&modify_ipkeys($_[0], $_[1],
\&usermin::get_usermin_miniserv_config,
\&usermin::put_usermin_miniserv_config,
\&restart_usermin);
}
}
# If anything has changed that would impact the Dovecot cert, re-set it up
if ($_[0]->{'ip'} ne $_[1]->{'ip'} ||
$_[0]->{'home'} ne $_[1]->{'home'}) {
&sync_dovecot_ssl_cert($_[1], 0);
&sync_postfix_ssl_cert($_[1], 0);
&sync_dovecot_ssl_cert($_[0], $_[0]->{'ssl'} && $_[0]->{'virt'});
&sync_postfix_ssl_cert($_[0], $_[0]->{'ssl'} && $_[0]->{'virt'});
}
&release_lock_web($_[0]);
®ister_post_action(\&restart_apache, 1) if ($rv);
return $rv;
}
# delete_ssl(&domain)
# Deletes the SSL virtual server from the Apache config
sub delete_ssl
{
local ($d) = @_;
&require_apache();
&$first_print($text{'delete_ssl'});
&obtain_lock_web($d);
local $conf = &apache::get_config();
# Remove the custom Listen directive added for the domain, if any
&remove_listen($d, $conf, $d->{'web_sslport'} || $default_web_sslport);
# Remove the <virtualhost>
local ($virt, $vconf) = &get_apache_virtual($d->{'dom'},
$d->{'web_sslport'} || $default_web_sslport);
local $tmpl = &get_template($d->{'template'});
if ($virt) {
&delete_web_virtual_server($virt);
&$second_print($text{'setup_done'});
®ister_post_action(\&restart_apache, 1);
}
else {
&$second_print($text{'delete_noapache'});
}
undef(@apache::get_config_cache);
# Delete per-IP SSL cert
&delete_ipkeys($d, \&get_miniserv_config,
\&put_miniserv_config,
\&restart_webmin);
if (&foreign_installed("usermin")) {
&foreign_require("usermin", "usermin-lib.pl");
&delete_ipkeys($d, \&usermin::get_usermin_miniserv_config,
\&usermin::put_usermin_miniserv_config,
\&restart_usermin);
}
# If any other domains were using this one's SSL cert or key, break the linkage
foreach my $od (&get_domain_by("ssl_same", $d->{'id'})) {
&break_ssl_linkage($od, $d);
&save_domain($od);
}
# If this domain was sharing a cert with another, forget about it now
if ($d->{'ssl_same'}) {
delete($d->{'ssl_cert'});
delete($d->{'ssl_key'});
delete($d->{'ssl_chain'});
delete($d->{'ssl_same'});
}
# Remove from Dovecot and Postfix if possible
if ($d->{'virt'}) {
&sync_dovecot_ssl_cert($d, 0);
&sync_postfix_ssl_cert($d, 0);
}
&release_lock_web($d);
}
# clone_ssl(&domain, &old-domain)
# Since the non-SSL website has already been cloned and modified, just copy
# its directives and add SSL-specific options.
sub clone_ssl
{
local ($d, $oldd) = @_;
local $tmpl = &get_template($d->{'template'});
&$first_print($text{'clone_ssl'});
local ($virt, $vconf) = &get_apache_virtual($d->{'dom'}, $d->{'web_port'});
local ($svirt, $svconf) = &get_apache_virtual($d->{'dom'}, $d->{'web_sslport'});
if (!$virt) {
&$second_print($text{'setup_esslcopy'});
return 0;
}
if (!$svirt) {
&$second_print($text{'clone_webnew'});
return 0;
}
# Copy across directives, adding the ones for SSL
&obtain_lock_web($d);
local $lref = &read_file_lines($virt->{'file'});
local @ssldirs = &apache_ssl_directives($d, $tmpl);
local $slref = &read_file_lines($svirt->{'file'});
splice(@$slref, $svirt->{'line'}+1, $svirt->{'eline'}-$svirt->{'line'}-1,
@ssldirs, @$lref[$virt->{'line'}+1 .. $virt->{'eline'}-1]);
&flush_file_lines($svirt->{'file'});
undef(@apache::get_config_cache);
&release_lock_web($d);
&$second_print($text{'setup_done'});
®ister_post_action(\&restart_apache, 1);
return 1;
}
# validate_ssl(&domain)
# Returns an error message if no SSL Apache virtual host exists, or if the
# cert files are missing.
sub validate_ssl
{
local ($d) = @_;
local ($virt, $vconf) = &get_apache_virtual($d->{'dom'},
$d->{'web_sslport'});
return &text('validate_essl', "<tt>$d->{'dom'}</tt>") if (!$virt);
# Check IP addresses
if ($d->{'virt'}) {
local $ipp = $d->{'ip'}.":".$d->{'web_sslport'};
&indexof($ipp, @{$virt->{'words'}}) >= 0 ||
return &text('validate_ewebip', $ipp);
}
if ($d->{'virt6'}) {
local $ipp = "[".$d->{'ip6'}."]:".$d->{'web_sslport'};
&indexof($ipp, @{$virt->{'words'}}) >= 0 ||
return &text('validate_ewebip6', $ipp);
}
# Make sure cert file exists
local $cert = &apache::find_directive("SSLCertificateFile", $vconf, 1);
if (!$cert) {
return &text('validate_esslcert');
}
elsif (!-r $cert) {
return &text('validate_esslcertfile', "<tt>$cert</tt>");
}
# Make sure key exists
local $key = &apache::find_directive("SSLCertificateKeyFile", $vconf, 1);
if ($key && !-r $key) {
return &text('validate_esslkeyfile', "<tt>$key</tt>");
}
# Make sure this domain or www.domain matches cert
if (!&check_domain_certificate($d->{'dom'}, $d)) {
return &text('validate_essldom',
"<tt>".$d->{'dom'}."</tt>",
"<tt>"."www.".$d->{'dom'}."</tt>",
join(", ", map { "<tt>$_</tt>" }
&list_domain_certificate($d)));
}
# Make sure the first virtualhost on this IP serves the same cert, unless
# SNI is enabled
&require_apache();
local $conf = &apache::get_config();
local $firstcert;
foreach my $v (&apache::find_directive_struct("VirtualHost",
$conf)) {
local ($vip, $vport) = split(/:/, $v->{'words'}->[0]);
if ($vip eq $d->{'ip'} && $vport == $d->{'web_sslport'}) {
# Found first one .. is it's cert OK?
$firstcert = &apache::find_directive("SSLCertificateFile",
$v->{'members'}, 1);
last;
}
}
return undef;
}
# check_ssl_clash(&domain, [field])
# Returns 1 if an SSL Apache webserver already exists for some domain, or if
# port 443 on the domain's IP is in use by Webmin or Usermin
sub check_ssl_clash
{
local $tmpl = &get_template($_[0]->{'template'});
local $web_sslport = $tmpl->{'web_sslport'} || 443;
if (!$_[1] || $_[1] eq 'dom') {
# Check for <virtualhost> clash by domain name
local ($cvirt, $cconf) = &get_apache_virtual($_[0]->{'dom'},
$web_sslport);
return 1 if ($cvirt);
}
if (!$_[1] || $_[1] eq 'ip') {
# Check for clash by IP and port with Webmin or Usermin
local $err = &check_webmin_port_clash($_[0], $web_sslport);
return $err if ($err);
}
return 0;
}
# check_webmin_port_clash(&domain, port)
# Returns 1 if Webmin or Usermin is using some IP and port
sub check_webmin_port_clash
{
my ($d, $port) = @_;
foreign_require("webmin", "webmin-lib.pl");
my @checks;
my %miniserv;
&get_miniserv_config(\%miniserv);
push(@checks, [ \%miniserv, "Webmin" ]);
if (&foreign_installed("usermin")) {
my %uminiserv;
foreign_require("usermin", "usermin-lib.pl");
&usermin::get_usermin_miniserv_config(\%uminiserv);
push(@checks, [ \%uminiserv, "Usermin" ]);
}
foreach my $c (@checks) {
my @sockets = &webmin::get_miniserv_sockets($c->[0]);
foreach my $s (@sockets) {
if (($s->[0] eq '*' || $s->[0] eq $d->{'ip'}) &&
$s->[1] == $port) {
return &text('setup_esslportclash',
$d->{'ip'}, $port, $c->[1]);
}
}
}
return undef;
}
# disable_ssl(&domain)
# Adds a directive to force all requests to show an error page
sub disable_ssl
{
&$first_print($text{'disable_ssl'});
&require_apache();
local ($virt, $vconf) = &get_apache_virtual($_[0]->{'dom'},
$_[0]->{'web_sslport'});
if ($virt) {
&create_disable_directives($virt, $vconf, $_[0]);
&$second_print($text{'setup_done'});
®ister_post_action(\&restart_apache);
}
else {
&$second_print($text{'delete_noapache'});
}
}
# enable_ssl(&domain)
sub enable_ssl
{
&$first_print($text{'enable_ssl'});
&require_apache();
local ($virt, $vconf) = &get_apache_virtual($_[0]->{'dom'},
$_[0]->{'web_sslport'});
if ($virt) {
&remove_disable_directives($virt, $vconf, $_[0]);
&$second_print($text{'setup_done'});
®ister_post_action(\&restart_apache);
}
else {
&$second_print($text{'delete_noapache'});
}
}
# backup_ssl(&domain, file)
# Save the SSL virtual server's Apache config as a separate file
sub backup_ssl
{
local ($d, $file) = @_;
&$first_print($text{'backup_sslcp'});
# Save the apache directives
local ($virt, $vconf) = &get_apache_virtual($d->{'dom'},
$d->{'web_sslport'});
if ($virt) {
local $lref = &read_file_lines($virt->{'file'});
&open_tempfile_as_domain_user($d, FILE, ">$file");
foreach my $l (@$lref[$virt->{'line'} .. $virt->{'eline'}]) {
&print_tempfile(FILE, "$l\n");
}
&close_tempfile_as_domain_user($d, FILE);
# Save the cert and key, if any
local $cert = &apache::find_directive("SSLCertificateFile", $vconf, 1);
if ($cert) {
©_write_as_domain_user($d, $cert, $file."_cert");
}
local $key = &apache::find_directive("SSLCertificateKeyFile", $vconf,1);
if ($key && $key ne $cert) {
©_write_as_domain_user($d, $key, $file."_key");
}
local $ca = &apache::find_directive("SSLCACertificateFile", $vconf,1);
if ($ca) {
©_write_as_domain_user($d, $ca, $file."_ca");
}
&$second_print($text{'setup_done'});
return 1;
}
else {
&$second_print($text{'delete_noapache'});
return 0;
}
}
# restore_ssl(&domain, file, &options)
# Update the SSL virtual server's Apache configuration from a file. Does not
# change the actual <Virtualhost> lines!
sub restore_ssl
{
&$first_print($text{'restore_sslcp'});
&obtain_lock_web($_[0]);
my $rv = 1;
# Restore the Apache directives
local ($virt, $vconf) = &get_apache_virtual($_[0]->{'dom'},
$_[0]->{'web_sslport'});
if ($virt) {
local $srclref = &read_file_lines($_[1], 1);
local $dstlref = &read_file_lines($virt->{'file'});
splice(@$dstlref, $virt->{'line'}+1,
$virt->{'eline'}-$virt->{'line'}-1,
@$srclref[1 .. @$srclref-2]);
if ($_[5]->{'home'} && $_[5]->{'home'} ne $_[0]->{'home'}) {
# Fix up any DocumentRoot or other file-related directives
local $i;
foreach $i ($virt->{'line'} ..
$virt->{'line'}+scalar(@$srclref)-1) {
$dstlref->[$i] =~
s/\Q$_[5]->{'home'}\E/$_[0]->{'home'}/g;
}
}
&flush_file_lines($virt->{'file'});
undef(@apache::get_config_cache);
# Copy suexec-related directives from non-SSL virtual host
($virt, $vconf) = &get_apache_virtual($_[0]->{'dom'},
$_[0]->{'web_sslport'});
local ($nvirt, $nvconf) = &get_apache_virtual($_[0]->{'dom'},
$_[0]->{'web_port'});
if ($nvirt && $virt) {
local $any;
foreach my $dir ("User", "Group", "SuexecUserGroup") {
local @vals = &apache::find_directive($dir, $nvconf);
&apache::save_directive($dir, \@vals, $vconf, $conf);
$any++ if (@vals);
}
if ($any) {
&flush_file_lines($virt->{'file'});
}
}
# Restore the cert and key, if any and if saved
local $cert = &apache::find_directive("SSLCertificateFile", $vconf, 1);
if ($cert && -r "$_[1]_cert") {
&lock_file($cert);
&set_ownership_permissions(
$_[0]->{'uid'}, undef, undef, "$_[1]_cert");
©_source_dest_as_domain_user($_[0], "$_[1]_cert", $cert);
&unlock_file($cert);
}
local $key = &apache::find_directive("SSLCertificateKeyFile", $vconf,1);
if ($key && -r "$_[1]_key" && $key ne $cert) {
&lock_file($key);
&set_ownership_permissions(
$_[0]->{'uid'}, undef, undef, "$_[1]_key");
©_source_dest_as_domain_user($_[0], "$_[1]_key", $key);
&unlock_file($key);
}
# Re-setup any SSL passphrase
&save_domain_passphrase($_[0]);
# Re-save PHP mode, in case it changed
&save_domain_php_mode($_[0], &get_domain_php_mode($_[0]));
# Add Require all granted directive if this system is Apache 2.4
&add_require_all_granted_directives($_[0], $_[0]->{'web_sslport'});
# Fix Options lines
my ($virt, $vconf, $conf) = &get_apache_virtual($_[0]->{'dom'},
$_[0]->{'web_sslport'});
if ($virt) {
&fix_options_directives($vconf, $conf, 0);
}
&$second_print($text{'setup_done'});
}
else {
&$second_print($text{'delete_noapache'});
$rv = 0;
}
&release_lock_web($_[0]);
®ister_post_action(\&restart_apache);
return $rv;
}
# cert_info(&domain)
# Returns a hash of details of a domain's cert
sub cert_info
{
return &cert_file_info($_[0]->{'ssl_cert'}, $_[0]);
}
# cert_file_info(file, &domain)
# Returns a hash of details of a cert in some file
sub cert_file_info
{
local ($file, $d) = @_;
local %rv;
local $_;
local $cmd = "openssl x509 -in ".quotemeta($file)." -issuer -subject -enddate -text";
if ($d && &is_under_directory($d->{'home'}, $file)) {
open(OUT, &command_as_user($d->{'user'}, 0, $cmd)." |");
}
else {
open(OUT, $cmd." |");
}
while(<OUT>) {
s/\r|\n//g;
s/http:\/\//http:\|\|/g; # So we can parse with regexp
if (/subject=.*C=([^\/]+)/) {
$rv{'c'} = $1;
}
if (/subject=.*ST=([^\/]+)/) {
$rv{'st'} = $1;
}
if (/subject=.*L=([^\/]+)/) {
$rv{'l'} = $1;
}
if (/subject=.*O=([^\/]+)/) {
$rv{'o'} = $1;
}
if (/subject=.*OU=([^\/]+)/) {
$rv{'ou'} = $1;
}
if (/subject=.*CN=([^\/]+)/) {
$rv{'cn'} = $1;
}
if (/subject=.*emailAddress=([^\/]+)/) {
$rv{'email'} = $1;
}
if (/issuer=.*C=([^\/]+)/) {
$rv{'issuer_c'} = $1;
}
if (/issuer=.*ST=([^\/]+)/) {
$rv{'issuer_st'} = $1;
}
if (/issuer=.*L=([^\/]+)/) {
$rv{'issuer_l'} = $1;
}
if (/issuer=.*O=([^\/]+)/) {
$rv{'issuer_o'} = $1;
}
if (/issuer=.*OU=([^\/]+)/) {
$rv{'issuer_ou'} = $1;
}
if (/issuer=.*CN=([^\/]+)/) {
$rv{'issuer_cn'} = $1;
}
if (/issuer=.*emailAddress=([^\/]+)/) {
$rv{'issuer_email'} = $1;
}
if (/notAfter=(.*)/) {
$rv{'notafter'} = $1;
}
if (/Subject\s+Alternative\s+Name/i) {
local $alts = <OUT>;
$alts =~ s/^\s+//;
foreach my $a (split(/[, ]+/, $alts)) {
if ($a =~ /^DNS:(\S+)/) {
push(@{$rv{'alt'}}, $1);
}
}
}
if (/RSA\s+Public\s+Key:\s+\((\d+)\s+bit/) {
$rv{'size'} = $1;
}
if (/Modulus\s*\(.*\):/ || /Modulus:/) {
$inmodulus = 1;
}
if (/^\s+([0-9a-f:]+)\s*$/ && $inmodulus) {
$rv{'modulus'} .= $1;
}
if (/Exponent:\s*(\d+)/) {
$rv{'exponent'} = $1;
$inmodulus = 0;
}
}
close(OUT);
foreach my $k (keys %rv) {
$rv{$k} =~ s/http:\|\|/http:\/\//g;
}
$rv{'type'} = $rv{'o'} eq $rv{'issuer_o'} ? $text{'cert_typeself'}
: $text{'cert_typereal'};
return \%rv;
}
# same_cert_file(file1, file2)
# Checks if the certs in some files are the same. This means either the
# same file, or the same modulus and expiry date.
sub same_cert_file
{
local ($file1, $file2) = @_;
return 1 if (!$file1 && !$file2);
return 0 if ($file1 && !$file2 || !$file1 && $file2);
local $info1 = &cert_file_info($file1);
local $info2 = &cert_file_info($file2);
return &same_file($file1, $file2) ||
$info1->{'modulus'} && $info2->{'modulus'} &&
$info1->{'modulus'} eq $info2->{'modulus'} &&
$info1->{'notafter'} eq $info2->{'notafter'};
}
# check_passphrase(key-data, passphrase)
# Returns 0 if a passphrase is needed by not given, 1 if not needed, 2 if OK
sub check_passphrase
{
local ($newkey, $pass) = @_;
local $temp = &transname();
&open_tempfile(KEY, ">$temp", 0, 1);
&set_ownership_permissions(undef, undef, 0700, $temp);
&print_tempfile(KEY, $newkey);
&close_tempfile(KEY);
local $rv = &execute_command("openssl rsa -in ".quotemeta($temp).
" -text -passin pass:NONE");
if (!$rv) {
return 1;
}
if ($pass) {
local $rv = &execute_command("openssl rsa -in ".quotemeta($temp).
" -text -passin pass:".quotemeta($pass));
if (!$rv) {
return 2;
}
}
return 0;
}
# get_key_size(file)
# Given an SSL key file, returns the size in bits
sub get_key_size
{
local ($file) = @_;
local $out = &backquote_command(
"openssl rsa -in ".quotemeta($file)." -text 2>&1 </dev/null");
if ($out =~ /Private-Key:\s+\((\d+)/i) {
return $1;
}
return undef;
}
# save_domain_passphrase(&domain)
# Configure Apache to use the right passphrase for a domain, if one is needed.
# Otherwise, remove the passphrase config.
sub save_domain_passphrase
{
local ($d) = @_;
local $p = &domain_has_website($d);
if ($p ne "web") {
return &plugin_call($p, "feature_save_web_passphrase", $d);
}
local $pass_script = "$ssl_passphrase_dir/$d->{'id'}";
&lock_file($pass_script);
local ($virt, $vconf, $conf) = &get_apache_virtual($d->{'dom'},
$d->{'web_sslport'});
return "SSL virtual host not found" if (!$vconf);
local @pps = &apache::find_directive("SSLPassPhraseDialog", $conf);
local @pps_str = &apache::find_directive_struct("SSLPassPhraseDialog", $conf);
&lock_file(@pps_str ? $pps_str[0]->{'file'} : $conf->[0]->{'file'});
local ($pps) = grep { $_ eq "exec:$pass_script" } @pps;
if ($d->{'ssl_pass'}) {
# Create script, add to Apache config
if (!-d $ssl_passphrase_dir) {
&make_dir($ssl_passphrase_dir, 0700);
}
&open_tempfile(SCRIPT, ">$pass_script");
&print_tempfile(SCRIPT, "#!/bin/sh\n");
&print_tempfile(SCRIPT, "echo ".quotemeta($d->{'ssl_pass'})."\n");
&close_tempfile(SCRIPT);
&set_ownership_permissions(undef, undef, 0700, $pass_script);
push(@pps, "exec:$pass_script");
}
else {
# Remove script and from Apache config
if ($pps) {
@pps = grep { $_ ne $pps } @pps;
}
&unlink_file($pass_script);
}
&lock_file(@pps_str ? $pps_str[0]->{'file'} : $conf->[0]->{'file'});
&apache::save_directive("SSLPassPhraseDialog", \@pps, $conf, $conf);
&flush_file_lines();
®ister_post_action(\&restart_apache, 1);
}
# check_cert_key_match(cert-text, key-text)
# Checks if the modulus for a cert and key match and are valid. Returns undef
# on success or an error message on failure.
sub check_cert_key_match
{
local ($certtext, $keytext) = @_;
local $certfile = &transname();
local $keyfile = &transname();
foreach $tf ([ $certtext, $certfile ], [ $keytext, $keyfile ]) {
&open_tempfile(CERTOUT, ">$tf->[1]", 0, 1);
&print_tempfile(CERTOUT, $tf->[0]);
&close_tempfile(CERTOUT);
}
# Get certificate modulus
local $certmodout = &backquote_command(
"openssl x509 -noout -modulus -in $certfile 2>&1");
$certmodout =~ /Modulus=([A-F0-9]+)/i ||
return "Certificate data is not valid : $certmodout";
local $certmod = $1;
# Get key modulus
local $keymodout = &backquote_command(
"openssl rsa -noout -modulus -in $keyfile 2>&1");
$keymodout =~ /Modulus=([A-F0-9]+)/i ||
return "Key data is not valid : $keymodout";
local $keymod = $1;
# Make sure they match
$certmod eq $keymod ||
return "Certificate and private key do not match";
return undef;
}
# validate_cert_format(data|file, type)
# Checks if some file or string contains valid cert or key data, and returns
# an error message if not. The type can be one of 'key', 'cert', 'ca' or 'csr'
sub validate_cert_format
{
local ($data, $type) = @_;
if ($data =~ /^\//) {
$data = &read_file_contents($data);
}
local %headers = ( 'key' => '(RSA )?PRIVATE KEY',
'cert' => 'CERTIFICATE',
'ca' => 'CERTIFICATE',
'csr' => 'CERTIFICATE REQUEST',
'newkey' => '(RSA ?)PRIVATE KEY' );
local $h = $headers{$type};
$h || return "Unknown SSL file type $type";
local @lines = grep { /\S/ } split(/\r?\n/, $data);
local $begin = quotemeta("-----BEGIN ").$h.quotemeta("-----");
local $end = quotemeta("-----END ").$h.quotemeta("-----");
$lines[0] =~ /^$begin$/ || return "Data does not start with line ".
"-----BEGIN $h-----";
$lines[$#lines] =~ /^$end$/ || return "Data does not end with line ".
"-----END $h-----";
for(my $i=1; $i<$#lines; $i++) {
$lines[$i] =~ /^[A-Za-z0-9\+\/=]+$/ ||
($type eq 'ca' && ($lines[$i] =~ /^$begin$/ ||
$lines[$i] =~ /^$end$/)) ||
return "Line ".($i+1)." does not look like PEM format";
}
@lines > 4 || return "Data only has ".scalar(@lines)." lines";
return undef;
}
# cert_pem_data(&domain)
# Returns a domain's cert in PEM format
sub cert_pem_data
{
local ($d) = @_;
local $data = &read_file_contents_as_domain_user($d, $d->{'ssl_cert'});
if ($data =~ /(-----BEGIN\s+CERTIFICATE-----\n([A-Za-z0-9\+\/=\n\r]+)-----END\s+CERTIFICATE-----)/) {
return $1;
}
return undef;
}
# key_pem_data(&domain)
# Returns a domain's key in PEM format
sub key_pem_data
{
local ($d) = @_;
local $data = &read_file_contents_as_domain_user($d, $d->{'ssl_key'} ||
$d->{'ssl_cert'});
if ($data =~ /(-----BEGIN\s+RSA\s+PRIVATE\s+KEY-----\n([A-Za-z0-9\+\/=\n\r]+)-----END\s+RSA\s+PRIVATE\s+KEY-----)/) {
return $1;
}
elsif ($data =~ /(-----BEGIN\s+PRIVATE\s+KEY-----\n([A-Za-z0-9\+\/=\n\r]+)-----END\s+PRIVATE\s+KEY-----)/) {
return $1;
}
return undef;
}
# cert_pkcs12_data(&domain)
# Returns a domain's cert in PKCS12 format
sub cert_pkcs12_data
{
local ($d) = @_;
local $cmd = "openssl pkcs12 -in ".quotemeta($d->{'ssl_cert'}).
" -inkey ".quotemeta($_[0]->{'ssl_key'}).
" -export -passout pass: -nokeys";
open(OUT, &command_as_user($d->{'user'}, 0, $cmd)." |");
while(<OUT>) {
$data .= $_;
}
close(OUT);
return $data;
}
# key_pkcs12_data(&domain)
# Returns a domain's key in PKCS12 format
sub key_pkcs12_data
{
local ($d) = @_;
local $cmd = "openssl pkcs12 -in ".quotemeta($d->{'ssl_cert'}).
" -inkey ".quotemeta($_[0]->{'ssl_key'}).
" -export -passout pass: -nocerts";
open(OUT, &command_as_user($d->{'user'}, 0, $cmd)." |");
while(<OUT>) {
$data .= $_;
}
close(OUT);
return $data;
}
# setup_ipkeys(&domain, &miniserv-getter, &miniserv-saver, &post-action)
# Add the per-IP SSL key for some domain, based on its IP address
sub setup_ipkeys
{
local ($dom, $getfunc, $putfunc, $postfunc) = @_;
&foreign_require("webmin", "webmin-lib.pl");
local %miniserv;
&$getfunc(\%miniserv);
local @ipkeys = &webmin::get_ipkeys(\%miniserv);
push(@ipkeys, { 'ips' => [ $_[0]->{'ip'} ],
'key' => $_[0]->{'ssl_key'},
'cert' => $_[0]->{'ssl_cert'},
'extracas' => $_[0]->{'ssl_ca'}, });
&webmin::save_ipkeys(\%miniserv, \@ipkeys);
&$putfunc(\%miniserv);
®ister_post_action($postfunc);
return 1;
}
# delete_ipkeys(&domain, &miniserv-getter, &miniserv-saver, &post-action)
# Remove the per-IP SSL key for some domain, based on its IP address
sub delete_ipkeys
{
local ($dom, $getfunc, $putfunc, $postfunc) = @_;
&foreign_require("webmin", "webmin-lib.pl");
local %miniserv;
&$getfunc(\%miniserv);
local @ipkeys = &webmin::get_ipkeys(\%miniserv);
local @newipkeys = grep { $_->{'ips'}->[0] ne $_[0]->{'ip'} } @ipkeys;
if (@ipkeys != @newipkeys) {
&webmin::save_ipkeys(\%miniserv, \@newipkeys);
&$putfunc(\%miniserv);
®ister_post_action($postfunc);
return 1;
}
return 0;
}
# modify_ipkeys(&domain, &olddomain, &miniserv-getter, &miniserv-saver,
# &post-action)
# Remove and then re-add the per-IP SSL key for a domain, to pick up any
# IP or home directory change
sub modify_ipkeys
{
local ($dom, $olddom, $getfunc, $putfunc, $postfunc) = @_;
if (&delete_ipkeys($olddom, $getfunc, $putfunc, $postfunc)) {
&setup_ipkeys($dom, $getfunc, $putfunc, $postfunc);
}
}
# apache_ssl_directives(&domain, template)
# Returns extra Apache directives needed for SSL
sub apache_ssl_directives
{
local ($d, $tmpl) = @_;
local @dirs;
push(@dirs, "SSLEngine on");
push(@dirs, "SSLCertificateFile $d->{'ssl_cert'}");
push(@dirs, "SSLCertificateKeyFile $d->{'ssl_key'}");
if ($d->{'ssl_chain'}) {
push(@dirs, "SSLCACertificateFile $d->{'ssl_chain'}");
}
return @dirs;
}
# check_certificate_data(data)
# Checks if some data looks like a valid cert. Returns undef if OK, or an error
# message if not
sub check_certificate_data
{
local ($data) = @_;
local $temp = &transname();
&open_tempfile(CERTDATA, ">$temp", 0, 1);
&print_tempfile(CERTDATA, $data);
&close_tempfile(CERTDATA);
local $out = &backquote_command("openssl x509 -in ".quotemeta($temp)." -issuer -subject -enddate 2>&1");
local $ex = $?;
&unlink_file($temp);
if ($ex) {
return "<tt>".&html_escape($out)."</tt>";
}
elsif ($out !~ /subject=.*(CN|O)=/) {
return $text{'cert_esubject'};
}
else {
return undef;
}
}
# default_certificate_file(&domain, "cert"|"key"|"ca")
# Returns the default path that should be used for a cert, key or CA file
sub default_certificate_file
{
local ($d, $mode) = @_;
return $config{$mode.'_tmpl'} ?
&absolute_domain_path($d,
&substitute_domain_template($config{$mode.'_tmpl'}, $d)) :
"$d->{'home'}/ssl.".$mode;
}
# set_certificate_permissions(&domain, file)
# Set permissions on a cert file so that Apache can read them.
sub set_certificate_permissions
{
local ($d, $file) = @_;
&set_permissions_as_domain_user($d, 0700, $file);
}
# check_domain_certificate(domain-name, &domain-with-cert|&cert-info)
# Returns 1 if some virtual server's certificate can be used for a particular
# domain, 0 if not. Based on the common names, including wildcards and UCC
sub check_domain_certificate
{
local ($dname, $d_or_info) = @_;
local $info = $d_or_info->{'dom'} ? &cert_info($d_or_info) : $d_or_info;
foreach my $check ($dname, "www.".$dname) {
if (lc($info->{'cn'}) eq lc($check)) {
# Exact match
return 1;
}
elsif ($info->{'cn'} =~ /^\*\.(\S+)$/ &&
(lc($check) eq lc($1) || $check =~ /\.\Q$1\E$/i)) {
# Matches wildcard
return 1;
}
elsif ($info->{'cn'} eq '*') {
# Cert is for * , which matches everything
return 1;
}
else {
# Check for subjectAltNames match (as seen in UCC certs)
foreach my $a (@{$info->{'alt'}}) {
if (lc($a) eq $check ||
$a =~ /^\*\.(\S+)$/ &&
(lc($check) eq lc($1) || $check =~ /\.\Q$1\E$/i)) {
return 1;
}
}
}
}
return 0;
}
# list_domain_certificate(&domain|&cert-info)
# Returns a list of domain names that are in the cert for a domain
sub list_domain_certificate
{
local ($d_or_info) = @_;
local $info = $d_or_info->{'dom'} ? &cert_info($d_or_info) : $d_or_info;
local @rv;
push(@rv, $info->{'cn'});
push(@rv, @{$info->{'alt'}});
return &unique(@rv);
}
# self_signed_cert(&domain)
# Returns 1 if some domain has a self-signed certificate
sub self_signed_cert
{
local ($d) = @_;
local $info = &cert_info($d);
return $info->{'issuer_cn'} eq $info->{'cn'} &&
$info->{'issuer_o'} eq $info->{'o'};
}
# find_openssl_config_file()
# Returns the full path to the OpenSSL config file, or undef if not found
sub find_openssl_config_file
{
foreach my $p ($config{'openssl_cnf'}, # Module config
"/etc/ssl/openssl.cnf", # Debian and FreeBSD
"/etc/openssl.cnf",
"/usr/local/etc/openssl.cnf",
"/etc/pki/tls/openssl.cnf", # Redhat
"/opt/csw/ssl/openssl.cnf", # Solaris CSW
"/opt/csw/etc/ssl/openssl.cnf", # Solaris CSW
"/System/Library/OpenSSL/openssl.cnf", # OSX
) {
return $p if ($p && -r $p);
}
return undef;
}
# generate_self_signed_cert(certfile, keyfile, size, days, country, state,
# city, org, orgunit, commonname, email, &altnames,
# &domain, [cert-type])
# Generates a new self-signed cert, and stores it in the given cert and key
# files. Returns undef on success, or an error message on failure.
sub generate_self_signed_cert
{
local ($certfile, $keyfile, $size, $days, $country, $state, $city, $org,
$orgunit, $common, $email, $altnames, $d, $ctype) = @_;
$ctype ||= $config{'default_ctype'};
&foreign_require("webmin", "webmin-lib.pl");
$size ||= $webmin::default_key_size;
$days ||= 1825;
# Prepare for SSL alt names
local $flag;
if ($altnames && @$altnames) {
$flag = &setup_openssl_altnames([ @$altnames, $common ], 1);
}
# Call openssl and write to temp files
local $outtemp = &transname();
local $keytemp = &transname();
local $certtemp = &transname();
local $ctypeflag = $ctype eq "sha2" ? "-sha256" : "";
&open_execute_command(CA, "openssl req $ctypeflag $flag -newkey rsa:$size ".
"-x509 -nodes -out $certtemp -keyout $keytemp ".
"-days $days >$outtemp 2>&1", 0);
print CA ($country || "."),"\n";
print CA ($state || "."),"\n";
print CA ($city || "."),"\n";
print CA ($org || "."),"\n";
print CA ($orgunit || "."),"\n";
print CA ($common || "*"),"\n";
print CA ($email || "."),"\n";
close(CA);
local $rv = $?;
local $out = &read_file_contents($outtemp);
unlink($outtemp);
if (!-r $certtemp || !-r $keytemp || $?) {
# Failed .. return error
return &text('csr_ekey', "<pre>$out</pre>");
}
# Save as domain owner
&open_tempfile_as_domain_user($d, CERT, ">$certfile");
&print_tempfile(CERT, &read_file_contents($certtemp));
&close_tempfile_as_domain_user($d, CERT);
&open_tempfile_as_domain_user($d, KEY, ">$keyfile");
&print_tempfile(KEY, &read_file_contents($keytemp));
&close_tempfile_as_domain_user($d, KEY);
return undef;
}
# generate_certificate_request(csrfile, keyfile, size, days, country, state,
# city, org, orgunit, commonname, email, &altnames,
# &domain, [cert-type])
# Generates a new self-signed cert, and stores it in the given csr and key
# files. Returns undef on success, or an error message on failure.
sub generate_certificate_request
{
local ($csrfile, $keyfile, $size, $days, $country, $state, $city, $org,
$orgunit, $common, $email, $altnames, $d, $ctype) = @_;
$ctype ||= $config{'cert_type'};
&foreign_require("webmin", "webmin-lib.pl");
$size ||= $webmin::default_key_size;
$days ||= 1825;
# Prepare for SSL alt names
local $flag;
if ($altnames && @$altnames) {
$flag = &setup_openssl_altnames([ @$altnames, $common ], 0);
}
# Generate the key
local $keytemp = &transname();
local $out = &backquote_command("openssl genrsa -out ".quotemeta($keytemp)." $size 2>&1 </dev/null");
local $rv = $?;
if (!-r $keytemp || $rv) {
return &text('csr_ekey', "<pre>$out</pre>");
}
&open_tempfile_as_domain_user($d, KEY, ">$keyfile");
&print_tempfile(KEY, &read_file_contents($keytemp));
&close_tempfile_as_domain_user($d, KEY);
# Generate the matching CSR
local $outtemp = &transname();
local $csrtemp = &transname();
local $ctypeflag = $ctype eq "sha2" ? "-sha256" : "";
&open_execute_command(CA, "openssl req $ctypeflag $flag -new".
" -key ".quotemeta($keytemp).
" -out ".quotemeta($csrtemp).
" >$outtemp 2>&1", 0);
print CA ($country || "."),"\n";
print CA ($state || "."),"\n";
print CA ($city || "."),"\n";
print CA ($org || "."),"\n";
print CA ($orgunit || "."),"\n";
print CA ($common || "*"),"\n";
print CA ($email || "."),"\n";
print CA ".\n";
print CA ".\n";
close(CA);
local $rv = $?;
local $out = &read_file_contents($outtemp);
unlink($outtemp);
if (!-r $csrtemp || $rv) {
return &text('csr_ecsr', "<pre>$out</pre>");
}
# Copy into place
&open_tempfile_as_domain_user($d, CERT, ">$csrfile");
&print_tempfile(CERT, &read_file_contents($csrtemp));
&close_tempfile_as_domain_user($d, CERT);
return undef;
}
# setup_openssl_altnames(&altnames, self-signed)
# Creates a temporary openssl.cnf file for generating a cert with alternate
# names. Returns the additional command line parameters for openssl to use it.
sub setup_openssl_altnames
{
local ($altnames, $self) = @_;
local @alts = &unique(@$altnames);
local $temp = &transname();
local $sconf = &find_openssl_config_file();
$sconf || &error($text{'cert_esconf'});
©_source_dest($sconf, $temp);
# Make sure subjectAltNames is set in .cnf file, in the right places
local $lref = &read_file_lines($temp);
local $i = 0;
local $found_req = 0;
local $found_ca = 0;
local $altline = "subjectAltName=".join(",", map { "DNS:$_" } @alts);
foreach my $l (@$lref) {
if ($l =~ /^\s*\[\s*v3_req\s*\]/ && !$found_req) {
splice(@$lref, $i+1, 0, $altline);
$found_req = 1;
}
if ($l =~ /^\s*\[\s*v3_ca\s*\]/ && !$found_ca) {
splice(@$lref, $i+1, 0, $altline);
$found_ca = 1;
}
$i++;
}
# If v3_req or v3_ca sections are missing, add at end
if (!$found_req) {
push(@$lref, "[ v3_req ]", $altline);
}
if (!$found_ca) {
push(@$lref, "[ v3_ca ]", $altline);
}
# Add copyall line if needed
local $i = 0;
local $found_copy = 0;
local $copyline = "copy_extensions=copyall";
foreach my $l (@$lref) {
if (/^\s*\#*\s*copy_extensions\s*=/) {
$l = $copyline;
$found_copy = 1;
last;
}
elsif (/^\s*\[\s*CA_default\s*\]/) {
$found_ca = $i;
}
$i++;
}
if (!$found_copy) {
if ($found_ca) {
splice(@$lref, $found_ca+1, 0, $copyline);
}
else {
push(@$lref, "[ CA_default ]", $copyline);
}
}
&flush_file_lines($temp);
local $flag = "-config $temp -reqexts v3_req";
if ($self) {
$flag .= " -reqexts v3_ca";
}
return $flag;
}
# obtain_lock_ssl(&domain)
# Lock the Apache config file for some domain, and the Webmin config
sub obtain_lock_ssl
{
local ($d) = @_;
return if (!$config{'ssl'});
&obtain_lock_anything($d);
&obtain_lock_web($d) if ($d->{'web'});
if ($main::got_lock_ssl == 0) {
local @sfiles = ($ENV{'MINISERV_CONFIG'} ||
"$config_directory/miniserv.conf",
$config_directory =~ /^(.*)\/webmin$/ ?
"$1/usermin/miniserv.conf" :
"/etc/usermin/miniserv.conf");
foreach my $k ('ssl_cert', 'ssl_key', 'ssl_chain') {
push(@sfiles, $d->{$k}) if ($d->{$k});
}
@sfiles = &unique(@sfiles);
foreach my $f (@sfiles) {
&lock_file($f);
}
@main::got_lock_ssl_files = @sfiles;
}
$main::got_lock_ssl++;
}
# release_lock_web(&domain)
# Un-lock the Apache config file for some domain, and the Webmin config
sub release_lock_ssl
{
local ($d) = @_;
return if (!$config{'ssl'});
&release_lock_web($d) if ($d->{'web'});
if ($main::got_lock_ssl == 1) {
foreach my $f (@main::got_lock_ssl_files) {
&unlock_file($f);
}
}
$main::got_lock_ssl-- if ($main::got_lock_ssl);
&release_lock_anything($d);
}
# find_matching_certificate(&domain)
# For a domain with SSL being enabled, check if another domain on the same IP
# already has a matching cert. If so, update the domain hash's cert file
sub find_matching_certificate
{
local ($d) = @_;
local ($sslclash) = grep { $_->{'ip'} eq $d->{'ip'} &&
$_->{'ssl'} &&
$_->{'id'} ne $d->{'id'} &&
!$_->{'ssl_same'} } &list_domains();
if ($sslclash && &check_domain_certificate($d->{'dom'}, $sslclash)) {
# Yes - so just use it. In practice this doesn't really matter, as
# Apache will pick up the first domain's cert anyway.
$d->{'ssl_cert'} = $sslclash->{'ssl_cert'};
$d->{'ssl_key'} = $sslclash->{'ssl_key'};
$d->{'ssl_same'} = $sslclash->{'id'};
$d->{'ssl_chain'} = &get_website_ssl_file($sslclash, 'ca');
}
}
# generate_default_certificate(&domain)
# If a domain doesn't have a cert file set, pick one and generate a self-signed
# cert if needed. May print stuff.
sub generate_default_certificate
{
local ($d) = @_;
$d->{'ssl_cert'} ||= &default_certificate_file($d, 'cert');
$d->{'ssl_key'} ||= &default_certificate_file($d, 'key');
if (!-r $d->{'ssl_cert'} && !-r $d->{'ssl_key'}) {
# Need to do it
local $temp = &transname();
&$first_print($text{'setup_openssl'});
&lock_file($d->{'ssl_cert'});
&lock_file($d->{'ssl_key'});
local $err = &generate_self_signed_cert(
$d->{'ssl_cert'}, $d->{'ssl_key'}, undef, 1825,
undef, undef, undef, $d->{'owner'}, undef,
"*.$d->{'dom'}", $d->{'emailto_addr'}, undef, $d);
if ($err) {
&$second_print(&text('setup_eopenssl', $err));
return 0;
}
else {
&set_certificate_permissions($d, $d->{'ssl_cert'});
&set_certificate_permissions($d, $d->{'ssl_key'});
if (&has_command("chcon")) {
&execute_command("chcon -R -t httpd_config_t ".quotemeta($d->{'ssl_cert'}).">/dev/null 2>&1");
&execute_command("chcon -R -t httpd_config_t ".quotemeta($d->{'ssl_key'}).">/dev/null 2>&1");
}
&$second_print($text{'setup_done'});
}
&unlock_file($d->{'ssl_cert'});
&unlock_file($d->{'ssl_key'});
}
}
# break_ssl_linkage(&domain, &old-same-domain)
# If domain was using the SSL cert from old-same-domain before, break the link
# by copying the cert into the default location for domain and updating the
# domain and Apache config to match
sub break_ssl_linkage
{
local ($d, $samed) = @_;
foreach my $k ('cert', 'key', 'ca') {
if ($d->{'ssl_'.$k}) {
$d->{'ssl_'.$k} = &default_certificate_file($d, $k);
if ($d->{'user'} eq $samed->{'user'}) {
©_source_dest_as_domain_user(
$d, $samed->{'ssl_'.$k}, $d->{'ssl_'.$k});
}
else {
©_source_dest($samed->{'ssl_'.$k}, $d->{'ssl_'.$k});
}
}
}
delete($d->{'ssl_same'});
if ($d->{'web'}) {
local ($ovirt, $ovconf, $conf) = &get_apache_virtual(
$d->{'dom'}, $d->{'web_sslport'});
if ($ovirt) {
&apache::save_directive("SSLCertificateFile",
[ $d->{'ssl_cert'} ], $ovconf, $conf);
&apache::save_directive("SSLCertificateKeyFile",
$d->{'ssl_key'} ? [ $d->{'ssl_key'} ] : [ ],
$ovconf, $conf);
&apache::save_directive("SSLCACertificateFile",
$d->{'ssl_chain'} ? [ $d->{'ssl_chain'} ] : [ ],
$ovconf, $conf);
&flush_file_lines($ovirt->{'file'});
}
}
}
# break_invalid_ssl_linkages(&domain, [&new-cert])
# Find all domains that link to this domain's SSL cert, and if their domain
# names are no longer legit for the cert, break the link.
sub break_invalid_ssl_linkages
{
local ($d, $newcert) = @_;
foreach $od (&get_domain_by("ssl_same", $d->{'id'})) {
if (!&check_domain_certificate($od->{'dom'}, $newcert || $d)) {
&break_ssl_linkage($od, $d);
&save_domain($od);
}
}
}
# sync_dovecot_ssl_cert(&domain, [enable-or-disable])
# If supported, configure Dovecot to use this domain's SSL cert for its IP
sub sync_dovecot_ssl_cert
{
local ($d, $enable) = @_;
# Check if dovecot is installed and supports this feature
return undef if (!$config{'dovecot_ssl'});
return undef if (!&foreign_installed("dovecot"));
&foreign_require("dovecot");
my $ver = &dovecot::get_dovecot_version();
return undef if ($ver < 2);
# Check if dovecot is using SSL globally
my $conf = &dovecot::get_config();
my $sslyn = &dovecot::find_value("ssl_disable", $conf);
return undef if ($sslyn !~ /yes|required/i);
# Find the existing block for the IP
my $cfile = &dovecot::get_config_file();
&lock_file($cfile);
my @loc = grep { $_->{'name'} eq 'local' &&
$_->{'section'} } @$conf;
my ($l) = grep { $_->{'value'} eq $d->{'ip'} } @loc;
my $imap;
if ($l) {
($imap) = grep { $_->{'value'} eq 'imap' }
&dovecot::find("protocol", $l->{'members'});
}
if ($enable) {
# Needs a cert for the IP
local $chain = &get_website_ssl_file($d, "ca");
if (!$l) {
$l = { 'name' => 'local',
'value' => $d->{'ip'},
'members' => [],
'file' => $cfile };
my $lref = &read_file_lines($l->{'file'}, 1);
$l->{'line'} = $l->{'eline'} = scalar(@$lref);
&dovecot::save_section($conf, $l);
push(@$conf, $l);
}
if (!$imap) {
$imap = { 'name' => 'protocol',
'value' => 'imap',
'members' => [
{ 'name' => 'ssl_cert',
'value' => "<".$d->{'ssl_cert'} },
{ 'name' => 'ssl_key',
'value' => "<".$d->{'ssl_key'} },
],
'indent' => 1,
'file' => $l->{'file'},
'line' => $l->{'line'} + 1,
'eline' => $l->{'line'} };
if ($chain) {
push(@{$imap->{'members'}},
{ 'name' => 'ssl_ca',
'value' => "<".$chain });
}
&dovecot::save_section($conf, $imap);
push(@{$l->{'members'}}, $imap);
}
else {
&dovecot::save_directive($l->{'members'}, "ssl_cert",
"<".$d->{'ssl_cert'}, "protocol", "imap");
&dovecot::save_directive($l->{'members'}, "ssl_key",
"<".$d->{'ssl_key'}, "protocol", "imap");
if ($chain) {
&dovecot::save_directive($l->{'members'}, "ssl_ca",
"<".$chain, "protocol", "imap");
}
}
&flush_file_lines($imap->{'file'});
}
else {
# Doesn't need one, either because SSL isn't enabled or the domain
# doesn't have a private IP. So remove the whole local block.
if ($l) {
my $lref = &read_file_lines($l->{'file'});
splice(@$lref, $l->{'line'}, $l->{'eline'}-$l->{'line'}+1);
&flush_file_lines($l->{'file'});
undef(@dovecot::get_config_cache);
}
}
&unlock_file($cfile);
if ($changed) {
&dovecot::apply_configuration();
}
}
# sync_postfix_ssl_cert(&domain, enable)
# Configure Postfix to use a domain's SSL cert for connections on its IP
sub sync_postfix_ssl_cert
{
local ($d, $enable) = @_;
# Check if Postfix is in use
return undef if ($config{'mail_system'} != 0);
return undef if (!$config{'postfix_ssl'});
# Check if using SSL globally
&foreign_require("postfix");
local $cfile = &postfix::get_real_value("smtpd_tls_cert_file");
return undef if (!$cfile);
# Find the existing master file entry
&lock_file($postfix::config{'postfix_master'});
local $master = &postfix::get_master_config();
local $defip = &get_default_ip();
# Work out which flags are needed
local $chain = &get_website_ssl_file($d, 'ca');
local @flags = ( [ "smtpd_tls_cert_file", $d->{'ssl_cert'} ],
[ "smtpd_tls_key_file", $d->{'ssl_key'} ] );
push(@flags, [ "smtpd_tls_CAfile", $chain ]) if ($chain);
local $changed = 0;
foreach my $pfx ('smtp', 'submission') {
# Find the existing entry for the IP, and for the default service
local $already;
local $smtp;
local @others;
local $lsmtp;
foreach my $m (@$master) {
if ($m->{'name'} eq $d->{'ip'}.':'.$pfx && $m->{'enabled'}) {
# Entry for service for the domain
$already = $m;
}
if (($m->{'name'} eq $pfx || $m->{'name'} eq $defip.':'.$pfx) &&
$m->{'type'} eq 'inet' && $m->{'enabled'}) {
# Entry for default service
$smtp = $m;
}
if ($m->{'name'} =~ /^([0-9\.]+):\Q$pfx\E$/ &&
$m->{'enabled'} && $1 ne $d->{'ip'} && $1 ne $defip) {
# Entry for some other domain
if ($1 eq "127.0.0.1") {
$lsmtp = $m;
}
else {
push(@others, $m);
}
}
}
next if (!$smtp);
if ($enable) {
# Create or update the entry
if (!$already) {
# Create based on smtp inet entry
$already = { %$smtp };
delete($already->{'line'});
delete($already->{'uline'});
$already->{'name'} = $d->{'ip'}.':'.$pfx;
foreach my $f (@flags) {
$already->{'command'} .=
" -o ".$f->[0]."=".$f->[1];
}
&postfix::create_master($already);
$changed = 1;
# If the primary smtp entry isn't bound to an IP, fix it
# to prevent IP clashes
if ($smtp->{'name'} eq $pfx) {
$smtp->{'name'} = $defip.':'.$pfx;
&postfix::modify_master($smtp);
# Also add an entry to listen on 127.0.0.1
if (!$lsmtp) {
$lsmtp = { %$smtp };
delete($lsmtp->{'line'});
delete($lsmtp->{'uline'});
$lsmtp->{'name'} = '127.0.0.1:'.$pfx;
&postfix::create_master($lsmtp);
}
}
}
else {
# Update cert file paths
local $oldcommand = $already->{'command'};
foreach my $f (@flags) {
($already->{'command'} =~
s/-o\s+\Q$f->[0]\E=(\S+)/-o $f->[0]=$f->[1]/)
||
($already->{'command'} .=
" -o ".$f->[0]."=".$f->[1]);
}
if ($oldcommand ne $already->{'command'}) {
&postfix::modify_master($already);
$changed = 1;
}
}
}
else {
# Remove the entry
if ($already) {
&postfix::delete_master($already);
$changed = 1;
}
if (!@others && $smtp->{'name'} ne $pfx) {
# If the default service has an IP but this is no longer
# needed, remove it
$smtp->{'name'} = $pfx;
&postfix::modify_master($smtp);
$changed = 1;
# Also remove 127.0.0.1 entry
if ($lsmtp) {
&postfix::delete_master($lsmtp);
}
}
}
}
&unlock_file($postfix::config{'postfix_master'});
if ($changed) {
®ister_post_action(\&restart_mail_server);
}
}
$done_feature_script{'ssl'} = 1;
1;
y~or5J={Eeu磝Qk ᯘG{?+]ן?wM3X^歌>{7پK>on\jy Rg/=fOroNVv~Y+ NGuÝHWyw[eQʨSb> >}Gmx[o[<{Ϯ_qFvM IENDB`