1#!/usr/local/bin/perl
2# Request a new SSL cert using Let's Encrypt
3
4use strict;
5use warnings;
6
7require "./webmin-lib.pl";
8our %text;
9our %miniserv;
10our %in;
11our $config_directory;
12our %config;
13our $module_name;
14&error_setup($text{'letsencrypt_err'});
15
16# Re-check if let's encrypt is available
17my $err = &check_letsencrypt();
18&error($err) if ($err);
19
20# Validate inputs
21&ReadParse();
22my @doms = split(/\s+/, $in{'dom'});
23foreach my $dom (@doms) {
24	$dom =~ /^(\*\.)?[a-z0-9\-\.\_]+$/i || &error($text{'letsencrypt_edom'});
25	}
26$in{'renew_def'} || $in{'renew'} =~ /^[1-9][0-9]*$/ ||
27	&error($text{'letsencrypt_erenew'});
28$in{'size_def'} || $in{'size'} =~ /^\d+$/ ||
29	&error($text{'newkey_esize'});
30my $size = $in{'size_def'} ? undef : $in{'size'};
31my $webroot;
32my $mode = "web";
33if ($in{'webroot_mode'} == 3) {
34	# Validation via DNS
35	$mode = "dns";
36	}
37elsif ($in{'webroot_mode'} == 2) {
38	# Some directory
39	$in{'webroot'} =~ /^\/\S+/ && -d $in{'webroot'} ||
40		&error($text{'letsencrypt_ewebroot'});
41	$webroot = $in{'webroot'};
42	}
43else {
44	# Apache domain
45	&foreign_require("apache");
46	my $conf = &apache::get_config();
47	foreach my $virt (&apache::find_directive_struct(
48				"VirtualHost", $conf)) {
49		my $sn = &apache::find_directive(
50			"ServerName", $virt->{'members'});
51		my $match = 0;
52		if ($in{'webroot_mode'} == 0 && $sn eq $doms[0]) {
53			# Based on domain name
54			$match = 1;
55			}
56		elsif ($in{'webroot_mode'} == 1 && $sn eq $in{'vhost'}) {
57			# Specifically selected domain
58			$match = 1;
59			}
60		if ($match) {
61			# Get document root
62			$webroot = &apache::find_directive(
63				"DocumentRoot", $virt->{'members'}, 1);
64			$webroot || &error(&text('letsencrypt_edroot', $sn));
65			last;
66			}
67		}
68	$webroot || &error(&text('letsencrypt_evhost', $doms[0]));
69	}
70
71if ($in{'save'}) {
72	# Just update renewal
73	&save_renewal_only(\@doms, $webroot, $mode);
74	&redirect("edit_ssl.cgi");
75	}
76else {
77	# Request the cert
78	&ui_print_unbuffered_header(undef, $text{'letsencrypt_title'}, "");
79
80	print &text($mode eq 'dns' ? 'letsencrypt_doingdns'
81				   : 'letsencrypt_doing',
82		    "<tt>".&html_escape(join(", ", @doms))."</tt>",
83		    "<tt>".&html_escape($webroot)."</tt>"),"<p>\n";
84	my ($ok, $cert, $key, $chain) = &request_letsencrypt_cert(\@doms, $webroot, undef, $size, $mode, $in{'staging'});
85	if (!$ok) {
86		print &text('letsencrypt_failed', $cert),"<p>\n";
87		}
88	else {
89		# Worked, now copy to Webmin
90		print $text{'letsencrypt_done'},"<p>\n";
91
92		if ($in{'use'}) {
93			# Copy cert, key and chain to Webmin
94			print $text{'letsencrypt_webmin'},"<p>\n";
95			&lock_file($ENV{'MINISERV_CONFIG'});
96			&get_miniserv_config(\%miniserv);
97
98			$miniserv{'keyfile'} = $config_directory.
99					       "/letsencrypt-key.pem";
100			&lock_file($miniserv{'keyfile'});
101			&copy_source_dest($key, $miniserv{'keyfile'}, 1);
102			&unlock_file($miniserv{'keyfile'});
103
104			$miniserv{'certfile'} = $config_directory.
105						"/letsencrypt-cert.pem";
106			&lock_file($miniserv{'certfile'});
107			&copy_source_dest($cert, $miniserv{'certfile'}, 1);
108			&unlock_file($miniserv{'certfile'});
109
110			if ($chain) {
111				$miniserv{'extracas'} = $config_directory.
112							"/letsencrypt-ca.pem";
113				&lock_file($miniserv{'extracas'});
114				&copy_source_dest($chain, $miniserv{'extracas'}, 1);
115				&unlock_file($miniserv{'extracas'});
116				}
117			else {
118				delete($miniserv{'extracas'});
119				}
120			&put_miniserv_config(\%miniserv);
121			&unlock_file($ENV{'MINISERV_CONFIG'});
122
123			&save_renewal_only(\@doms, $webroot, $mode);
124
125			&webmin_log("letsencrypt");
126			&restart_miniserv(1);
127			print $text{'letsencrypt_wdone'},"<p>\n";
128			}
129		else {
130			# Just tell the user
131			print $text{'letsencrypt_show'},"<p>\n";
132			my @grid = ( $text{'letsencrypt_cert'}, $cert,
133				     $text{'letsencrypt_key'}, $key );
134			if ($chain) {
135				push(@grid, $text{'letsencrypt_chain'}, $chain);
136				}
137			print &ui_grid_table(\@grid, 2);
138			}
139		}
140
141	&ui_print_footer("", $text{'index_return'});
142	}
143
144# save_renewal_only(&doms, webroot, mode)
145# Save for future renewals
146sub save_renewal_only
147{
148my ($doms, $webroot, $mode) = @_;
149$config{'letsencrypt_doms'} = join(" ", @$doms);
150$config{'letsencrypt_webroot'} = $webroot;
151$config{'letsencrypt_mode'} = $mode;
152$config{'letsencrypt_size'} = $size;
153&save_module_config();
154if (&foreign_check("webmincron")) {
155	my $job = &find_letsencrypt_cron_job();
156	if ($in{'renew_def'}) {
157		&webmincron::delete_webmin_cron($job) if ($job);
158		}
159	else {
160		my @tm = localtime(time() - 60);
161		$job ||= { 'module' => $module_name,
162			   'func' => 'renew_letsencrypt_cert' };
163		$job->{'mins'} ||= $tm[1];
164		$job->{'hours'} ||= $tm[2];
165		$job->{'days'} ||= $tm[3];
166		$job->{'months'} = '*/'.$in{'renew'};
167		$job->{'weekdays'} = '*';
168		&webmincron::create_webmin_cron($job);
169		}
170	}
171}
172