Handle hostnames with upper-case letters
[webmin.git] / cluster-webmin / upgrade.cgi
1 #!/usr/local/bin/perl
2 # upgrade.cgi
3 # Download webmin and upgrade all managed servers of compatible types
4
5 require './cluster-webmin-lib.pl';
6 &foreign_require("proc", "proc-lib.pl");
7 &foreign_require("webmin", "webmin-lib.pl");
8 &foreign_require("webmin", "gnupg-lib.pl");
9 &ReadParseMime();
10
11 &ui_print_unbuffered_header(undef, $text{'upgrade_title'}, "");
12
13 # Save this CGI from being killed by the upgrade
14 $SIG{'TERM'} = 'IGNORE';
15
16 if ($in{'source'} == 0) {
17         # from local file
18         &error_setup(&webmin::text('upgrade_err1', $in{'file'}));
19         $file = $in{'file'};
20         if (!(-r $file)) { &inst_error($webmin::text{'upgrade_efile'}); }
21         }
22 elsif ($in{'source'} == 1) {
23         # from uploaded file
24         &error_setup($webmin::text{'upgrade_err2'});
25         $file = &tempname();
26         $need_unlink = 1;
27         if ($no_upload) {
28                 &inst_error($webmin::text{'upgrade_ebrowser'});
29                 }
30         open(MOD, ">$file");
31         print MOD $in{'upload'};
32         close(MOD);
33         }
34 elsif ($in{'source'} == 2) {
35         # find latest version at www.webmin.com by looking at index page
36         &error_setup($webmin::text{'upgrade_err3'});
37         $file = &tempname();
38         &http_download($webmin::update_host, $webmin::update_port, '/', $file, \$error);
39         $error && &inst_error($error);
40         open(FILE, $file);
41         while(<FILE>) {
42                 if (/webmin-([0-9\.]+)\.tar\.gz/) {
43                         $site_version = $1;
44                         last;
45                         }
46                 }
47         close(FILE);
48         unlink($file);
49         if ($in{'mode'} eq 'rpm') {
50                 $progress_callback_url = "http://$webmin::update_host/download/rpm/webmin-$site_version-1.noarch.rpm";
51                 &http_download($webmin::update_host, $webmin::update_port,
52                   "/download/rpm/webmin-$site_version-1.noarch.rpm", $file,
53                   \$error, \&progress_callback);
54                 }
55         elsif ($in{'mode'} eq 'deb') {
56                 # Downloading Debian package
57                 $progress_callback_url = "http://$webmin::update_host/download/deb/webmin_${site_version}_all.deb";
58                 &http_download($webmin::update_host, $webmin::update_port,
59                   "/download/deb/webmin_${site_version}_all.deb", $file,
60                   \$error, \&progress_callback);
61                 }
62         else {
63                 $progress_callback_url = "http://$webmin::update_host/download/webmin-$site_version.tar.gz";
64                 &http_download($webmin::update_host, $webmin::update_port,
65                   "/download/webmin-$site_version.tar.gz", $file,
66                   \$error, \&progress_callback);
67                 }
68         $error && &inst_error($error);
69         $need_unlink = 1;
70         }
71 elsif ($in{'source'} == 5) {
72         # Download from some URL
73         &error_setup(&webmin::text('upgrade_err5', $in{'url'}));
74         $file = &tempname();
75         $progress_callback_url = $in{'url'};
76         if ($in{'url'} =~ /^(http|https):\/\/([^\/]+)(\/.*)$/) {
77                 $ssl = $1 eq 'https';
78                 $host = $2; $page = $3; $port = $ssl ? 443 : 80;
79                 if ($host =~ /^(.*):(\d+)$/) { $host = $1; $port = $2; }
80                 &http_download($host, $port, $page, $file, \$error,
81                                \&progress_callback, $ssl);
82                 }
83         elsif ($in{'url'} =~ /^ftp:\/\/([^\/]+)(:21)?\/(.*)$/) {
84                 $host = $1; $ffile = $3;
85                 &ftp_download($host, $ffile, $file,
86                               \$error, \&progress_callback);
87                 }
88         else { &inst_error($webmin::text{'upgrade_eurl'}); }
89         $need_unlink = 1;
90         $error && &inst_error($error);
91         }
92
93 # Import the signature for RPM
94 if (&has_command("rpm")) {
95         system("rpm --import $root_directory/webmin/jcameron-key.asc >/dev/null 2>&1");
96         }
97
98 # Work out what kind of file we have (RPM or tar.gz)
99 if (`rpm -qp $file 2>&1` =~ /(^|\n)webmin-(\d+\.\d+)/) {
100         # Looks like a webmin RPM
101         $mode = "rpm";
102         $version = $2;
103         }
104 elsif (`dpkg --info $file 2>&1` =~ /Package:\s+webmin/) {
105         # Looks like a Webmin Debian package
106         $mode = "deb";
107         `dpkg --info $file 2>&1` =~ /Version:\s+(\S+)/;
108         $version = $1;
109         }
110 else {
111         # Check if it is a webmin tar.gz file
112         open(TAR, "gunzip -c $file | tar tf - 2>&1 |");
113         while(<TAR>) {
114                 s/\r|\n//g;
115                 if (/^webmin-([0-9\.]+)\//) {
116                         $version = $1;
117                         }
118                 if (/^usermin-([0-9\.]+)\//) {
119                         $usermin_version = $1;
120                         }
121                 if (/^[^\/]+\/(\S+)$/) {
122                         $hasfile{$1}++;
123                         }
124                 if (/^(webmin-([0-9\.]+)\/([^\/]+))$/ && $3 ne ".") {
125                         # Found a top-level file, or *possibly* a directory
126                         # under some versions of tar. Keep it so we know which
127                         # files to extract.
128                         push(@topfiles, $_);
129                         }
130                 elsif (/^(webmin-[0-9\.]+\/([^\/]+))\// && $2 ne ".") {
131                         # Found a sub-directory, like webmin-1.xx/foo/
132                         # Keep this, so that we know which modules to extract.
133                         # Also keep the full directory like webmin-1.xx/foo
134                         # to avoid treating it as a file.
135                         $intar{$2}++;
136                         $tardir{$1}++;
137                         }
138                 }
139         close(TAR);
140         if ($usermin_version) {
141                 &inst_error(&webmin::text('upgrade_eusermin',$usermin_version));
142                 }
143         if (!$version) {
144                 if ($hasfile{'module.info'}) {
145                         &inst_error(&webmin::text('upgrade_emod', 'index.cgi'));
146                         }
147                 else {
148                         &inst_error($webmin::text{'upgrade_etar'});
149                         }
150                 }
151         $mode = "";
152         }
153
154 # Check the signature if possible and if requested
155 if ($in{'sig'}) {
156         # Check the package signature
157         ($ec, $emsg) = &webmin::gnupg_setup();
158         if (!$ec) {
159                 if ($mode eq 'rpm') {
160                         # Use rpm's gpg signature verification
161                         system("rpm --import $webmin::module_root_directory/jcameron-key.asc >/dev/null 2>&1");
162                         local $out = `rpm --checksig $file 2>&1`;
163                         if ($?) {
164                                 $ec = 3;
165                                 $emsg = &webmin::text('upgrade_echecksig',
166                                               "<pre>$out</pre>");
167                                 }
168                         }
169                 else {
170                         # Do a manual signature check
171                         if ($in{'source'} == 2) {
172                                 # Download the key for this tar.gz
173                                 local ($sigtemp, $sigerror);
174                                 &http_download($webmin::update_host, $webmin::update_port, "/download/sigs/webmin-$version.tar.gz-sig.asc", \$sigtemp, \$sigerror);
175                                 if ($sigerror) {
176                                         $ec = 4;
177                                         $emsg = &webmin::text(
178                                                 'upgrade_edownsig', $sigerror);
179                                         }
180                                 else {
181                                         local $data = `cat $file`;
182                                         local ($vc, $vmsg) =
183                                             &webmin::verify_data(
184                                                 $data, $sigtemp);
185                                         if ($vc > 1) {
186                                                 $ec = 3;
187                                                 $emsg = &webmin::text(
188                                                     "upgrade_everify$vc",
189                                                     &html_escape($vmsg));
190                                                 }
191                                         }
192                                 }
193                         else {
194                                 $emsg = $webmin::text{'upgrade_nosig'};
195                                 }
196                         }
197                 }
198
199         # Tell the user about any GnuPG error
200         if ($ec) {
201                 &inst_error($emsg);
202                 }
203         elsif ($emsg) {
204                 print "$emsg<p>\n";
205                 }
206         else {
207                 print "$webmin::text{'upgrade_sigok'}<p>\n";
208                 }
209         }
210 else {
211         print "$webmin::text{'upgrade_nocheck'}<p>\n";
212         }
213
214 # gunzip the file if needed
215 open(FILE, $file);
216 read(FILE, $two, 2);
217 close(FILE);
218 if ($two eq "\037\213") {
219         if (!&has_command("gunzip")) {
220                 &inst_error($webmin::text{'upgrade_egunzip'});
221                 }
222         $newfile = &tempname();
223         $out = `gunzip -c $file 2>&1 >$newfile`;
224         if ($?) {
225                 unlink($newfile);
226                 &inst_error(&webmin::text('upgrade_egzip', "<tt>$out</tt>"));
227                 }
228         unlink($file) if ($need_unlink);
229         $need_unlink = 1;
230         $file = $newfile;
231         }
232
233 # Setup error handler for down hosts
234 sub inst_error
235 {
236 $inst_error_msg = join("", @_);
237 }
238 &remote_error_setup(\&inst_error);
239
240 # Build list of selected hosts
241 @hosts = &list_webmin_hosts();
242 @servers = &list_servers();
243 if ($in{'server'} == -2) {
244         # Upgrade servers know to run older versions?
245         @hosts = grep { $_->{'version'} < $version } @hosts;
246         print "<b>",&text('upgrade_header3', $version),"</b><p>\n";
247         }
248 elsif ($in{'server'} =~ /^group_(.*)/) {
249         # Upgrade members of some group
250         local ($group) = grep { $_->{'name'} eq $1 }
251                               &servers::list_all_groups(\@servers);
252         @hosts = grep { local $hid = $_->{'id'};
253                         local ($s) = grep { $_->{'id'} == $hid } @servers;
254                         &indexof($s->{'host'}, @{$group->{'members'}}) >= 0 }
255                       @hosts;
256         print "<b>",&text('upgrade_header4', $group->{'name'}),"</b><p>\n";
257         }
258 elsif ($in{'server'} != -1) {
259         # Upgrade one host
260         @hosts = grep { $_->{'id'} == $in{'server'} } @hosts;
261         local ($s) = grep { $_->{'id'} == $hosts[0]->{'id'} } @servers;
262         print "<b>",&text('upgrade_header2', &server_name($s)),"</b><p>\n";
263         }
264 else {
265         # Upgrading every host
266         print "<p><b>",&text('upgrade_header'),"</b><p>\n";
267         }
268
269 # Run the install
270 $p = 0;
271 foreach $h (@hosts) {
272         local ($s) = grep { $_->{'id'} == $h->{'id'} } @servers;
273
274         local ($rh = "READ$p", $wh = "WRITE$p");
275         pipe($rh, $wh);
276         select($wh); $| = 1; select(STDOUT);
277         if (!fork()) {
278                 # Do the install in a subprocess
279                 close($rh);
280
281                 if (!$s->{'fast'} && $s->{'id'} != 0) {
282                         print $wh &serialise_variable($text{'upgrade_efast'});
283                         exit;
284                         }
285                 &remote_foreign_require($s->{'host'}, "webmin","webmin-lib.pl");
286                 if ($inst_error_msg) {
287                         # Failed to contact host ..
288                         print $wh &serialise_variable($inst_error_msg);
289                         exit;
290                         }
291
292                 # Check the remote host's version
293                 local $rver = &remote_foreign_call($s->{'host'}, "webmin",
294                                                    "get_webmin_version");
295                 if ($version == $rver) {
296                         print $wh &serialise_variable(
297                                 &webmin::text('upgrade_elatest', $version));
298                         exit;
299                         }
300                 elsif ($version <= $rver) {
301                         print $wh &serialise_variable(
302                                 &webmin::text('upgrade_eversion', $version));
303                         exit;
304                         }
305
306                 # Check the install type on the remote host
307                 local $rmode = &remote_eval($s->{'host'}, "webmin",
308                     "chop(\$m = `cat \$root_directory/install-type`); \$m");
309                 if ($rmode ne $mode) {
310                         print $wh &serialise_variable(
311                                 &text('upgrade_emode',
312                                       $text{'upgrade_mode_'.$rmode},
313                                       $text{'upgrade_mode_'.$mode}));
314                         exit;
315                         }
316
317                 # Get the file to the server somehow
318                 local $rfile;
319                 local $host_need_unlink = 1;
320                 if (!$s->{'id'}) {
321                         # This host, so we already have the file
322                         $rfile = $file;
323                         $host_need_unlink = 0;
324                         }
325                 elsif ($in{'source'} == 0) {
326                         # Is the file the same on remote? (like if we have NFS)
327                         local @st = stat($file);
328                         local $rst = &remote_eval($s->{'host'}, "webmin",
329                                                   "[ stat('$file') ]");
330                         local @rst = @$rst;
331                         if (@st && @rst && $st[7] == $rst[7] &&
332                             $st[9] == $rst[9]) {
333                                 # File is the same! No need to download
334                                 $rfile = $file;
335                                 $host_need_unlink = 0;
336                                 }
337                         else {
338                                 # Need to copy the file across :(
339                                 $rfile = &remote_write(
340                                         $s->{'host'}, $file);
341                                 }
342                         }
343                 else {
344                         # Need to copy the file across :(
345                         $rfile = &remote_write($s->{'host'}, $file);
346                         }
347
348                 # Do the install ..
349                 if ($mode eq "rpm") {
350                         # Can just run RPM command
351                         # XXX doesn't actually check output!
352                         &remote_eval($s->{'host'}, "webmin", "system(\"rpm --import \$root_directory/webmin/jcameron-key.asc >/dev/null 2>&1\")");
353                         ($out, $ex) = &remote_eval($s->{'host'}, "webmin", "\$out = `rpm -U --ignoreos --ignorearch '$rfile' >/dev/null 2>&1 </dev/null`; (\$out, \$?)");
354                         &remote_eval($s->{'host'}, "webmin", "unlink('$rfile')")
355                                 if ($host_need_unlink);
356                         if ($ex) {
357                                 print $wh &serialise_variable(
358                                         "<pre>$out</pre>");
359                                 exit;
360                                 }
361                         }
362                 elsif ($mode eq "deb") {
363                         # Can just run dpkg command
364                         ($out, $ex) = &remote_eval($s->{'host'}, "webmin", "\$out = `dpkg --install '$rfile' >/dev/null 2>&1 </dev/null`; (\$out, \$?)");
365                         &remote_eval($s->{'host'}, "webmin", "unlink('$rfile')")
366                                 if ($host_need_unlink);
367                         if ($ex) {
368                                 print $wh &serialise_variable(
369                                         "<pre>$out</pre>");
370                                 exit;
371                                 }
372                         }
373                 else {
374                         # Get the original install directory
375                         local $rdir = &remote_eval($s->{'host'}, "webmin",
376                                 "chop(\$d = `cat \$config_directory/install-dir 2>/dev/null`); \$d");
377                         if ($rdir) {
378                                 # Extract tar.gz in temporary location first
379                                 $extract = &remote_foreign_call($s->{'host'}, "webmin", "tempname");
380                                 &remote_eval($s->{'host'}, "webmin", "mkdir('$extract', 0755)");
381                                 }
382                         else {
383                                 # Extract next to original dir
384                                 $oldroot = &remote_eval($s->{'host'}, "webmin", "\$root_directory");
385                                 $extract = "$oldroot/..";
386                                 }
387
388                         if ($in{'only'}) {
389                                 # Extract only root files and modules that we
390                                 # already have
391                                 $topfiles = join(" ", map { quotemeta($_) }
392                                          grep { !$tardir{$_} } @topfiles);
393                                 local ($out, $ex) = &remote_eval($s->{'host'}, "webmin", "\$out = `cd '$extract' ; tar xf '$rfile' $topfiles 2>&1 >/dev/null`; (\$out, \$?)");
394                                 if ($ex) {
395                                         print $wh &serialise_variable(
396                                                 "<pre>$out</pre>");
397                                         exit;
398                                         }
399                                 @mods = grep { $intar{$_} } map { $_->{'dir'} }
400                                         &remote_foreign_call($s->{'host'},
401                                           "webmin", "get_all_module_infos", 1);
402                                                 opendir(DIR, $root_directory);
403                                 foreach $d (readdir(DIR)) {
404                                         next if ($d =~ /^\./);
405                                         local $p = "$root_directory/$d";
406                                         if (-d $p && !-r "$p/module.info" &&
407                                             $intar{$d}) {
408                                                 push(@mods, $d);
409                                                 }
410                                         }
411                                 closedir(DIR);
412                                 $mods = join(" ",
413                                          map { quotemeta("webmin-$version/$_") }
414                                          @mods);
415                                 local ($out, $ex) = &remote_eval($s->{'host'}, "webmin", "\$out = `cd '$extract' ; tar xf '$rfile' $mods 2>&1 >/dev/null`; (\$out, \$?)");
416                                 }
417                         else {
418                                 # Unpack the whole tar file
419                                 local ($out, $ex) = &remote_eval($s->{'host'}, "webmin", "\$out = `cd '$extract' ; tar xf '$rfile' 2>&1 >/dev/null`; (\$out, \$?)");
420                                 }
421                         if ($ex) {
422                                 print $wh &serialise_variable(
423                                         "<pre>$out</pre>");
424                                 exit;
425                                 }
426
427                         # Delete the original tar.gz
428                         &remote_eval($s->{'host'}, "webmin", "unlink('$rfile')")
429                                 if ($host_need_unlink);
430
431                         # Run setup.sh in the extracted directory
432                         $setup = $rdir ? "./setup.sh '$rdir'" : "./setup.sh";
433                         ($out, $ex) = &remote_eval($s->{'host'}, "webmin",
434                                 "\$SIG{'TERM'} = 'IGNORE';
435                                  \$ENV{'config_dir'} = \$config_directory;
436                                  \$ENV{'webmin_upgrade'} = 1;
437                                  \$ENV{'autothird'} = 1;
438                                  \$out = `(cd $extract/webmin-$version && $setup) </dev/null 2>&1 | tee /tmp/.webmin/webmin-setup.out`;
439                                  (\$out, \$?)");
440                         if ($ex || $out !~ /success|^0$/i) {
441                                 print $wh &serialise_variable(
442                                         "<pre>$out</pre>");
443                                 exit;
444                                 }
445                         if ($rdir) {
446                                 # Can delete the temporary source directory
447                                 &remote_eval($s->{'host'}, "webmin",
448                                              "system(\"rm -rf \'$extract\'\")");
449                                 }
450                         elsif ($in{'delete'}) {
451                                 # Can delete the old root directory
452                                 &remote_eval($s->{'host'}, "webmin",
453                                              "system(\"rm -rf \'$oldroot\'\")");
454                                 }
455                         }
456
457                 # Force an RPC re-connect to new version
458                 &remote_finished();
459                 &remote_foreign_require($s->{'host'}, "webmin","webmin-lib.pl");
460                 if ($inst_error_msg) {
461                         # Failed to contact host ..
462                         print $wh &serialise_variable(
463                                 &text('upgrade_ereconn', $inst_error_msg));
464                         exit;
465                         }
466                 &remote_foreign_require($s->{'host'}, "acl", "acl-lib.pl");
467
468                 # Update local version number and module lists
469                 $h->{'version'} = $version;
470                 local @mods = &remote_foreign_call($s->{'host'},
471                                 "webmin", "get_all_module_infos", 1);
472                 @mods = grep { !$_->{'clone'} } @mods;
473                 $h->{'modules'} = \@mods;
474                 local @themes = &remote_foreign_call($s->{'host'},
475                                  "webmin", "list_themes");
476                 $h->{'themes'} = \@themes;
477                 local @users = &remote_foreign_call($s->{'host'},
478                                 "acl", "list_users");
479                 $h->{'users'} = \@users;
480                 local @groups = &remote_foreign_call($s->{'host'},
481                                 "acl", "list_groups");
482                 $h->{'groups'} = \@groups;
483                 &save_webmin_host($h);
484
485                 print $wh &serialise_variable("");
486                 close($wh);
487                 exit;
488                 }
489         close($wh);
490         $p++;
491         }
492
493 # Get back all the results
494 $p = 0;
495 foreach $h (@hosts) {
496         local $rh = "READ$p";
497         local $line = <$rh>;
498         close($rh);
499         local $rv = &unserialise_variable($line);
500
501         local ($s) = grep { $_->{'id'} == $h->{'id'} } @servers;
502         local $d = &server_name($s);
503
504         if (!$line) {
505                 print &text('upgrade_failed', $d, "Unknown reason"),"<br>\n";
506                 }
507         elsif ($rv) {
508                 print &text('upgrade_failed', $d, $rv),"<br>\n";
509                 }
510         else {
511                 print &text('upgrade_ok',
512                             $text{'upgrade_mode_'.$mode}, $d),"<br>\n";
513                 }
514         $p++;
515         }
516 unlink($file) if ($need_unlink);
517 print "<p><b>$text{'upgrade_done'}</b><p>\n";
518
519 &remote_finished();
520 &ui_print_footer("", $text{'index_return'});
521
522 sub inst_error
523 {
524 unlink($file) if ($need_unlink);
525 print "<br><b>$whatfailed : $_[0]</b> <p>\n";
526 &ui_print_footer("", $text{'index_return'});
527 exit;
528 }
529