Handle hostnames with upper-case letters
[webmin.git] / status / sslcert-monitor.pl
1 # Check the status of a SSL cert on a remote server
2
3 sub get_sslcert_status
4 {
5 local $up = 0;
6 local $desc;
7 local $certfile;
8 local ($host, $port, $page, $ssl);
9
10 if ($_[0]->{'url'}) {
11         # Parse the URL and connect
12         ($host, $port, $page, $ssl) = &parse_http_url($_[0]->{'url'});
13
14         # Run the openssl command to connect
15         local $cmd = "openssl s_client -host ".quotemeta($host).
16                      " -port ".quotemeta($port)." </dev/null 2>&1";
17         local $out = &backquote_with_timeout($cmd, 10);
18         if ($?) {
19                 # Connection failed
20                 return { 'up' => -1 };
21                 }
22
23         # Extract the cert part and save
24         $certfile = &transname();
25         if ($out =~ /(-----BEGIN CERTIFICATE-----\n(.*\n)+-----END CERTIFICATE-----\n)/) {
26                 local $cert = $1;
27                 &open_tempfile(CERT, ">$certfile", 0, 1);
28                 &print_tempfile(CERT, $cert);
29                 &close_tempfile(CERT);
30                 }
31         else {
32                 # No cert?
33                 return { 'up' => 0,
34                          'desc' => $text{'sslcert_ecert'} };
35                 }
36         }
37 else {
38         # Cert is already in a file
39         $certfile = $_[0]->{'file'};
40         }
41
42 # Get end date with openssl x509 -in cert.pem -inform PEM -text -noout -enddate 
43 local $info = &backquote_command("openssl x509 -in ".quotemeta($certfile).
44                                  " -inform PEM -text -noout -enddate ".
45                                  " </dev/null 2>&1");
46
47 # Check dates
48 &foreign_require("mailboxes");
49 local ($start, $end);
50 if ($info =~ /Not\s*Before\s*:\s*(.*)/i) {
51         $start = &mailboxes::parse_mail_date("$1");
52         }
53 if ($info =~ /Not\s+After\s*:\s*(.*)/i) {
54         $end = &mailboxes::parse_mail_date("$1");
55         }
56 local $now = time();
57 if ($start && $now < $start) {
58         # Too new?!
59         $desc = &text('sslcert_estart', &make_date($start));
60         }
61 elsif ($end && $now > $end-$_[0]->{'days'}*24*60*60) {
62         # Too old
63         $desc = &text('sslcert_eend', &make_date($end));
64         }
65 elsif ($_[0]->{'mismatch'} && $_[0]->{'url'} &&
66        $info =~ /Subject:.*CN=([a-z0-9\.\-\_\*]+)/i) {
67         # Check hostname
68         local $cn = $1;
69         local $match = $1;
70         $match =~ s/\*/\.\*/g;  # Make perl RE
71         local @matches = ( $match );
72         if ($info =~ /Subject\s+Alternative\s+Name.*\r?\n\s*(.*)\n/) {
73                 # Add UCC alternate names
74                 local $alts = $1;
75                 $alts =~ s/^\s+//;
76                 $alts =~ s/\s+$//;
77                 foreach my $a (split(/[, ]+/, $alts)) {
78                         if ($a =~ /^DNS:(\S+)/) {
79                                 $match = $1;
80                                 $match =~ s/\*/\.\*/g;  # Make perl RE
81                                 push(@matches, $match);
82                                 }
83                         }
84                 }
85         local $ok = 0;
86         foreach $match (@matches) {
87                 $ok++ if ($host =~ /^$match$/i);
88                 }
89         if (!$ok) {
90                 $desc = &text('sslcert_ematch', "<tt>$host</tt>",
91                               "<tt>$cn</tt>");
92                 }
93         }
94
95 if (!$desc) {
96         # All OK!
97         $desc = &text('sslcert_left', int(($end-$now)/(24*60*60)));
98         $up = 1;
99         }
100
101 return { 'up' => $up, 'desc' => $desc };
102 }
103
104 sub show_sslcert_dialog
105 {
106 # URL or file to check
107 print &ui_table_row($text{'sslcert_src'},
108         &ui_radio_table("src", $_[0]->{'file'} ? 1 : 0,
109                 [ [ 0, $text{'sslcert_url'},
110                     &ui_textbox("url", $_[0]->{'url'}, 50) ],
111                   [ 1, $text{'sslcert_file'},
112                     &ui_textbox("file", $_[0]->{'file'}, 50)." ".
113                     &file_chooser_button("file") ] ]), 3);
114
115 # Days before expiry to warn
116 print &ui_table_row($text{'sslcert_days'},
117         &ui_opt_textbox("days", $_[0]->{'days'}, 5, $text{'sslcert_when'}));
118
119 # Warn about mismatch
120 print &ui_table_row($text{'sslcert_mismatch'},
121         &ui_yesno_radio("mismatch", $_[0]->{'mismatch'}));
122 }
123
124 sub parse_sslcert_dialog
125 {
126 &has_command("openssl") || &error($text{'sslcert_eopenssl'});
127
128 if ($in{'src'} == 0) {
129         # Parse URL
130         $in{'url'} =~ /^https:\/\/(\S+)$/ || &error($text{'sslcert_eurl'});
131         $_[0]->{'url'} = $in{'url'};
132         delete($_[0]->{'file'});
133         }
134 else {
135         # Parse file
136         $in{'file'} =~ /^\// && -r $in{'file'} ||
137                 &error($text{'sslcert_efile'});
138         $_[0]->{'file'} = $in{'file'};
139         delete($_[0]->{'url'});
140         }
141
142 # Parse number of days
143 if ($in{'days_def'}) {
144         delete($_[0]->{'days'});
145         }
146 else {
147         $in{'days'} =~ /^[1-9]\d*$/ || &error($text{'sslcert_edays'});
148         $_[0]->{'days'} = $in{'days'};
149         }
150
151 # Check hostname
152 $_[0]->{'mismatch'} = $in{'mismatch'};
153 if ($in{'mismatch'} && $in{'src'}) {
154         &error($text{'sslcert_emismatch'});
155         }
156 }
157