#!/usr/local/bin/perl # upgrade.cgi # Download webmin and upgrade all managed servers of compatible types require './cluster-webmin-lib.pl'; &foreign_require("proc", "proc-lib.pl"); &foreign_require("webmin", "webmin-lib.pl"); &foreign_require("webmin", "gnupg-lib.pl"); &ReadParseMime(); &ui_print_unbuffered_header(undef, $text{'upgrade_title'}, ""); # Save this CGI from being killed by the upgrade $SIG{'TERM'} = 'IGNORE'; if ($in{'source'} == 0) { # from local file &error_setup(&webmin::text('upgrade_err1', $in{'file'})); $file = $in{'file'}; if (!(-r $file)) { &inst_error($webmin::text{'upgrade_efile'}); } } elsif ($in{'source'} == 1) { # from uploaded file &error_setup($webmin::text{'upgrade_err2'}); $file = &tempname(); $need_unlink = 1; if ($no_upload) { &inst_error($webmin::text{'upgrade_ebrowser'}); } open(MOD, ">$file"); print MOD $in{'upload'}; close(MOD); } elsif ($in{'source'} == 2) { # find latest version at www.webmin.com by looking at index page &error_setup($webmin::text{'upgrade_err3'}); $file = &tempname(); &http_download($webmin::update_host, $webmin::update_port, '/', $file, \$error); $error && &inst_error($error); open(FILE, $file); while() { if (/webmin-([0-9\.]+)\.tar\.gz/) { $site_version = $1; last; } } close(FILE); unlink($file); if ($in{'mode'} eq 'rpm') { $progress_callback_url = "http://$webmin::update_host/download/rpm/webmin-$site_version-1.noarch.rpm"; &http_download($webmin::update_host, $webmin::update_port, "/download/rpm/webmin-$site_version-1.noarch.rpm", $file, \$error, \&progress_callback); } elsif ($in{'mode'} eq 'deb') { # Downloading Debian package $progress_callback_url = "http://$webmin::update_host/download/deb/webmin_${site_version}_all.deb"; &http_download($webmin::update_host, $webmin::update_port, "/download/deb/webmin_${site_version}_all.deb", $file, \$error, \&progress_callback); } else { $progress_callback_url = "http://$webmin::update_host/download/webmin-$site_version.tar.gz"; &http_download($webmin::update_host, $webmin::update_port, "/download/webmin-$site_version.tar.gz", $file, \$error, \&progress_callback); } $error && &inst_error($error); $need_unlink = 1; } elsif ($in{'source'} == 5) { # Download from some URL &error_setup(&webmin::text('upgrade_err5', $in{'url'})); $file = &tempname(); $progress_callback_url = $in{'url'}; if ($in{'url'} =~ /^(http|https):\/\/([^\/]+)(\/.*)$/) { $ssl = $1 eq 'https'; $host = $2; $page = $3; $port = $ssl ? 443 : 80; if ($host =~ /^(.*):(\d+)$/) { $host = $1; $port = $2; } &http_download($host, $port, $page, $file, \$error, \&progress_callback, $ssl); } elsif ($in{'url'} =~ /^ftp:\/\/([^\/]+)(:21)?\/(.*)$/) { $host = $1; $ffile = $3; &ftp_download($host, $ffile, $file, \$error, \&progress_callback); } else { &inst_error($webmin::text{'upgrade_eurl'}); } $need_unlink = 1; $error && &inst_error($error); } # Import the signature for RPM if (&has_command("rpm")) { system("rpm --import $root_directory/webmin/jcameron-key.asc >/dev/null 2>&1"); } # Work out what kind of file we have (RPM or tar.gz) if (`rpm -qp $file 2>&1` =~ /(^|\n)webmin-(\d+\.\d+)/) { # Looks like a webmin RPM $mode = "rpm"; $version = $2; } elsif (`dpkg --info $file 2>&1` =~ /Package:\s+webmin/) { # Looks like a Webmin Debian package $mode = "deb"; `dpkg --info $file 2>&1` =~ /Version:\s+(\S+)/; $version = $1; } else { # Check if it is a webmin tar.gz file open(TAR, "gunzip -c $file | tar tf - 2>&1 |"); while() { s/\r|\n//g; if (/^webmin-([0-9\.]+)\//) { $version = $1; } if (/^usermin-([0-9\.]+)\//) { $usermin_version = $1; } if (/^[^\/]+\/(\S+)$/) { $hasfile{$1}++; } if (/^(webmin-([0-9\.]+)\/([^\/]+))$/ && $3 ne ".") { # Found a top-level file, or *possibly* a directory # under some versions of tar. Keep it so we know which # files to extract. push(@topfiles, $_); } elsif (/^(webmin-[0-9\.]+\/([^\/]+))\// && $2 ne ".") { # Found a sub-directory, like webmin-1.xx/foo/ # Keep this, so that we know which modules to extract. # Also keep the full directory like webmin-1.xx/foo # to avoid treating it as a file. $intar{$2}++; $tardir{$1}++; } } close(TAR); if ($usermin_version) { &inst_error(&webmin::text('upgrade_eusermin',$usermin_version)); } if (!$version) { if ($hasfile{'module.info'}) { &inst_error(&webmin::text('upgrade_emod', 'index.cgi')); } else { &inst_error($webmin::text{'upgrade_etar'}); } } $mode = ""; } # Check the signature if possible and if requested if ($in{'sig'}) { # Check the package signature ($ec, $emsg) = &webmin::gnupg_setup(); if (!$ec) { if ($mode eq 'rpm') { # Use rpm's gpg signature verification system("rpm --import $webmin::module_root_directory/jcameron-key.asc >/dev/null 2>&1"); local $out = `rpm --checksig $file 2>&1`; if ($?) { $ec = 3; $emsg = &webmin::text('upgrade_echecksig', "
$out
"); } } else { # Do a manual signature check if ($in{'source'} == 2) { # Download the key for this tar.gz local ($sigtemp, $sigerror); &http_download($webmin::update_host, $webmin::update_port, "/download/sigs/webmin-$version.tar.gz-sig.asc", \$sigtemp, \$sigerror); if ($sigerror) { $ec = 4; $emsg = &webmin::text( 'upgrade_edownsig', $sigerror); } else { local $data = `cat $file`; local ($vc, $vmsg) = &webmin::verify_data( $data, $sigtemp); if ($vc > 1) { $ec = 3; $emsg = &webmin::text( "upgrade_everify$vc", &html_escape($vmsg)); } } } else { $emsg = $webmin::text{'upgrade_nosig'}; } } } # Tell the user about any GnuPG error if ($ec) { &inst_error($emsg); } elsif ($emsg) { print "$emsg

\n"; } else { print "$webmin::text{'upgrade_sigok'}

\n"; } } else { print "$webmin::text{'upgrade_nocheck'}

\n"; } # gunzip the file if needed open(FILE, $file); read(FILE, $two, 2); close(FILE); if ($two eq "\037\213") { if (!&has_command("gunzip")) { &inst_error($webmin::text{'upgrade_egunzip'}); } $newfile = &tempname(); $out = `gunzip -c $file 2>&1 >$newfile`; if ($?) { unlink($newfile); &inst_error(&webmin::text('upgrade_egzip', "$out")); } unlink($file) if ($need_unlink); $need_unlink = 1; $file = $newfile; } # Setup error handler for down hosts sub inst_error { $inst_error_msg = join("", @_); } &remote_error_setup(\&inst_error); # Build list of selected hosts @hosts = &list_webmin_hosts(); @servers = &list_servers(); if ($in{'server'} == -2) { # Upgrade servers know to run older versions? @hosts = grep { $_->{'version'} < $version } @hosts; print "",&text('upgrade_header3', $version),"

\n"; } elsif ($in{'server'} =~ /^group_(.*)/) { # Upgrade members of some group local ($group) = grep { $_->{'name'} eq $1 } &servers::list_all_groups(\@servers); @hosts = grep { local $hid = $_->{'id'}; local ($s) = grep { $_->{'id'} == $hid } @servers; &indexof($s->{'host'}, @{$group->{'members'}}) >= 0 } @hosts; print "",&text('upgrade_header4', $group->{'name'}),"

\n"; } elsif ($in{'server'} != -1) { # Upgrade one host @hosts = grep { $_->{'id'} == $in{'server'} } @hosts; local ($s) = grep { $_->{'id'} == $hosts[0]->{'id'} } @servers; print "",&text('upgrade_header2', &server_name($s)),"

\n"; } else { # Upgrading every host print "

",&text('upgrade_header'),"

\n"; } # Run the install $p = 0; foreach $h (@hosts) { local ($s) = grep { $_->{'id'} == $h->{'id'} } @servers; local ($rh = "READ$p", $wh = "WRITE$p"); pipe($rh, $wh); select($wh); $| = 1; select(STDOUT); if (!fork()) { # Do the install in a subprocess close($rh); if (!$s->{'fast'} && $s->{'id'} != 0) { print $wh &serialise_variable($text{'upgrade_efast'}); exit; } &remote_foreign_require($s->{'host'}, "webmin","webmin-lib.pl"); if ($inst_error_msg) { # Failed to contact host .. print $wh &serialise_variable($inst_error_msg); exit; } # Check the remote host's version local $rver = &remote_foreign_call($s->{'host'}, "webmin", "get_webmin_version"); if ($version == $rver) { print $wh &serialise_variable( &webmin::text('upgrade_elatest', $version)); exit; } elsif ($version <= $rver) { print $wh &serialise_variable( &webmin::text('upgrade_eversion', $version)); exit; } # Check the install type on the remote host local $rmode = &remote_eval($s->{'host'}, "webmin", "chop(\$m = `cat \$root_directory/install-type`); \$m"); if ($rmode ne $mode) { print $wh &serialise_variable( &text('upgrade_emode', $text{'upgrade_mode_'.$rmode}, $text{'upgrade_mode_'.$mode})); exit; } # Get the file to the server somehow local $rfile; local $host_need_unlink = 1; if (!$s->{'id'}) { # This host, so we already have the file $rfile = $file; $host_need_unlink = 0; } elsif ($in{'source'} == 0) { # Is the file the same on remote? (like if we have NFS) local @st = stat($file); local $rst = &remote_eval($s->{'host'}, "webmin", "[ stat('$file') ]"); local @rst = @$rst; if (@st && @rst && $st[7] == $rst[7] && $st[9] == $rst[9]) { # File is the same! No need to download $rfile = $file; $host_need_unlink = 0; } else { # Need to copy the file across :( $rfile = &remote_write( $s->{'host'}, $file); } } else { # Need to copy the file across :( $rfile = &remote_write($s->{'host'}, $file); } # Do the install .. if ($mode eq "rpm") { # Can just run RPM command # XXX doesn't actually check output! &remote_eval($s->{'host'}, "webmin", "system(\"rpm --import \$root_directory/webmin/jcameron-key.asc >/dev/null 2>&1\")"); ($out, $ex) = &remote_eval($s->{'host'}, "webmin", "\$out = `rpm -U --ignoreos --ignorearch '$rfile' >/dev/null 2>&1 {'host'}, "webmin", "unlink('$rfile')") if ($host_need_unlink); if ($ex) { print $wh &serialise_variable( "

$out
"); exit; } } elsif ($mode eq "deb") { # Can just run dpkg command ($out, $ex) = &remote_eval($s->{'host'}, "webmin", "\$out = `dpkg --install '$rfile' >/dev/null 2>&1 {'host'}, "webmin", "unlink('$rfile')") if ($host_need_unlink); if ($ex) { print $wh &serialise_variable( "
$out
"); exit; } } else { # Get the original install directory local $rdir = &remote_eval($s->{'host'}, "webmin", "chop(\$d = `cat \$config_directory/install-dir 2>/dev/null`); \$d"); if ($rdir) { # Extract tar.gz in temporary location first $extract = &remote_foreign_call($s->{'host'}, "webmin", "tempname"); &remote_eval($s->{'host'}, "webmin", "mkdir('$extract', 0755)"); } else { # Extract next to original dir $oldroot = &remote_eval($s->{'host'}, "webmin", "\$root_directory"); $extract = "$oldroot/.."; } if ($in{'only'}) { # Extract only root files and modules that we # already have $topfiles = join(" ", map { quotemeta($_) } grep { !$tardir{$_} } @topfiles); local ($out, $ex) = &remote_eval($s->{'host'}, "webmin", "\$out = `cd '$extract' ; tar xf '$rfile' $topfiles 2>&1 >/dev/null`; (\$out, \$?)"); if ($ex) { print $wh &serialise_variable( "
$out
"); exit; } @mods = grep { $intar{$_} } map { $_->{'dir'} } &remote_foreign_call($s->{'host'}, "webmin", "get_all_module_infos", 1); opendir(DIR, $root_directory); foreach $d (readdir(DIR)) { next if ($d =~ /^\./); local $p = "$root_directory/$d"; if (-d $p && !-r "$p/module.info" && $intar{$d}) { push(@mods, $d); } } closedir(DIR); $mods = join(" ", map { quotemeta("webmin-$version/$_") } @mods); local ($out, $ex) = &remote_eval($s->{'host'}, "webmin", "\$out = `cd '$extract' ; tar xf '$rfile' $mods 2>&1 >/dev/null`; (\$out, \$?)"); } else { # Unpack the whole tar file local ($out, $ex) = &remote_eval($s->{'host'}, "webmin", "\$out = `cd '$extract' ; tar xf '$rfile' 2>&1 >/dev/null`; (\$out, \$?)"); } if ($ex) { print $wh &serialise_variable( "
$out
"); exit; } # Delete the original tar.gz &remote_eval($s->{'host'}, "webmin", "unlink('$rfile')") if ($host_need_unlink); # Run setup.sh in the extracted directory $setup = $rdir ? "./setup.sh '$rdir'" : "./setup.sh"; ($out, $ex) = &remote_eval($s->{'host'}, "webmin", "\$SIG{'TERM'} = 'IGNORE'; \$ENV{'config_dir'} = \$config_directory; \$ENV{'webmin_upgrade'} = 1; \$ENV{'autothird'} = 1; \$out = `(cd $extract/webmin-$version && $setup) &1 | tee /tmp/.webmin/webmin-setup.out`; (\$out, \$?)"); if ($ex || $out !~ /success|^0$/i) { print $wh &serialise_variable( "
$out
"); exit; } if ($rdir) { # Can delete the temporary source directory &remote_eval($s->{'host'}, "webmin", "system(\"rm -rf \'$extract\'\")"); } elsif ($in{'delete'}) { # Can delete the old root directory &remote_eval($s->{'host'}, "webmin", "system(\"rm -rf \'$oldroot\'\")"); } } # Force an RPC re-connect to new version &remote_finished(); &remote_foreign_require($s->{'host'}, "webmin","webmin-lib.pl"); if ($inst_error_msg) { # Failed to contact host .. print $wh &serialise_variable( &text('upgrade_ereconn', $inst_error_msg)); exit; } &remote_foreign_require($s->{'host'}, "acl", "acl-lib.pl"); # Update local version number and module lists $h->{'version'} = $version; local @mods = &remote_foreign_call($s->{'host'}, "webmin", "get_all_module_infos", 1); @mods = grep { !$_->{'clone'} } @mods; $h->{'modules'} = \@mods; local @themes = &remote_foreign_call($s->{'host'}, "webmin", "list_themes"); $h->{'themes'} = \@themes; local @users = &remote_foreign_call($s->{'host'}, "acl", "list_users"); $h->{'users'} = \@users; local @groups = &remote_foreign_call($s->{'host'}, "acl", "list_groups"); $h->{'groups'} = \@groups; &save_webmin_host($h); print $wh &serialise_variable(""); close($wh); exit; } close($wh); $p++; } # Get back all the results $p = 0; foreach $h (@hosts) { local $rh = "READ$p"; local $line = <$rh>; close($rh); local $rv = &unserialise_variable($line); local ($s) = grep { $_->{'id'} == $h->{'id'} } @servers; local $d = &server_name($s); if (!$line) { print &text('upgrade_failed', $d, "Unknown reason"),"
\n"; } elsif ($rv) { print &text('upgrade_failed', $d, $rv),"
\n"; } else { print &text('upgrade_ok', $text{'upgrade_mode_'.$mode}, $d),"
\n"; } $p++; } unlink($file) if ($need_unlink); print "

$text{'upgrade_done'}

\n"; &remote_finished(); &ui_print_footer("", $text{'index_return'}); sub inst_error { unlink($file) if ($need_unlink); print "
$whatfailed : $_[0]

\n"; &ui_print_footer("", $text{'index_return'}); exit; }