Handle hostnames with upper-case letters
[webmin.git] / cluster-webmin / install.cgi
1 #!/usr/local/bin/perl
2 # install.cgi
3 # Download and install webmin module or theme on multiple hosts
4
5 require './cluster-webmin-lib.pl';
6 if ($ENV{REQUEST_METHOD} eq "POST") { &ReadParseMime(); }
7 else { &ReadParse(); $no_upload = 1; }
8 &error_setup($text{'install_err'});
9
10 if ($in{source} == 2) {
11         &ui_print_unbuffered_header(undef, $text{'install_title'}, "");
12         }
13 else {
14         &ui_print_header(undef, $text{'install_title'}, "");
15         }
16
17 if ($in{source} == 0) {
18         # installing from local file (or maybe directory)
19         if (!$in{'local'})
20                 { &download_error($text{'install_elocal'}); }
21         if (!-r $in{'local'})
22                 { &download_error(&text('install_elocal2', $in{'local'})); }
23         $source = $in{'local'};
24         $pfile = $in{'local'};
25         $need_unlink = 0;
26         }
27 elsif ($in{source} == 1) {
28         # installing from upload .. store file in temp location
29         if ($no_upload) {
30                 &download_error($text{'install_eupload'});
31                 }
32         $in{'upload_filename'} =~ /([^\/\\]+$)/;
33         $pfile = &tempname("$1");
34         &open_tempfile(PFILE, "> $pfile");
35         &print_tempfile(PFILE, $in{'upload'});
36         &close_tempfile(PFILE);
37         $source = $in{'upload_filename'};
38         $need_unlink = 1;
39         }
40 elsif ($in{source} == 2) {
41         # installing from URL.. store downloaded file in temp location
42         $in{'url'} =~ /\/([^\/]+)\/*$/;
43         $pfile = &tempname("$1");
44         $progress_callback_url = $in{'url'};
45         if ($in{'url'} =~ /^(http|https):\/\/([^\/]+)(\/.*)$/) {
46                 # Make a HTTP request
47                 $ssl = $1 eq 'https';
48                 $host = $2; $page = $3; $port = $ssl ? 443 : 80;
49                 if ($host =~ /^(.*):(\d+)$/) { $host = $1; $port = $2; }
50                 &http_download($host, $port, $page, $pfile, \$error,
51                                \&progress_callback, $ssl);
52                 }
53         elsif ($in{'url'} =~ /^ftp:\/\/([^\/]+)(:21)?(\/.*)$/) {
54                 $host = $1; $file = $3;
55                 &ftp_download($host, $file, $pfile, \$error,
56                               \&progress_callback);
57                 }
58         else { &download_error(&text('install_eurl', $in{'url'})); }
59         &download_error($error) if ($error);
60         $source = $in{'url'};
61         $need_unlink = 1;
62         }
63 $grant = $in{'grant'} ? undef : [ split(/\s+/, $in{'grantto'}) ];
64
65 # Check validity
66 open(MFILE, $pfile);
67 read(MFILE, $two, 2);
68 close(MFILE);
69 if ($two eq "\037\235") {
70         # Unix compressed
71         &has_command("uncompress") ||
72                 &download_error(&text('install_ecomp', "<tt>uncompress</tt>"));
73         $cmd = "uncompress -c '$pfile' | tar tf -";
74         }
75 elsif ($two eq "\037\213") {
76         # Gzipped
77         &has_command("gunzip") || 
78                 &download_error(&text('install_egzip', "<tt>gunzip</tt>"));
79         $cmd = "gunzip -c '$pfile' | tar tf -";
80         }
81 else {
82         # Just a tar file
83         $cmd = "tar tf '$pfile'";
84         }
85 $tar = `$cmd 2>&1`;
86 $? && &download_error(&text('install_ecmd', "<tt>$tar</tt>"));
87 foreach $f (split(/\n/, $tar)) {
88         if ($f =~ /^\.\/([^\/]+)\/(.*)$/ || $f =~ /^([^\/]+)\/(.*)$/) {
89                 $mods{$1}++;
90                 $hasfile{$1,$2}++;
91                 }
92         }
93 foreach $m (keys %mods) {
94         $hasfile{$m,"module.info"} || $hasfile{$m,"theme.info"} ||
95                 &download_error(&text('install_einfo', "<tt>$m</tt>"));
96         }
97 if (!%mods) {
98         &download_error($text{'install_enone'});
99         }
100
101 # Get the version numbers
102 $tempdir = &transname();
103 mkdir($tempdir, 0755);
104 foreach $m (keys %mods) {
105         local $xcmd = $cmd;
106         $xcmd =~ s/tf/xf/g;
107         system("cd $tempdir ; $xcmd $m/module.info ./$m/module.info >/dev/null 2>&1");
108         local %minfo;
109         &read_file("$tempdir/$m/module.info", \%minfo);
110         $modver{$m} = $minfo{'version'};
111         }
112
113 # Setup error handler for down hosts
114 sub inst_error
115 {
116 $inst_error_msg = join("", @_);
117 }
118 &remote_error_setup(\&inst_error);
119
120 # Work out which hosts have the module, and which to install on
121 @hosts = &list_webmin_hosts();
122 @servers = &list_servers();
123 foreach $h (@hosts) {
124         local $gotall = 1;
125         foreach $m (keys %mods) {
126                 local ($alr) = grep { $_->{'dir'} eq $m }
127                                 (@{$h->{'modules'}}, @{$h->{'themes'}});
128                 $gotall = 0 if (!$alr ||
129                                 (defined($modver{$m}) &&
130                                  $modver{$m} > $alr->{'version'}));
131                 }
132         push(@gothosts, $h) if ($gotall);
133         }
134 @hosts = &create_on_parse("install_header", \@gothosts,
135                  join(" ", keys %mods));
136
137 # Run the install
138 $p = 0;
139 foreach $h (@hosts) {
140         local ($s) = grep { $_->{'id'} == $h->{'id'} } @servers;
141
142         local ($rh = "READ$p", $wh = "WRITE$p");
143         pipe($rh, $wh);
144         select($wh); $| = 1; select(STDOUT);
145         if (!fork()) {
146                 # Do the install in a subprocess
147                 close($rh);
148
149                 &remote_foreign_require($s->{'host'}, "webmin",
150                                         "webmin-lib.pl");
151                 if ($inst_error_msg) {
152                         # Failed to contact host ..
153                         print $wh &serialise_variable($inst_error_msg);
154                         exit;
155                         }
156                 &remote_foreign_require($s->{'host'}, "acl", "acl-lib.pl");
157                 local $rfile;
158                 local $host_need_unlink = 1;
159                 if (!$s->{'id'}) {
160                         # This host, so we already have the file
161                         $rfile = $pfile;
162                         $host_need_unlink = 0;
163                         }
164                 elsif ($in{'source'} == 0) {
165                         # Is the file the same on remote? (like if we have NFS)
166                         local @st = stat($pfile);
167                         local $rst = &remote_eval($s->{'host'}, "webmin",
168                                                   "[ stat('$pfile') ]");
169                         local @rst = @$rst;
170                         if (@st && @rst && $st[7] == $rst[7] &&
171                             $st[9] == $rst[9]) {
172                                 # File is the same! No need to download
173                                 $rfile = $pfile;
174                                 $host_need_unlink = 0;
175                                 }
176                         else {
177                                 # Need to copy the file across :(
178                                 $rfile = &remote_write(
179                                         $s->{'host'}, $pfile);
180                                 }
181                         }
182                 elsif ($in{'source'} == 2 && $in{'down'}) {
183                         # Ask the remote server to download the file
184                         $rfile = &remote_foreign_call($s->{'host'}, "webmin",
185                                                       "tempname");
186                         if ($in{'url'} =~ /^ftp/) {
187                                 &remote_foreign_call($s->{'host'}, "webmin",
188                                     "ftp_download", $host, $file,
189                                     $rfile);
190                                 }
191                         else {
192                                 &remote_foreign_call($s->{'host'}, "webmin",
193                                     "http_download", $host, $port,
194                                     $page, $rfile, undef, undef, $ssl);
195                                 }
196                         }
197                 else {
198                         # Need to copy the file across :(
199                         $rfile = &remote_write($s->{'host'}, $pfile);
200                         }
201
202                 # Do the install ..
203                 local $rv = &remote_foreign_call($s->{'host'},
204                                 "webmin", "install_webmin_module", $rfile,
205                                 $host_need_unlink, $in{'nodeps'}, $grant);
206                 if (ref($rv)) {
207                         # It worked .. get all the module infos
208                         local @mods;
209                         foreach $m (@{$rv->[1]}) {
210                                 $m =~ s/^.*\///;
211                                 local %info = &remote_foreign_call(
212                                                 $s->{'host'}, "webmin",
213                                                 "get_module_info", $m);
214                                 if (!%info) {
215                                         # Must have been a theme
216                                         %info = &remote_foreign_call(
217                                                         $s->{'host'}, "webmin",
218                                                         "get_theme_info", $m);
219                                         $info{'theme'} = 1;
220                                         }
221                                 push(@mods, \%info);
222                                 }
223                         print $wh &serialise_variable(\@mods);
224
225                         # Re-request all users, groups, modules and themes
226                         # from the server
227                         $h->{'modules'} = [ grep { !$_->{'clone'} }
228                                 &remote_foreign_call($s->{'host'},
229                                         "webmin", "get_all_module_infos", 1) ];
230                         $h->{'themes'} = [ &remote_foreign_call($s->{'host'},
231                                                 "webmin", "list_themes") ];
232                         $h->{'users'} = [ &remote_foreign_call($s->{'host'},
233                                                 "acl", "list_users") ];
234                         $h->{'groups'} = [ &remote_foreign_call($s->{'host'},
235                                                 "acl", "list_groups") ];
236                         &save_webmin_host($h);
237                         }
238                 else {
239                         print $wh &serialise_variable($rv);
240                         }
241                 close($wh);
242                 exit;
243                 }
244         close($wh);
245         $p++;
246         }
247
248 # Get back all the results
249 $p = 0;
250 foreach $h (@hosts) {
251         local $rh = "READ$p";
252         local $line = <$rh>;
253         close($rh);
254         local $rv = &unserialise_variable($line);
255
256         local ($s) = grep { $_->{'id'} == $h->{'id'} } @servers;
257         local $d = &server_name($s);
258
259         if (!$line) {
260                 print &text('do_failed', $d, "Unknown reason"),"<br>\n";
261                 }
262         elsif (!ref($rv)) {
263                 print &text('do_failed', $d, $rv),"<br>\n";
264                 }
265         else {
266                 # List the modules installed, and add to lists
267                 foreach $m (@$rv) {
268                         if ($m->{'theme'}) {
269                                 print &text('do_success_theme', $d, "<b>$m->{'desc'}</b>"), "<br>\n";
270                                 }
271                         else {
272                                 print &text('do_success_mod', $d, "<b>$m->{'desc'}</b>"), "<br>\n";
273                                 }
274                         }
275                 }
276         $p++;
277         }
278 unlink($pfile) if ($need_unlink);
279 print "<p><b>$text{'do_done'}</b><p>\n";
280
281 &remote_finished();
282 &ui_print_footer("", $text{'index_return'});
283
284 sub download_error
285 {
286 unlink($pfile) if ($need_unlink);
287 print "<br><b>$whatfailed : $_[0]</b> <p>\n";
288 &ui_print_footer("", $text{'index_return'});
289 exit;
290 }
291