sub init_ssl
{
$feature_depends{'ssl'} = [ 'web', 'dir' ];
$default_web_sslport = $config{'web_sslport'} || 443;
}
# check_depends_ssl(&dom)
# An SSL website requires either a private IP, or private port
sub check_depends_ssl
{
local $tmpl = &get_template($_[0]->{'template'});
local $defport = $tmpl->{'web_sslport'} || 443;
local $port = $_[0]->{'web_sslport'} || $defport;
if ($_[0]->{'virt'}) {
# Has a private IP
return undef;
}
elsif ($port != $defport) {
# Has a private port
return undef;
}
else {
# Neither!
return $text{'setup_edepssl2'};
}
}
# setup_ssl(&domain)
# Creates a website with SSL enabled, and a private key and cert it to use.
sub setup_ssl
{
local $tmpl = &get_template($_[0]->{'template'});
local $web_sslport = $_[0]->{'web_sslport'} || $tmpl->{'web_sslport'} || 443;
&require_apache();
local $conf = &apache::get_config();
local $f = &get_website_file($_[0]);
&lock_file($f);
# Create a self-signed cert and key, if needed
$_[0]->{'ssl_cert'} ||= &default_certificate_file($_[0], 'cert');
$_[0]->{'ssl_key'} ||= &default_certificate_file($_[0], 'key');
if (!-r $_[0]->{'ssl_cert'} && !-r $_[0]->{'ssl_key'}) {
# Need to do it
&foreign_require("webmin", "webmin-lib.pl");
local $temp = &transname();
&$first_print($text{'setup_openssl'});
&lock_file($_[0]->{'ssl_cert'});
&lock_file($_[0]->{'ssl_key'});
local $size = $config{'key_size'} || $webmin::default_key_size;
&open_execute_command(CA, "openssl req -newkey rsa:$size -x509 -nodes -out $_[0]->{'ssl_cert'} -keyout $_[0]->{'ssl_key'} -days 1825 >$temp 2>&1", 0);
print CA ".\n";
print CA ".\n";
print CA ".\n";
print CA "$_[0]->{'owner'}\n";
print CA ".\n";
print CA "*.$_[0]->{'dom'}\n";
print CA ($_[0]->{'email'} || "."),"\n";
close(CA);
local $rv = $?;
local $out = `cat $temp`;
unlink($temp);
if (!-r $_[0]->{'ssl_cert'} || !-r $_[0]->{'ssl_key'} || $?) {
&$second_print(&text('setup_eopenssl', "
$out
"));
return 0;
}
else {
&set_ownership_permissions($_[0]->{'uid'}, $_[0]->{'ugid'}, 0755, $_[0]->{'ssl_cert'}, $_[0]->{'ssl_key'});
if (&has_command("chcon")) {
&execute_command("chcon -R -t httpd_config_t ".quotemeta($_[0]->{'ssl_cert'}).">/dev/null 2>&1");
&execute_command("chcon -R -t httpd_config_t ".quotemeta($_[0]->{'ssl_key'}).">/dev/null 2>&1");
}
&$second_print($text{'setup_done'});
}
&unlock_file($_[0]->{'ssl_cert'});
&unlock_file($_[0]->{'ssl_key'});
}
# Add a Listen directive if needed
&add_listen($_[0], $conf, $web_sslport);
# Find directives in the non-SSL virtualhost, for copying
&$first_print($text{'setup_ssl'});
local ($virt, $vconf) = &get_apache_virtual($_[0]->{'dom'},
$_[0]->{'web_port'});
if (!$virt) {
&$second_print($text{'setup_esslcopy'});
return 0;
}
local $srclref = &read_file_lines($virt->{'file'});
# Add the actual
local $lref = &read_file_lines($f);
local @ssldirs = &apache_ssl_directives($_[0], $tmpl);
push(@$lref, "{'ip'}:$web_sslport>");
push(@$lref, @$srclref[$virt->{'line'}+1 .. $virt->{'eline'}-1]);
push(@$lref, @ssldirs);
push(@$lref, "");
&flush_file_lines($f);
&unlock_file($f);
# Update the non-SSL virtualhost to include the port number, to fix old
# hosts that were missing the :80
&lock_file($virt->{'file'});
local $lref = &read_file_lines($virt->{'file'});
if (!$_[0]->{'name'} && $lref->[$virt->{'line'}] !~ /:\d+/) {
$lref->[$virt->{'line'}] =
"{'ip'}:$_[0]->{'web_port'}>";
&flush_file_lines();
}
&unlock_file($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($_[0], \&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($_[0], \&usermin::get_usermin_miniserv_config,
\&usermin::put_usermin_miniserv_config,
\&restart_usermin);
}
&$second_print($text{'setup_done'});
®ister_post_action(\&restart_apache, 1);
$_[0]->{'web_sslport'} = $web_sslport;
}
# modify_ssl(&domain, &olddomain)
sub modify_ssl
{
local $rv = 0;
&require_apache();
local $conf = &apache::get_config();
local ($virt, $vconf) = &get_apache_virtual($_[1]->{'dom'},
$_[1]->{'web_sslport'});
local $tmpl = &get_template($_[0]->{'template'});
&lock_file($virt->{'file'});
if ($_[0]->{'home'} ne $_[1]->{'home'}) {
# Home directory has changed .. update any directives that referred
# to the old directory
&$first_print($text{'save_ssl3'});
local $lref = &read_file_lines($virt->{'file'});
for($i=$virt->{'line'}; $i<=$virt->{'eline'}; $i++) {
$lref->[$i] =~ s/$_[1]->{'home'}/$_[0]->{'home'}/g;
}
&flush_file_lines();
$rv++;
&$second_print($text{'setup_done'});
}
if ($_[0]->{'dom'} ne $_[1]->{'dom'}) {
# Domain name has changed
&$first_print($text{'save_ssl2'});
&apache::save_directive("ServerName", [ $_[0]->{'dom'} ], $vconf,$conf);
local @sa = map { s/$_[1]->{'dom'}/$_[0]->{'dom'}/g; $_ }
&apache::find_directive("ServerAlias", $vconf);
&apache::save_directive("ServerAlias", \@sa, $vconf, $conf);
&flush_file_lines();
$rv++;
&$second_print($text{'setup_done'});
}
if ($_[0]->{'ip'} ne $_[1]->{'ip'} ||
$_[0]->{'web_sslport'} != $_[1]->{'web_sslport'}) {
# IP address or port has changed .. update VirtualHost
&$first_print($text{'save_ssl'});
local $conf = &apache::get_config();
&add_listen($_[0], $conf, $_[0]->{'web_sslport'});
local $lref = &read_file_lines($virt->{'file'});
$lref->[$virt->{'line'}] =
"{'ip'}:$_[1]->{'web_sslport'}>";
&flush_file_lines();
$rv++;
&$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'});
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'});
local ($nonvirt, $nonvconf) = &get_apache_virtual($_[1]->{'dom'},
$_[1]->{'web_port'});
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++;
&$second_print($text{'setup_done'});
}
if ($_[0]->{'ip'} ne $_[1]->{'ip'}) {
# IP address has changed .. fix per-IP SSL cert
if ($tmpl->{'web_webmin_ssl'}) {
&modify_ipkeys($_[0], $_[1], \&get_miniserv_config,
\&put_miniserv_config,
\&restart_webmin);
}
if ($tmpl->{'web_usermin_ssl'} && &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);
}
}
&unlock_file($virt->{'file'});
®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
{
&require_apache();
local $conf = &apache::get_config();
&$first_print($text{'delete_ssl'});
# Remove the custom Listen directive added for the domain
&remove_listen($d, $conf, $d->{'web_sslport'});
# Remove the
local ($virt, $vconf) = &get_apache_virtual($_[0]->{'dom'},
$_[0]->{'web_sslport'});
local $tmpl = &get_template($_[0]->{'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
if ($tmpl->{'web_webmin_ssl'}) {
&delete_ipkeys($_[0], \&get_miniserv_config,
\&put_miniserv_config,
\&restart_webmin);
}
if ($tmpl->{'web_usermin_ssl'} && &foreign_installed("usermin")) {
&foreign_require("usermin", "usermin-lib.pl");
&delete_ipkeys($_[0], \&usermin::get_usermin_miniserv_config,
\&usermin::put_usermin_miniserv_config,
\&restart_usermin);
}
}
# 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', "$d->{'dom'}") if (!$virt);
local $cert = &apache::find_directive("SSLCertificateFile", $vconf, 1);
if (!$cert) {
return &text('validate_esslcert');
}
elsif (!-r $cert) {
return &text('validate_esslcertfile', "$cert");
}
local $key = &apache::find_directive("SSLCertificateKeyFile", $vconf, 1);
if ($key && !-r $key) {
return &text('validate_esslkeyfile', "$key");
}
return undef;
}
# check_ssl_clash(&domain, [field])
# Returns 1 if an SSL Apache webserver already exists for some domain
sub check_ssl_clash
{
if (!$_[1] || $_[1] eq 'dom') {
local $tmpl = &get_template($_[0]->{'template'});
local $web_sslport = $tmpl->{'web_sslport'} || 443;
local ($cvirt, $cconf) = &get_apache_virtual($_[0]->{'dom'}, $web_sslport);
return $cvirt ? 1 : 0;
}
return 0;
}
# 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
{
&$first_print($text{'backup_sslcp'});
# Save the apache directives
local ($virt, $vconf) = &get_apache_virtual($_[0]->{'dom'},
$_[0]->{'web_sslport'});
local $lref = &read_file_lines($virt->{'file'});
local $l;
&open_tempfile(FILE, ">$_[1]");
foreach $l (@$lref[$virt->{'line'} .. $virt->{'eline'}]) {
&print_tempfile(FILE, "$l\n");
}
&close_tempfile(FILE);
# Save the cert and key, if any
local $cert = &apache::find_directive("SSLCertificateFile", $vconf, 1);
if ($cert) {
&execute_command("cp ".quotemeta($cert)." ".quotemeta("$_[1]_cert"));
}
local $key = &apache::find_directive("SSLCertificateKeyFile", $vconf, 1);
if ($key && $key ne $cert) {
&execute_command("cp ".quotemeta($key)." ".quotemeta("$_[1]_key"));
}
&$second_print($text{'setup_done'});
return 1;
}
# restore_ssl(&domain, file, &options)
# Update the SSL virtual server's Apache configuration from a file. Does not
# change the actual lines!
sub restore_ssl
{
&$first_print($text{'restore_sslcp'});
# Restore the Apache directives
local ($virt, $vconf) = &get_apache_virtual($_[0]->{'dom'},
$_[0]->{'web_sslport'});
local $srclref = &read_file_lines($_[1]);
local $dstlref = &read_file_lines($virt->{'file'});
&lock_file($virt->{'file'});
splice(@$dstlref, $virt->{'line'}+1, $virt->{'eline'}-$virt->{'line'}-1,
@$srclref[1 .. @$srclref-2]);
# Fix ip address in section (if needed)
if ($dstlref->[$virt->{'line'}] =~
/^(.*[$virt->{'line'}] = $1.$_[0]->{'ip'}.$3;
}
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();
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) {
foreach my $dir ("User", "Group", "SuexecUserGroup") {
local @vals = &apache::find_directive($dir, $nvconf);
&apache::save_directive($dir, \@vals, $vconf, $conf);
}
&flush_file_lines();
}
&unlock_file($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);
&execute_command("cp ".quotemeta("$_[1]_cert")." ".quotemeta($cert));
&unlock_file($cert);
}
local $key = &apache::find_directive("SSLCertificateKeyFile", $vconf, 1);
if ($key && -r "$_[1]_key" && $key ne $cert) {
&lock_file($key);
&execute_command("cp ".quotemeta("$_[1]_key")." ".quotemeta($key));
&unlock_file($key);
}
&$second_print($text{'setup_done'});
®ister_post_action(\&restart_apache);
return 1;
}
# cert_info(&domain)
# Returns a hash of details of a domain's cert
sub cert_info
{
return &cert_file_info($_[0]->{'ssl_cert'});
}
# cert_file_info(file)
# Returns a hash of details of a cert in some file
sub cert_file_info
{
local ($file) = @_;
local %rv;
local $_;
open(OUT, "openssl x509 -in ".quotemeta($file)." -issuer -subject -enddate |");
while() {
s/\r|\n//g;
s/http:\/\//http:\|\|/g; # So we can parse with regexp
if (/subject=.*CN=([^\/]+)/) {
$rv{'cn'} = $1;
}
if (/subject=.*O=([^\/]+)/) {
$rv{'o'} = $1;
}
if (/issuer=.*CN=([^\/]+)/) {
$rv{'issuer_cn'} = $1;
}
if (/issuer=.*O=([^\/]+)/) {
$rv{'issuer_o'} = $1;
}
if (/notAfter=(.*)/) {
$rv{'notafter'} = $1;
}
}
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;
}
# cert_pem_data(&domain)
# Returns a domain's cert in PEM format
sub cert_pem_data
{
local ($d) = @_;
local $data = &read_file_contents($d->{'ssl_cert'});
if ($data =~ /(-----BEGIN\s+CERTIFICATE-----\n([A-Za-z0-9\+\/=\n\r]+)-----END\s+CERTIFICATE-----)/) {
return $1;
}
return undef;
}
# cert_pkcs12_data(&domain)
# Returns a domain's cert in PKCS12 format
sub cert_pkcs12_data
{
local ($d) = @_;
open(OUT, "openssl pkcs12 -in ".quotemeta($d->{'ssl_cert'}).
" -inkey ".quotemeta($_[0]->{'ssl_key'}).
" -export -passout pass: -nokeys |");
while() {
$data .= $_;
}
close(OUT);
return $data;
}
# setup_ipkeys(&domain, &miniserv-getter, &miniserv-saver, &post-action)
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'} });
&webmin::save_ipkeys(\%miniserv, \@ipkeys);
&$putfunc(\%miniserv);
®ister_post_action($postfunc);
return 1;
}
# delete_ipkeys(&domain, &miniserv-getter, &miniserv-saver, &post-action)
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)
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'}");
return @dirs;
}
# get_chained_certificate_file(&domain)
# Returns the file used for the chained cert, or undef if not set
sub get_chained_certificate_file
{
local ($d) = @_;
local ($virt, $vconf) = &get_apache_virtual($d->{'dom'},
$d->{'web_sslport'});
return undef if (!$virt);
local ($cert) = &apache::find_directive("SSLCACertificateFile", $vconf);
return $cert;
}
# save_chained_certificate_file(&domain, [file])
# Updates the chained cert file, or removed it if file is undef
sub save_chained_certificate_file
{
local ($d, $file) = @_;
local ($virt, $vconf) = &get_apache_virtual($d->{'dom'},
$d->{'web_sslport'});
return undef if (!$virt);
&lock_file($virt->{'file'});
&apache::save_directive("SSLCACertificateFile", $file ? [ $file ] : [ ],
$vconf,$conf);
&flush_file_lines($virt->{'file'});
&unlock_file($virt->{'file'});
®ister_post_action(\&restart_apache);
}
# 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 "".&html_escape($out)."";
}
elsif ($out !~ /subject=.*CN=/) {
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;
}
$done_feature_script{'ssl'} = 1;
1;