Better handling of URLs with IPv6 addresses in []
[webmin.git] / web-lib-funcs.pl
1 =head1 web-lib-funcs.pl
2
3 Common functions for Webmin CGI scripts. This file gets in-directly included
4 by all scripts that use web-lib.pl.
5 Example code:
6
7   use WebminCore;
8   init_config();
9   ui_print_header(undef, 'My Module', '');
10   print 'This is Webmin version ',get_webmin_version(),'<p>\n';
11   ui_print_footer();
12
13 =cut
14
15 #use warnings;
16 use Socket;
17 use POSIX;
18 eval "use Socket6";
19 $ipv6_module_error = $@;
20
21 use vars qw($user_risk_level $loaded_theme_library $wait_for_input
22             $done_webmin_header $trust_unknown_referers $unsafe_index_cgi
23             %done_foreign_require $webmin_feedback_address
24             $user_skill_level $pragma_no_cache $foreign_args);
25 # Globals
26 use vars qw($module_index_name $number_to_month_map $month_to_number_map
27             $umask_already $default_charset $licence_status $os_type
28             $licence_message $script_name $loaded_theme_oo_library
29             $done_web_lib_funcs $os_version $module_index_link
30             $called_from_webmin_core $ipv6_module_error);
31
32 =head2 read_file(file, &hash, [&order], [lowercase], [split-char])
33
34 Fill the given hash reference with name=value pairs from a file. The required
35 parameters are :
36
37 =item file - The file to head, which must be text with each line like name=value
38
39 =item hash - The hash reference to add values read from the file to.
40
41 =item order - If given, an array reference to add names to in the order they were read
42
43 =item lowercase - If set to 1, names are converted to lower case
44
45 =item split-char - If set, names and values are split on this character instead of =
46
47 =cut
48 sub read_file
49 {
50 local $_;
51 my $split = defined($_[4]) ? $_[4] : "=";
52 my $realfile = &translate_filename($_[0]);
53 &open_readfile(ARFILE, $_[0]) || return 0;
54 while(<ARFILE>) {
55         chomp;
56         my $hash = index($_, "#");
57         my $eq = index($_, $split);
58         if ($hash != 0 && $eq >= 0) {
59                 my $n = substr($_, 0, $eq);
60                 my $v = substr($_, $eq+1);
61                 chomp($v);
62                 $_[1]->{$_[3] ? lc($n) : $n} = $v;
63                 push(@{$_[2]}, $n) if ($_[2]);
64                 }
65         }
66 close(ARFILE);
67 $main::read_file_missing{$realfile} = 0;        # It exists now
68 if (defined($main::read_file_cache{$realfile})) {
69         %{$main::read_file_cache{$realfile}} = %{$_[1]};
70         }
71 return 1;
72 }
73
74 =head2 read_file_cached(file, &hash, [&order], [lowercase], [split-char])
75
76 Like read_file, but reads from an in-memory cache if the file has already been
77 read in this Webmin script. Recommended, as it behaves exactly the same as
78 read_file, but can be much faster.
79
80 =cut
81 sub read_file_cached
82 {
83 my $realfile = &translate_filename($_[0]);
84 if (defined($main::read_file_cache{$realfile})) {
85         # Use cached data
86         %{$_[1]} = ( %{$_[1]}, %{$main::read_file_cache{$realfile}} );
87         return 1;
88         }
89 elsif ($main::read_file_missing{$realfile}) {
90         # Doesn't exist, so don't re-try read
91         return 0;
92         }
93 else {
94         # Actually read the file
95         my %d;
96         if (&read_file($_[0], \%d, $_[2], $_[3], $_[4])) {
97                 %{$main::read_file_cache{$realfile}} = %d;
98                 %{$_[1]} = ( %{$_[1]}, %d );
99                 return 1;
100                 }
101         else {
102                 # Flag as non-existant
103                 $main::read_file_missing{$realfile} = 1;
104                 return 0;
105                 }
106         }
107 }
108  
109 =head2 write_file(file, &hash, [join-char])
110
111 Write out the contents of a hash as name=value lines. The parameters are :
112
113 =item file - Full path to write to
114
115 =item hash - A hash reference containing names and values to output
116
117 =item join-char - If given, names and values are separated by this instead of =
118
119 =cut
120 sub write_file
121 {
122 my (%old, @order);
123 my $join = defined($_[2]) ? $_[2] : "=";
124 my $realfile = &translate_filename($_[0]);
125 &read_file($_[0], \%old, \@order);
126 &open_tempfile(ARFILE, ">$_[0]");
127 foreach $k (@order) {
128         if (exists($_[1]->{$k})) {
129                 (print ARFILE $k,$join,$_[1]->{$k},"\n") ||
130                         &error(&text("efilewrite", $realfile, $!));
131                 }
132         }
133 foreach $k (keys %{$_[1]}) {
134         if (!exists($old{$k})) {
135                 (print ARFILE $k,$join,$_[1]->{$k},"\n") ||
136                         &error(&text("efilewrite", $realfile, $!));
137                 }
138         }
139 &close_tempfile(ARFILE);
140 if (defined($main::read_file_cache{$realfile})) {
141         %{$main::read_file_cache{$realfile}} = %{$_[1]};
142         }
143 if (defined($main::read_file_missing{$realfile})) {
144         $main::read_file_missing{$realfile} = 0;
145         }
146 }
147
148 =head2 html_escape(string)
149
150 Converts &, < and > codes in text to HTML entities, and returns the new string.
151 This should be used when including data read from other sources in HTML pages.
152
153 =cut
154 sub html_escape
155 {
156 my ($tmp) = @_;
157 $tmp =~ s/&/&amp;/g;
158 $tmp =~ s/</&lt;/g;
159 $tmp =~ s/>/&gt;/g;
160 $tmp =~ s/\"/&quot;/g;
161 $tmp =~ s/\'/&#39;/g;
162 $tmp =~ s/=/&#61;/g;
163 return $tmp;
164 }
165
166 =head2 quote_escape(string, [only-quote])
167
168 Converts ' and " characters in a string into HTML entities, and returns it.
169 Useful for outputing HTML tag values.
170
171 =cut
172 sub quote_escape
173 {
174 my ($tmp, $only) = @_;
175 if ($tmp !~ /\&[a-zA-Z]+;/ && $tmp !~ /\&#/) {
176         # convert &, unless it is part of &#nnn; or &foo;
177         $tmp =~ s/&([^#])/&amp;$1/g;
178         }
179 $tmp =~ s/&$/&amp;/g;
180 $tmp =~ s/\"/&quot;/g if ($only eq '' || $only eq '"');
181 $tmp =~ s/\'/&#39;/g if ($only eq '' || $only eq "'");
182 return $tmp;
183 }
184
185 =head2 tempname([filename])
186
187 Returns a mostly random temporary file name, typically under the /tmp/.webmin
188 directory. If filename is given, this will be the base name used. Otherwise
189 a unique name is selected randomly.
190
191 =cut
192 sub tempname
193 {
194 my $tmp_base = $gconfig{'tempdir_'.&get_module_name()} ?
195                         $gconfig{'tempdir_'.&get_module_name()} :
196                   $gconfig{'tempdir'} ? $gconfig{'tempdir'} :
197                   $ENV{'TEMP'} ? $ENV{'TEMP'} :
198                   $ENV{'TMP'} ? $ENV{'TMP'} :
199                   -d "c:/temp" ? "c:/temp" : "/tmp/.webmin";
200 my $tmp_dir = -d $remote_user_info[7] && !$gconfig{'nohometemp'} ?
201                         "$remote_user_info[7]/.tmp" :
202                  @remote_user_info ? $tmp_base."-".$remote_user :
203                  $< != 0 ? $tmp_base."-".getpwuid($<) :
204                                      $tmp_base;
205 if ($gconfig{'os_type'} eq 'windows' || $tmp_dir =~ /^[a-z]:/i) {
206         # On Windows system, just create temp dir if missing
207         if (!-d $tmp_dir) {
208                 mkdir($tmp_dir, 0755) ||
209                         &error("Failed to create temp directory $tmp_dir : $!");
210                 }
211         }
212 else {
213         # On Unix systems, need to make sure temp dir is valid
214         my $tries = 0;
215         while($tries++ < 10) {
216                 my @st = lstat($tmp_dir);
217                 last if ($st[4] == $< && (-d _) && ($st[2] & 0777) == 0755);
218                 if (@st) {
219                         unlink($tmp_dir) || rmdir($tmp_dir) ||
220                                 system("/bin/rm -rf ".quotemeta($tmp_dir));
221                         }
222                 mkdir($tmp_dir, 0755) || next;
223                 chown($<, $(, $tmp_dir);
224                 chmod(0755, $tmp_dir);
225                 }
226         if ($tries >= 10) {
227                 my @st = lstat($tmp_dir);
228                 &error("Failed to create temp directory $tmp_dir : uid=$st[4] mode=$st[2]");
229                 }
230         }
231 my $rv;
232 if (defined($_[0]) && $_[0] !~ /\.\./) {
233         $rv = "$tmp_dir/$_[0]";
234         }
235 else {
236         $main::tempfilecount++;
237         &seed_random();
238         $rv = $tmp_dir."/".int(rand(1000000))."_".
239                $main::tempfilecount."_".$scriptname;
240         }
241 return $rv;
242 }
243
244 =head2 transname([filename])
245
246 Behaves exactly like tempname, but records the temp file for deletion when the
247 current Webmin script process exits.
248
249 =cut
250 sub transname
251 {
252 my $rv = &tempname(@_);
253 push(@main::temporary_files, $rv);
254 return $rv;
255 }
256
257 =head2 trunc(string, maxlen)
258
259 Truncates a string to the shortest whole word less than or equal to the
260 given width. Useful for word wrapping.
261
262 =cut
263 sub trunc
264 {
265 if (length($_[0]) <= $_[1]) {
266         return $_[0];
267         }
268 my $str = substr($_[0],0,$_[1]);
269 my $c;
270 do {
271         $c = chop($str);
272         } while($c !~ /\S/);
273 $str =~ s/\s+$//;
274 return $str;
275 }
276
277 =head2 indexof(string, value, ...)
278
279 Returns the index of some value in an array of values, or -1 if it was not
280 found.
281
282 =cut
283 sub indexof
284 {
285 for(my $i=1; $i <= $#_; $i++) {
286         if ($_[$i] eq $_[0]) { return $i - 1; }
287         }
288 return -1;
289 }
290
291 =head2 indexoflc(string, value, ...)
292
293 Like indexof, but does a case-insensitive match
294
295 =cut
296 sub indexoflc
297 {
298 my $str = lc(shift(@_));
299 my @arr = map { lc($_) } @_;
300 return &indexof($str, @arr);
301 }
302
303 =head2 sysprint(handle, [string]+)
304
305 Outputs some strings to a file handle, but bypassing IO buffering. Can be used
306 as a replacement for print when writing to pipes or sockets.
307
308 =cut
309 sub sysprint
310 {
311 my $fh = &callers_package($_[0]);
312 my $str = join('', @_[1..$#_]);
313 syswrite $fh, $str, length($str);
314 }
315
316 =head2 check_ipaddress(ip)
317
318 Check if some IPv4 address is properly formatted, returning 1 if so or 0 if not.
319
320 =cut
321 sub check_ipaddress
322 {
323 return $_[0] =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/ &&
324         $1 >= 0 && $1 <= 255 &&
325         $2 >= 0 && $2 <= 255 &&
326         $3 >= 0 && $3 <= 255 &&
327         $4 >= 0 && $4 <= 255;
328 }
329
330 =head2 check_ip6address(ip)
331
332 Check if some IPv6 address is properly formatted, and returns 1 if so.
333
334 =cut
335 sub check_ip6address
336 {
337   my @blocks = split(/:/, $_[0]);
338   return 0 if (@blocks == 0 || @blocks > 8);
339
340   # The address/netmask format is accepted. So we're looking for a "/" to isolate a possible netmask.
341   # After that, we delete the netmask to control the address only format, but we verify whether the netmask 
342   # value is in [0;128].
343   my $ib = $#blocks;
344   my $where = index($blocks[$ib],"/");
345   my $m = 0;
346   if ($where != -1) {
347     my $b = substr($blocks[$ib],0,$where);
348     $m = substr($blocks[$ib],$where+1,length($blocks[$ib])-($where+1));
349     $blocks[$ib]=$b;
350   }
351
352   # The netmask must take its value in [0;128] 
353   return 0 if ($m <0 || $m >128); 
354
355   # Check the different blocks of the address : 16 bits block in hexa notation.
356   # Possibility of 1 empty block or 2 if the address begins with "::".
357   my $b;
358   my $empty = 0;
359   foreach $b (@blocks) {
360           return 0 if ($b ne "" && $b !~ /^[0-9a-f]{1,4}$/i);
361           $empty++ if ($b eq "");
362           }
363   return 0 if ($empty > 1 && !($_[0] =~ /^::/ && $empty == 2));
364   return 1;
365 }
366
367
368
369 =head2 generate_icon(image, title, link, [href], [width], [height], [before-title], [after-title])
370
371 Prints HTML for an icon image. The parameters are :
372
373 =item image - URL for the image, like images/foo.gif
374
375 =item title - Text to appear under the icon
376
377 =item link - Optional destination for the icon's link
378
379 =item href - Other HTML attributes to be added to the <a href> for the link
380
381 =item width - Optional width of the icon
382
383 =item height - Optional height of the icon
384
385 =item before-title - HTML to appear before the title link, but which is not actually in the link
386
387 =item after-title - HTML to appear after the title link, but which is not actually in the link
388
389 =cut
390 sub generate_icon
391 {
392 &load_theme_library();
393 if (defined(&theme_generate_icon)) {
394         &theme_generate_icon(@_);
395         return;
396         }
397 my $w = !defined($_[4]) ? "width=48" : $_[4] ? "width=$_[4]" : "";
398 my $h = !defined($_[5]) ? "height=48" : $_[5] ? "height=$_[5]" : "";
399 if ($tconfig{'noicons'}) {
400         if ($_[2]) {
401                 print "$_[6]<a href=\"$_[2]\" $_[3]>$_[1]</a>$_[7]\n";
402                 }
403         else {
404                 print "$_[6]$_[1]$_[7]\n";
405                 }
406         }
407 elsif ($_[2]) {
408         print "<table border><tr><td width=48 height=48>\n",
409               "<a href=\"$_[2]\" $_[3]><img src=\"$_[0]\" alt=\"\" border=0 ",
410               "$w $h></a></td></tr></table>\n";
411         print "$_[6]<a href=\"$_[2]\" $_[3]>$_[1]</a>$_[7]\n";
412         }
413 else {
414         print "<table border><tr><td width=48 height=48>\n",
415               "<img src=\"$_[0]\" alt=\"\" border=0 $w $h>",
416               "</td></tr></table>\n$_[6]$_[1]$_[7]\n";
417         }
418 }
419
420 =head2 urlize
421
422 Converts a string to a form ok for putting in a URL, using % escaping.
423
424 =cut
425 sub urlize
426 {
427 my ($rv) = @_;
428 $rv =~ s/([^A-Za-z0-9])/sprintf("%%%2.2X", ord($1))/ge;
429 return $rv;
430 }
431
432 =head2 un_urlize(string)
433
434 Converts a URL-encoded string to it's original contents - the reverse of the
435 urlize function.
436
437 =cut
438 sub un_urlize
439 {
440 my ($rv) = @_;
441 $rv =~ s/\+/ /g;
442 $rv =~ s/%(..)/pack("c",hex($1))/ge;
443 return $rv;
444 }
445
446 =head2 include(filename)
447
448 Read and output the contents of the given file.
449
450 =cut
451 sub include
452 {
453 local $_;
454 open(INCLUDE, &translate_filename($_[0])) || return 0;
455 while(<INCLUDE>) {
456         print;
457         }
458 close(INCLUDE);
459 return 1;
460 }
461
462 =head2 copydata(in-handle, out-handle)
463
464 Read from one file handle and write to another, until there is no more to read.
465
466 =cut
467 sub copydata
468 {
469 my ($in, $out) = @_;
470 $in = &callers_package($in);
471 $out = &callers_package($out);
472 my $buf;
473 while(read($in, $buf, 1024) > 0) {
474         (print $out $buf) || return 0;
475         }
476 return 1;
477 }
478
479 =head2 ReadParseMime([maximum], [&cbfunc, &cbargs])
480
481 Read data submitted via a POST request using the multipart/form-data coding,
482 and store it in the global %in hash. The optional parameters are :
483
484 =item maximum - If the number of bytes of input exceeds this number, stop reading and call error.
485
486 =item cbfunc - A function reference to call after reading each block of data.
487
488 =item cbargs - Additional parameters to the callback function.
489
490 =cut
491 sub ReadParseMime
492 {
493 my ($max, $cbfunc, $cbargs) = @_;
494 my ($boundary, $line, $foo, $name, $got, $file);
495 my $err = &text('readparse_max', $max);
496 $ENV{'CONTENT_TYPE'} =~ /boundary=(.*)$/ || &error($text{'readparse_enc'});
497 if ($ENV{'CONTENT_LENGTH'} && $max && $ENV{'CONTENT_LENGTH'} > $max) {
498         &error($err);
499         }
500 &$cbfunc(0, $ENV{'CONTENT_LENGTH'}, $file, @$cbargs) if ($cbfunc);
501 $boundary = $1;
502 <STDIN>;        # skip first boundary
503 while(1) {
504         $name = "";
505         # Read section headers
506         my $lastheader;
507         while(1) {
508                 $line = <STDIN>;
509                 $got += length($line);
510                 &$cbfunc($got, $ENV{'CONTENT_LENGTH'}, @$cbargs) if ($cbfunc);
511                 if ($max && $got > $max) {
512                         &error($err)
513                         }
514                 $line =~ tr/\r\n//d;
515                 last if (!$line);
516                 if ($line =~ /^(\S+):\s*(.*)$/) {
517                         $header{$lastheader = lc($1)} = $2;
518                         }
519                 elsif ($line =~ /^\s+(.*)$/) {
520                         $header{$lastheader} .= $line;
521                         }
522                 }
523
524         # Parse out filename and type
525         if ($header{'content-disposition'} =~ /^form-data(.*)/) {
526                 $rest = $1;
527                 while ($rest =~ /([a-zA-Z]*)=\"([^\"]*)\"(.*)/) {
528                         if ($1 eq 'name') {
529                                 $name = $2;
530                                 }
531                         else {
532                                 $foo = $name . "_$1";
533                                 $in{$foo} = $2;
534                                 }
535                         $rest = $3;
536                         }
537                 }
538         else {
539                 &error($text{'readparse_cdheader'});
540                 }
541         if ($header{'content-type'} =~ /^([^\s;]+)/) {
542                 $foo = $name . "_content_type";
543                 $in{$foo} = $1;
544                 }
545         $file = $in{$name."_filename"};
546
547         # Read data
548         $in{$name} .= "\0" if (defined($in{$name}));
549         while(1) {
550                 $line = <STDIN>;
551                 $got += length($line);
552                 &$cbfunc($got, $ENV{'CONTENT_LENGTH'}, $file, @$cbargs)
553                         if ($cbfunc);
554                 if ($max && $got > $max) {
555                         #print STDERR "over limit of $max\n";
556                         #&error($err);
557                         }
558                 if (!$line) {
559                         # Unexpected EOF?
560                         &$cbfunc(-1, $ENV{'CONTENT_LENGTH'}, $file, @$cbargs)
561                                 if ($cbfunc);
562                         return;
563                         }
564                 my $ptline = $line;
565                 $ptline =~ s/[^a-zA-Z0-9\-]/\./g;
566                 if (index($line, $boundary) != -1) { last; }
567                 $in{$name} .= $line;
568                 }
569         chop($in{$name}); chop($in{$name});
570         if (index($line,"$boundary--") != -1) { last; }
571         }
572 &$cbfunc(-1, $ENV{'CONTENT_LENGTH'}, $file, @$cbargs) if ($cbfunc);
573 }
574
575 =head2 ReadParse([&hash], [method], [noplus])
576
577 Fills the given hash reference with CGI parameters, or uses the global hash
578 %in if none is given. Also sets the global variables $in and @in. The other
579 parameters are :
580
581 =item method - For use of this HTTP method, such as GET
582
583 =item noplus - Don't convert + in parameters to spaces.
584
585 =cut
586 sub ReadParse
587 {
588 my $a = $_[0] || \%in;
589 %$a = ( );
590 my $meth = $_[1] ? $_[1] : $ENV{'REQUEST_METHOD'};
591 undef($in);
592 if ($meth eq 'POST') {
593         my $clen = $ENV{'CONTENT_LENGTH'};
594         &read_fully(STDIN, \$in, $clen) == $clen ||
595                 &error("Failed to read POST input : $!");
596         }
597 if ($ENV{'QUERY_STRING'}) {
598         if ($in) { $in .= "&".$ENV{'QUERY_STRING'}; }
599         else { $in = $ENV{'QUERY_STRING'}; }
600         }
601 @in = split(/\&/, $in);
602 foreach my $i (@in) {
603         my ($k, $v) = split(/=/, $i, 2);
604         if (!$_[2]) {
605                 $k =~ tr/\+/ /;
606                 $v =~ tr/\+/ /;
607                 }
608         $k =~ s/%(..)/pack("c",hex($1))/ge;
609         $v =~ s/%(..)/pack("c",hex($1))/ge;
610         $a->{$k} = defined($a->{$k}) ? $a->{$k}."\0".$v : $v;
611         }
612 }
613
614 =head2 read_fully(fh, &buffer, length)
615
616 Read data from some file handle up to the given length, even in the face
617 of partial reads. Reads the number of bytes read. Stores received data in the
618 string pointed to be the buffer reference.
619
620 =cut
621 sub read_fully
622 {
623 my ($fh, $buf, $len) = @_;
624 $fh = &callers_package($fh);
625 my $got = 0;
626 while($got < $len) {
627         my $r = read(STDIN, $$buf, $len-$got, $got);
628         last if ($r <= 0);
629         $got += $r;
630         }
631 return $got;
632 }
633
634 =head2 read_parse_mime_callback(size, totalsize, upload-id)
635
636 Called by ReadParseMime as new data arrives from a form-data POST. Only updates
637 the file on every 1% change though. For internal use by the upload progress
638 tracker.
639
640 =cut
641 sub read_parse_mime_callback
642 {
643 my ($size, $totalsize, $filename, $id) = @_;
644 return if ($gconfig{'no_upload_tracker'});
645 return if (!$id);
646
647 # Create the upload tracking directory - if running as non-root, this has to
648 # be under the user's home
649 my $vardir;
650 if ($<) {
651         my @uinfo = @remote_user_info ? @remote_user_info : getpwuid($<);
652         $vardir = "$uinfo[7]/.tmp";
653         }
654 else {
655         $vardir = $ENV{'WEBMIN_VAR'};
656         }
657 if (!-d $vardir) {
658         &make_dir($vardir, 0755);
659         }
660
661 # Remove any upload.* files more than 1 hour old
662 if (!$main::read_parse_mime_callback_flushed) {
663         my $now = time();
664         opendir(UPDIR, $vardir);
665         foreach my $f (readdir(UPDIR)) {
666                 next if ($f !~ /^upload\./);
667                 my @st = stat("$vardir/$f");
668                 if ($st[9] < $now-3600) {
669                         unlink("$vardir/$f");
670                         }
671                 }
672         closedir(UPDIR);
673         $main::read_parse_mime_callback_flushed++;
674         }
675
676 # Only update file once per percent
677 my $upfile = "$vardir/upload.$id";
678 if ($totalsize && $size >= 0) {
679         my $pc = int(100 * $size / $totalsize);
680         if ($pc <= $main::read_parse_mime_callback_pc{$upfile}) {
681                 return;
682                 }
683         $main::read_parse_mime_callback_pc{$upfile} = $pc;
684         }
685
686 # Write to the file
687 &open_tempfile(UPFILE, ">$upfile");
688 print UPFILE $size,"\n";
689 print UPFILE $totalsize,"\n";
690 print UPFILE $filename,"\n";
691 &close_tempfile(UPFILE);
692 }
693
694 =head2 read_parse_mime_javascript(upload-id, [&fields])
695
696 Returns an onSubmit= Javascript statement to popup a window for tracking
697 an upload with the given ID. For internal use by the upload progress tracker.
698
699 =cut
700 sub read_parse_mime_javascript
701 {
702 my ($id, $fields) = @_;
703 return "" if ($gconfig{'no_upload_tracker'});
704 my $opener = "window.open(\"$gconfig{'webprefix'}/uptracker.cgi?id=$id&uid=$<\", \"uptracker\", \"toolbar=no,menubar=no,scrollbars=no,width=500,height=100\");";
705 if ($fields) {
706         my $if = join(" || ", map { "typeof($_) != \"undefined\" && $_.value != \"\"" } @$fields);
707         return "onSubmit='if ($if) { $opener }'";
708         }
709 else {
710         return "onSubmit='$opener'";
711         }
712 }
713
714 =head2 PrintHeader(charset)
715
716 Outputs the HTTP headers for an HTML page. The optional charset parameter
717 can be used to set a character set. Normally this function is not called
718 directly, but is rather called by ui_print_header or header.
719
720 =cut
721 sub PrintHeader
722 {
723 if ($pragma_no_cache || $gconfig{'pragma_no_cache'}) {
724         print "pragma: no-cache\n";
725         print "Expires: Thu, 1 Jan 1970 00:00:00 GMT\n";
726         print "Cache-Control: no-store, no-cache, must-revalidate\n";
727         print "Cache-Control: post-check=0, pre-check=0\n";
728         }
729 if (defined($_[0])) {
730         print "Content-type: text/html; Charset=$_[0]\n\n";
731         }
732 else {
733         print "Content-type: text/html\n\n";
734         }
735 }
736
737 =head2 header(title, image, [help], [config], [nomodule], [nowebmin], [rightside], [head-stuff], [body-stuff], [below])
738
739 Outputs a Webmin HTML page header with a title, including HTTP headers. The
740 parameters are :
741
742 =item title - The text to show at the top of the page
743
744 =item image - An image to show instead of the title text. This is typically left blank.
745
746 =item help - If set, this is the name of a help page that will be linked to in the title.
747
748 =item config - If set to 1, the title will contain a link to the module's config page.
749
750 =item nomodule - If set to 1, there will be no link in the title section to the module's index.
751
752 =item nowebmin - If set to 1, there will be no link in the title section to the Webmin index.
753
754 =item rightside - HTML to be shown on the right-hand side of the title. Can contain multiple lines, separated by <br>. Typically this is used for links to stop, start or restart servers.
755
756 =item head-stuff - HTML to be included in the <head> section of the page.
757
758 =item body-stuff - HTML attributes to be include in the <body> tag.
759
760 =item below - HTML to be displayed below the title. Typically this is used for application or server version information.
761
762 =cut
763 sub header
764 {
765 return if ($main::done_webmin_header++);
766 my $ll;
767 my $charset = defined($main::force_charset) ? $main::force_charset
768                                             : &get_charset();
769 &PrintHeader($charset);
770 &load_theme_library();
771 if (defined(&theme_header)) {
772         $module_name = &get_module_name();
773         &theme_header(@_);
774         $miniserv::page_capture = 1;
775         return;
776         }
777 print "<!doctype html public \"-//W3C//DTD HTML 3.2 Final//EN\">\n";
778 print "<html>\n";
779 print "<head>\n";
780 if (defined(&theme_prehead)) {
781         &theme_prehead(@_);
782         }
783 if ($charset) {
784         print "<meta http-equiv=\"Content-Type\" ",
785               "content=\"text/html; Charset=".&quote_escape($charset)."\">\n";
786         }
787 if (@_ > 0) {
788         my $title = &get_html_title($_[0]);
789         print "<title>$title</title>\n";
790         print $_[7] if ($_[7]);
791         print &get_html_status_line(0);
792         }
793 print "$tconfig{'headhtml'}\n" if ($tconfig{'headhtml'});
794 if ($tconfig{'headinclude'}) {
795         print &read_file_contents(
796                 "$theme_root_directory/$tconfig{'headinclude'}");
797         }
798 print "</head>\n";
799 my $bgcolor = defined($tconfig{'cs_page'}) ? $tconfig{'cs_page'} :
800                  defined($gconfig{'cs_page'}) ? $gconfig{'cs_page'} : "ffffff";
801 my $link = defined($tconfig{'cs_link'}) ? $tconfig{'cs_link'} :
802               defined($gconfig{'cs_link'}) ? $gconfig{'cs_link'} : "0000ee";
803 my $text = defined($tconfig{'cs_text'}) ? $tconfig{'cs_text'} : 
804               defined($gconfig{'cs_text'}) ? $gconfig{'cs_text'} : "000000";
805 my $bgimage = defined($tconfig{'bgimage'}) ? "background=$tconfig{'bgimage'}"
806                                               : "";
807 my $dir = $current_lang_info->{'dir'} ? "dir=\"$current_lang_info->{'dir'}\""
808                                          : "";
809 print "<body bgcolor=#$bgcolor link=#$link vlink=#$link text=#$text ",
810       "$bgimage $tconfig{'inbody'} $dir $_[8]>\n";
811 if (defined(&theme_prebody)) {
812         &theme_prebody(@_);
813         }
814 my $hostname = &get_display_hostname();
815 my $version = &get_webmin_version();
816 my $prebody = $tconfig{'prebody'};
817 if ($prebody) {
818         $prebody =~ s/%HOSTNAME%/$hostname/g;
819         $prebody =~ s/%VERSION%/$version/g;
820         $prebody =~ s/%USER%/$remote_user/g;
821         $prebody =~ s/%OS%/$os_type $os_version/g;
822         print "$prebody\n";
823         }
824 if ($tconfig{'prebodyinclude'}) {
825         local $_;
826         open(INC, "$theme_root_directory/$tconfig{'prebodyinclude'}");
827         while(<INC>) {
828                 print;
829                 }
830         close(INC);
831         }
832 if (@_ > 1) {
833         print $tconfig{'preheader'};
834         my %this_module_info = &get_module_info(&get_module_name());
835         print "<table class='header' width=100%><tr>\n";
836         if ($gconfig{'sysinfo'} == 2 && $remote_user) {
837                 print "<td id='headln1' colspan=3 align=center>\n";
838                 print &get_html_status_line(1);
839                 print "</td></tr> <tr>\n";
840                 }
841         print "<td id='headln2l' width=15% valign=top align=left>";
842         if ($ENV{'HTTP_WEBMIN_SERVERS'} && !$tconfig{'framed'}) {
843                 print "<a href='$ENV{'HTTP_WEBMIN_SERVERS'}'>",
844                       "$text{'header_servers'}</a><br>\n";
845                 }
846         if (!$_[5] && !$tconfig{'noindex'}) {
847                 my @avail = &get_available_module_infos(1);
848                 my $nolo = $ENV{'ANONYMOUS_USER'} ||
849                               $ENV{'SSL_USER'} || $ENV{'LOCAL_USER'} ||
850                               $ENV{'HTTP_USER_AGENT'} =~ /webmin/i;
851                 if ($gconfig{'gotoone'} && $main::session_id && @avail == 1 &&
852                     !$nolo) {
853                         print "<a href='$gconfig{'webprefix'}/session_login.cgi?logout=1'>",
854                               "$text{'main_logout'}</a><br>";
855                         }
856                 elsif ($gconfig{'gotoone'} && @avail == 1 && !$nolo) {
857                         print "<a href=$gconfig{'webprefix'}/switch_user.cgi>",
858                               "$text{'main_switch'}</a><br>";
859                         }
860                 elsif (!$gconfig{'gotoone'} || @avail > 1) {
861                         print "<a href='$gconfig{'webprefix'}/?cat=",
862                               $this_module_info{'category'},
863                               "'>$text{'header_webmin'}</a><br>\n";
864                         }
865                 }
866         if (!$_[4] && !$tconfig{'nomoduleindex'}) {
867                 my $idx = $this_module_info{'index_link'};
868                 my $mi = $module_index_link || "/".&get_module_name()."/$idx";
869                 my $mt = $module_index_name || $text{'header_module'};
870                 print "<a href=\"$gconfig{'webprefix'}$mi\">$mt</a><br>\n";
871                 }
872         if (ref($_[2]) eq "ARRAY" && !$ENV{'ANONYMOUS_USER'} &&
873             !$tconfig{'nohelp'}) {
874                 print &hlink($text{'header_help'}, $_[2]->[0], $_[2]->[1]),
875                       "<br>\n";
876                 }
877         elsif (defined($_[2]) && !$ENV{'ANONYMOUS_USER'} &&
878                !$tconfig{'nohelp'}) {
879                 print &hlink($text{'header_help'}, $_[2]),"<br>\n";
880                 }
881         if ($_[3]) {
882                 my %access = &get_module_acl();
883                 if (!$access{'noconfig'} && !$config{'noprefs'}) {
884                         my $cprog = $user_module_config_directory ?
885                                         "uconfig.cgi" : "config.cgi";
886                         print "<a href=\"$gconfig{'webprefix'}/$cprog?",
887                               &get_module_name()."\">",
888                               $text{'header_config'},"</a><br>\n";
889                         }
890                 }
891         print "</td>\n";
892         if ($_[1]) {
893                 # Title is a single image
894                 print "<td id='headln2c' align=center width=70%>",
895                       "<img alt=\"$_[0]\" src=\"$_[1]\"></td>\n";
896                 }
897         else {
898                 # Title is just text
899                 my $ts = defined($tconfig{'titlesize'}) ?
900                                 $tconfig{'titlesize'} : "+2";
901                 print "<td id='headln2c' align=center width=70%>",
902                       ($ts ? "<font size=$ts>" : ""),$_[0],
903                       ($ts ? "</font>" : "");
904                 print "<br>$_[9]\n" if ($_[9]);
905                 print "</td>\n";
906                 }
907         print "<td id='headln2r' width=15% valign=top align=right>";
908         print $_[6];
909         print "</td></tr></table>\n";
910         print $tconfig{'postheader'};
911         }
912 $miniserv::page_capture = 1;
913 }
914
915 =head2 get_html_title(title)
916
917 Returns the full string to appear in the HTML <title> block.
918
919 =cut
920 sub get_html_title
921 {
922 my ($msg) = @_;
923 my $title;
924 my $os_type = $gconfig{'real_os_type'} || $gconfig{'os_type'};
925 my $os_version = $gconfig{'real_os_version'} || $gconfig{'os_version'};
926 my $host = &get_display_hostname();
927 if ($gconfig{'sysinfo'} == 1 && $remote_user) {
928         $title = sprintf "%s : %s on %s (%s %s)\n",
929                 $msg, $remote_user, $host,
930                 $os_type, $os_version;
931         }
932 elsif ($gconfig{'sysinfo'} == 4 && $remote_user) {
933         $title = sprintf "%s on %s (%s %s)\n",
934                 $remote_user, $host,
935                 $os_type, $os_version;
936         }
937 else {
938         $title = $msg;
939         }
940 if ($gconfig{'showlogin'} && $remote_user) {
941         $title = $remote_user.($title ? " : ".$title : "");
942         }
943 if ($gconfig{'showhost'}) {
944         $title = $host.($title ? " : ".$title : "");
945         }
946 return $title;
947 }
948
949 =head2 get_html_framed_title
950
951 Returns the title text for a framed theme main page.
952
953 =cut
954 sub get_html_framed_title
955 {
956 my $ostr;
957 my $os_type = $gconfig{'real_os_type'} || $gconfig{'os_type'};
958 my $os_version = $gconfig{'real_os_version'} || $gconfig{'os_version'};
959 my $title;
960 if (($gconfig{'sysinfo'} == 4 || $gconfig{'sysinfo'} == 1) && $remote_user) {
961         # Alternate title mode requested
962         $title = sprintf "%s on %s (%s %s)\n",
963                 $remote_user, &get_display_hostname(),
964                 $os_type, $os_version;
965         }
966 else {
967         # Title like 'Webmin x.yy on hostname (Linux 6)'
968         if ($os_version eq "*") {
969                 $ostr = $os_type;
970                 }
971         else {
972                 $ostr = "$os_type $os_version";
973                 }
974         my $host = &get_display_hostname();
975         my $ver = &get_webmin_version();
976         $title = $gconfig{'nohostname'} ? $text{'main_title2'} :
977                  $gconfig{'showhost'} ? &text('main_title3', $ver, $ostr) :
978                                         &text('main_title', $ver, $host, $ostr);
979         if ($gconfig{'showlogin'}) {
980                 $title = $remote_user.($title ? " : ".$title : "");
981                 }
982         if ($gconfig{'showhost'}) {
983                 $title = $host.($title ? " : ".$title : "");
984                 }
985         }
986 return $title;
987 }
988
989 =head2 get_html_status_line(text-only)
990
991 Returns HTML for a script block that sets the status line, or if text-only
992 is set to 1, just return the status line text.
993
994 =cut
995 sub get_html_status_line
996 {
997 my ($textonly) = @_;
998 if (($gconfig{'sysinfo'} != 0 || !$remote_user) && !$textonly) {
999         # Disabled in this mode
1000         return undef;
1001         }
1002 my $os_type = $gconfig{'real_os_type'} || $gconfig{'os_type'};
1003 my $os_version = $gconfig{'real_os_version'} || $gconfig{'os_version'};
1004 my $line = &text('header_statusmsg',
1005                  ($ENV{'ANONYMOUS_USER'} ? "Anonymous user"
1006                                            : $remote_user).
1007                  ($ENV{'SSL_USER'} ? " (SSL certified)" :
1008                   $ENV{'LOCAL_USER'} ? " (Local user)" : ""),
1009                  $text{'programname'},
1010                  &get_webmin_version(),
1011                  &get_display_hostname(),
1012                  $os_type.($os_version eq "*" ? "" :" $os_version"));
1013 if ($textonly) {
1014         return $line;
1015         }
1016 else {
1017         $line =~ s/\r|\n//g;
1018         return "<script language=JavaScript type=text/javascript>\n".
1019                "defaultStatus=\"".&quote_escape($line)."\";\n".
1020                "</script>\n";
1021         }
1022 }
1023
1024 =head2 popup_header([title], [head-stuff], [body-stuff], [no-body])
1025
1026 Outputs a page header, suitable for a popup window. If no title is given,
1027 absolutely no decorations are output. Also useful in framesets. The parameters
1028 are :
1029
1030 =item title - Title text for the popup window.
1031
1032 =item head-stuff - HTML to appear in the <head> section.
1033
1034 =item body-stuff - HTML attributes to be include in the <body> tag.
1035
1036 =item no-body - If set to 1, don't generate a body tag
1037
1038 =cut
1039 sub popup_header
1040 {
1041 return if ($main::done_webmin_header++);
1042 my $ll;
1043 my $charset = defined($main::force_charset) ? $main::force_charset
1044                                             : &get_charset();
1045 &PrintHeader($charset);
1046 &load_theme_library();
1047 if (defined(&theme_popup_header)) {
1048         &theme_popup_header(@_);
1049         $miniserv::page_capture = 1;
1050         return;
1051         }
1052 print "<!doctype html public \"-//W3C//DTD HTML 3.2 Final//EN\">\n";
1053 print "<html>\n";
1054 print "<head>\n";
1055 if (defined(&theme_popup_prehead)) {
1056         &theme_popup_prehead(@_);
1057         }
1058 print "<title>$_[0]</title>\n";
1059 print $_[1];
1060 print "$tconfig{'headhtml'}\n" if ($tconfig{'headhtml'});
1061 if ($tconfig{'headinclude'}) {
1062         print &read_file_contents(
1063                 "$theme_root_directory/$tconfig{'headinclude'}");
1064         }
1065 print "</head>\n";
1066 my $bgcolor = defined($tconfig{'cs_page'}) ? $tconfig{'cs_page'} :
1067                  defined($gconfig{'cs_page'}) ? $gconfig{'cs_page'} : "ffffff";
1068 my $link = defined($tconfig{'cs_link'}) ? $tconfig{'cs_link'} :
1069               defined($gconfig{'cs_link'}) ? $gconfig{'cs_link'} : "0000ee";
1070 my $text = defined($tconfig{'cs_text'}) ? $tconfig{'cs_text'} : 
1071               defined($gconfig{'cs_text'}) ? $gconfig{'cs_text'} : "000000";
1072 my $bgimage = defined($tconfig{'bgimage'}) ? "background=$tconfig{'bgimage'}"
1073                                               : "";
1074 if (!$_[3]) {
1075         print "<body id='popup' bgcolor=#$bgcolor link=#$link vlink=#$link ",
1076               "text=#$text $bgimage $tconfig{'inbody'} $_[2]>\n";
1077         if (defined(&theme_popup_prebody)) {
1078                 &theme_popup_prebody(@_);
1079                 }
1080         }
1081 $miniserv::page_capture = 1;
1082 }
1083
1084 =head2 footer([page, name]+, [noendbody])
1085
1086 Outputs the footer for a Webmin HTML page, possibly with links back to other
1087 pages. The links are specified by pairs of parameters, the first of which is 
1088 a link destination, and the second the link text. For example :
1089
1090  footer('/', 'Webmin index', '', 'Module menu');
1091
1092 =cut
1093 sub footer
1094 {
1095 $miniserv::page_capture = 0;
1096 &load_theme_library();
1097 my %this_module_info = &get_module_info(&get_module_name());
1098 if (defined(&theme_footer)) {
1099         $module_name = &get_module_name();      # Old themes use these
1100         %module_info = %this_module_info;
1101         &theme_footer(@_);
1102         return;
1103         }
1104 for(my $i=0; $i+1<@_; $i+=2) {
1105         my $url = $_[$i];
1106         if ($url ne '/' || !$tconfig{'noindex'}) {
1107                 if ($url eq '/') {
1108                         $url = "/?cat=$this_module_info{'category'}";
1109                         }
1110                 elsif ($url eq '' && &get_module_name()) {
1111                         $url = "/".&get_module_name()."/".
1112                                $this_module_info{'index_link'};
1113                         }
1114                 elsif ($url =~ /^\?/ && &get_module_name()) {
1115                         $url = "/".&get_module_name()."/$url";
1116                         }
1117                 $url = "$gconfig{'webprefix'}$url" if ($url =~ /^\//);
1118                 if ($i == 0) {
1119                         print "<a href=\"$url\"><img alt=\"<-\" align=middle border=0 src=$gconfig{'webprefix'}/images/left.gif></a>\n";
1120                         }
1121                 else {
1122                         print "&nbsp;|\n";
1123                         }
1124                 print "&nbsp;<a href=\"$url\">",&text('main_return', $_[$i+1]),"</a>\n";
1125                 }
1126         }
1127 print "<br>\n";
1128 if (!$_[$i]) {
1129         my $postbody = $tconfig{'postbody'};
1130         if ($postbody) {
1131                 my $hostname = &get_display_hostname();
1132                 my $version = &get_webmin_version();
1133                 my $os_type = $gconfig{'real_os_type'} ||
1134                               $gconfig{'os_type'};
1135                 my $os_version = $gconfig{'real_os_version'} ||
1136                                  $gconfig{'os_version'};
1137                 $postbody =~ s/%HOSTNAME%/$hostname/g;
1138                 $postbody =~ s/%VERSION%/$version/g;
1139                 $postbody =~ s/%USER%/$remote_user/g;
1140                 $postbody =~ s/%OS%/$os_type $os_version/g;
1141                 print "$postbody\n";
1142                 }
1143         if ($tconfig{'postbodyinclude'}) {
1144                 local $_;
1145                 open(INC, "$theme_root_directory/$tconfig{'postbodyinclude'}");
1146                 while(<INC>) {
1147                         print;
1148                         }
1149                 close(INC);
1150                 }
1151         if (defined(&theme_postbody)) {
1152                 &theme_postbody(@_);
1153                 }
1154         print "</body></html>\n";
1155         }
1156 }
1157
1158 =head2 popup_footer([no-body])
1159
1160 Outputs html for a footer for a popup window, started by popup_header.
1161
1162 =cut
1163 sub popup_footer
1164 {
1165 $miniserv::page_capture = 0;
1166 &load_theme_library();
1167 if (defined(&theme_popup_footer)) {
1168         &theme_popup_footer(@_);
1169         return;
1170         }
1171 if (!$_[0]) {
1172         print "</body>\n";
1173         }
1174 print "</html>\n";
1175 }
1176
1177 =head2 load_theme_library
1178
1179 Immediately loads the current theme's theme.pl file. Not generally useful for
1180 most module developers, as this is called automatically by the header function.
1181
1182 =cut
1183 sub load_theme_library
1184 {
1185 return if (!$current_theme || $loaded_theme_library++);
1186 for(my $i=0; $i<@theme_root_directories; $i++) {
1187         if ($theme_configs[$i]->{'functions'}) {
1188                 do $theme_root_directories[$i]."/".
1189                    $theme_configs[$i]->{'functions'};
1190                 }
1191         }
1192 }
1193
1194 =head2 redirect(url)
1195
1196 Output HTTP headers to redirect the browser to some page. The url parameter is
1197 typically a relative URL like index.cgi or list_users.cgi.
1198
1199 =cut
1200 sub redirect
1201 {
1202 my $port = $ENV{'SERVER_PORT'} == 443 && uc($ENV{'HTTPS'}) eq "ON" ? "" :
1203            $ENV{'SERVER_PORT'} == 80 && uc($ENV{'HTTPS'}) ne "ON" ? "" :
1204                 ":$ENV{'SERVER_PORT'}";
1205 my $prot = uc($ENV{'HTTPS'}) eq "ON" ? "https" : "http";
1206 my $wp = $gconfig{'webprefixnoredir'} ? undef : $gconfig{'webprefix'};
1207 my $url;
1208 if ($_[0] =~ /^(http|https|ftp|gopher):/) {
1209         # Absolute URL (like http://...)
1210         $url = $_[0];
1211         }
1212 elsif ($_[0] =~ /^\//) {
1213         # Absolute path (like /foo/bar.cgi)
1214         $url = "$prot://$ENV{'SERVER_NAME'}$port$wp$_[0]";
1215         }
1216 elsif ($ENV{'SCRIPT_NAME'} =~ /^(.*)\/[^\/]*$/) {
1217         # Relative URL (like foo.cgi)
1218         $url = "$prot://$ENV{'SERVER_NAME'}$port$wp$1/$_[0]";
1219         }
1220 else {
1221         $url = "$prot://$ENV{'SERVER_NAME'}$port/$wp$_[0]";
1222         }
1223 &load_theme_library();
1224 if (defined(&theme_redirect)) {
1225         $module_name = &get_module_name();      # Old themes use these
1226         %module_info = &get_module_info($module_name);
1227         &theme_redirect($_[0], $url);
1228         }
1229 else {
1230         print "Location: $url\n\n";
1231         }
1232 }
1233
1234 =head2 kill_byname(name, signal)
1235
1236 Finds a process whose command line contains the given name (such as httpd), and
1237 sends some signal to it. The signal can be numeric (like 9) or named
1238 (like KILL).
1239
1240 =cut
1241 sub kill_byname
1242 {
1243 my @pids = &find_byname($_[0]);
1244 return scalar(@pids) if (&is_readonly_mode());
1245 &webmin_debug_log('KILL', "signal=$_[1] name=$_[0]")
1246         if ($gconfig{'debug_what_procs'});
1247 if (@pids) { kill($_[1], @pids); return scalar(@pids); }
1248 else { return 0; }
1249 }
1250
1251 =head2 kill_byname_logged(name, signal)
1252
1253 Like kill_byname, but also logs the killing.
1254
1255 =cut
1256 sub kill_byname_logged
1257 {
1258 my @pids = &find_byname($_[0]);
1259 return scalar(@pids) if (&is_readonly_mode());
1260 if (@pids) { &kill_logged($_[1], @pids); return scalar(@pids); }
1261 else { return 0; }
1262 }
1263
1264 =head2 find_byname(name)
1265
1266 Finds processes searching for the given name in their command lines, and
1267 returns a list of matching PIDs.
1268
1269 =cut
1270 sub find_byname
1271 {
1272 if ($gconfig{'os_type'} =~ /-linux$/ && -r "/proc/$$/cmdline") {
1273         # Linux with /proc filesystem .. use cmdline files, as this is
1274         # faster than forking
1275         my @pids;
1276         opendir(PROCDIR, "/proc");
1277         foreach my $f (readdir(PROCDIR)) {
1278                 if ($f eq int($f) && $f != $$) {
1279                         my $line = &read_file_contents("/proc/$f/cmdline");
1280                         if ($line =~ /$_[0]/) {
1281                                 push(@pids, $f);
1282                                 }
1283                         }
1284                 }
1285         closedir(PROCDIR);
1286         return @pids;
1287         }
1288
1289 if (&foreign_check("proc")) {
1290         # Call the proc module
1291         &foreign_require("proc", "proc-lib.pl");
1292         if (defined(&proc::list_processes)) {
1293                 my @procs = &proc::list_processes();
1294                 my @pids;
1295                 foreach my $p (@procs) {
1296                         if ($p->{'args'} =~ /$_[0]/) {
1297                                 push(@pids, $p->{'pid'});
1298                                 }
1299                         }
1300                 @pids = grep { $_ != $$ } @pids;
1301                 return @pids;
1302                 }
1303         }
1304
1305 # Fall back to running a command
1306 my ($cmd, @pids);
1307 $cmd = $gconfig{'find_pid_command'};
1308 $cmd =~ s/NAME/"$_[0]"/g;
1309 $cmd = &translate_command($cmd);
1310 @pids = split(/\n/, `($cmd) <$null_file 2>$null_file`);
1311 @pids = grep { $_ != $$ } @pids;
1312 return @pids;
1313 }
1314
1315 =head2 error([message]+)
1316
1317 Display an error message and exit. This should be used by CGI scripts that
1318 encounter a fatal error or invalid user input to notify users of the problem.
1319 If error_setup has been called, the displayed error message will be prefixed
1320 by the message setup using that function.
1321
1322 =cut
1323 sub error
1324 {
1325 $main::no_miniserv_userdb = 1;
1326 my $msg = join("", @_);
1327 $msg =~ s/<[^>]*>//g;
1328 if (!$main::error_must_die) {
1329         print STDERR "Error: ",$msg,"\n";
1330         }
1331 &load_theme_library();
1332 if ($main::error_must_die) {
1333         if ($gconfig{'error_stack'}) {
1334                 print STDERR "Error: ",$msg,"\n";
1335                 for(my $i=0; my @stack = caller($i); $i++) {
1336                         print STDERR "File: $stack[1] Line: $stack[2] ",
1337                                      "Function: $stack[3]\n";
1338                         }
1339                 }
1340         die @_;
1341         }
1342 elsif (!$ENV{'REQUEST_METHOD'}) {
1343         # Show text-only error
1344         print STDERR "$text{'error'}\n";
1345         print STDERR "-----\n";
1346         print STDERR ($main::whatfailed ? "$main::whatfailed : " : ""),
1347                      $msg,"\n";
1348         print STDERR "-----\n";
1349         if ($gconfig{'error_stack'}) {
1350                 # Show call stack
1351                 print STDERR $text{'error_stack'},"\n";
1352                 for(my $i=0; my @stack = caller($i); $i++) {
1353                         print STDERR &text('error_stackline',
1354                                 $stack[1], $stack[2], $stack[3]),"\n";
1355                         }
1356                 }
1357
1358         }
1359 elsif (defined(&theme_error)) {
1360         &theme_error(@_);
1361         }
1362 else {
1363         &header($text{'error'}, "");
1364         print "<hr>\n";
1365         print "<h3>",($main::whatfailed ? "$main::whatfailed : " : ""),
1366                      @_,"</h3>\n";
1367         if ($gconfig{'error_stack'}) {
1368                 # Show call stack
1369                 print "<h3>$text{'error_stack'}</h3>\n";
1370                 print "<table>\n";
1371                 print "<tr> <td><b>$text{'error_file'}</b></td> ",
1372                       "<td><b>$text{'error_line'}</b></td> ",
1373                       "<td><b>$text{'error_sub'}</b></td> </tr>\n";
1374                 for($i=0; my @stack = caller($i); $i++) {
1375                         print "<tr>\n";
1376                         print "<td>$stack[1]</td>\n";
1377                         print "<td>$stack[2]</td>\n";
1378                         print "<td>$stack[3]</td>\n";
1379                         print "</tr>\n";
1380                         }
1381                 print "</table>\n";
1382                 }
1383         print "<hr>\n";
1384         if ($ENV{'HTTP_REFERER'} && $main::completed_referers_check) {
1385                 &footer($ENV{'HTTP_REFERER'}, $text{'error_previous'});
1386                 }
1387         else {
1388                 &footer();
1389                 }
1390         }
1391 &unlock_all_files();
1392 &cleanup_tempnames();
1393 exit(1);
1394 }
1395
1396 =head2 popup_error([message]+)
1397
1398 This function is almost identical to error, but displays the message with HTML
1399 headers suitable for a popup window.
1400
1401 =cut
1402 sub popup_error
1403 {
1404 $main::no_miniserv_userdb = 1;
1405 &load_theme_library();
1406 if ($main::error_must_die) {
1407         die @_;
1408         }
1409 elsif (defined(&theme_popup_error)) {
1410         &theme_popup_error(@_);
1411         }
1412 else {
1413         &popup_header($text{'error'}, "");
1414         print "<h3>",($main::whatfailed ? "$main::whatfailed : " : ""),@_,"</h3>\n";
1415         &popup_footer();
1416         }
1417 &unlock_all_files();
1418 &cleanup_tempnames();
1419 exit;
1420 }
1421
1422 =head2 error_setup(message)
1423
1424 Registers a message to be prepended to all error messages displayed by the 
1425 error function.
1426
1427 =cut
1428 sub error_setup
1429 {
1430 $main::whatfailed = $_[0];
1431 }
1432
1433 =head2 wait_for(handle, regexp, regexp, ...)
1434
1435 Reads from the input stream until one of the regexps matches, and returns the
1436 index of the matching regexp, or -1 if input ended before any matched. This is
1437 very useful for parsing the output of interactive programs, and can be used with
1438 a two-way pipe to feed input to a program in response to output matched by
1439 this function.
1440
1441 If the matching regexp contains bracketed sub-expressions, their values will
1442 be placed in the global array @matches, indexed starting from 1. You cannot
1443 use the Perl variables $1, $2 and so on to capture matches.
1444
1445 Example code:
1446
1447  $rv = wait_for($loginfh, "username:");
1448  if ($rv == -1) {
1449    error("Didn't get username prompt");
1450  }
1451  print $loginfh "joe\n";
1452  $rv = wait_for($loginfh, "password:");
1453  if ($rv == -1) {
1454    error("Didn't get password prompt");
1455  }
1456  print $loginfh "smeg\n";
1457
1458 =cut
1459 sub wait_for
1460 {
1461 my ($c, $i, $sw, $rv, $ha);
1462 undef($wait_for_input);
1463 if ($wait_for_debug) {
1464         print STDERR "wait_for(",join(",", @_),")\n";
1465         }
1466 $ha = &callers_package($_[0]);
1467 if ($wait_for_debug) {
1468         print STDERR "File handle=$ha fd=",fileno($ha),"\n";
1469         }
1470 $codes =
1471 "my \$hit;\n".
1472 "while(1) {\n".
1473 " if ((\$c = getc(\$ha)) eq \"\") { return -1; }\n".
1474 " \$wait_for_input .= \$c;\n";
1475 if ($wait_for_debug) {
1476         $codes .= "print STDERR \$wait_for_input,\"\\n\";";
1477         }
1478 for($i=1; $i<@_; $i++) {
1479         $sw = $i>1 ? "elsif" : "if";
1480         $codes .= " $sw (\$wait_for_input =~ /$_[$i]/i) { \$hit = $i-1; }\n";
1481         }
1482 $codes .=
1483 " if (defined(\$hit)) {\n".
1484 "  \@matches = (-1, \$1, \$2, \$3, \$4, \$5, \$6, \$7, \$8, \$9);\n".
1485 "  return \$hit;\n".
1486 "  }\n".
1487 " }\n";
1488 $rv = eval $codes;
1489 if ($@) {
1490         &error("wait_for error : $@\n");
1491         }
1492 return $rv;
1493 }
1494
1495 =head2 fast_wait_for(handle, string, string, ...)
1496
1497 This function behaves very similar to wait_for (documented above), but instead
1498 of taking regular expressions as parameters, it takes strings. As soon as the
1499 input contains one of them, it will return the index of the matching string.
1500 If the input ends before any match, it returns -1.
1501
1502 =cut
1503 sub fast_wait_for
1504 {
1505 my ($inp, $maxlen, $ha, $i, $c, $inpl);
1506 for($i=1; $i<@_; $i++) {
1507         $maxlen = length($_[$i]) > $maxlen ? length($_[$i]) : $maxlen;
1508         }
1509 $ha = $_[0];
1510 while(1) {
1511         if (($c = getc($ha)) eq "") {
1512                 &error("fast_wait_for read error : $!");
1513                 }
1514         $inp .= $c;
1515         if (length($inp) > $maxlen) {
1516                 $inp = substr($inp, length($inp)-$maxlen);
1517                 }
1518         $inpl = length($inp);
1519         for($i=1; $i<@_; $i++) {
1520                 if ($_[$i] eq substr($inp, $inpl-length($_[$i]))) {
1521                         return $i-1;
1522                         }
1523                 }
1524         }
1525 }
1526
1527 =head2 has_command(command)
1528
1529 Returns the full path to the executable if some command is in the path, or
1530 undef if not found. If the given command is already an absolute path and
1531 exists, then the same path will be returned.
1532
1533 =cut
1534 sub has_command
1535 {
1536 if (!$_[0]) { return undef; }
1537 if (exists($main::has_command_cache{$_[0]})) {
1538         return $main::has_command_cache{$_[0]};
1539         }
1540 my $rv = undef;
1541 my $slash = $gconfig{'os_type'} eq 'windows' ? '\\' : '/';
1542 if ($_[0] =~ /^\// || $_[0] =~ /^[a-z]:[\\\/]/i) {
1543         # Absolute path given - just use it
1544         my $t = &translate_filename($_[0]);
1545         $rv = (-x $t && !-d _) ? $_[0] : undef;
1546         }
1547 else {
1548         # Check each directory in the path
1549         my %donedir;
1550         foreach my $d (split($path_separator, $ENV{'PATH'})) {
1551                 next if ($donedir{$d}++);
1552                 $d =~ s/$slash$// if ($d ne $slash);
1553                 my $t = &translate_filename("$d/$_[0]");
1554                 if (-x $t && !-d _) {
1555                         $rv = $d.$slash.$_[0];
1556                         last;
1557                         }
1558                 if ($gconfig{'os_type'} eq 'windows') {
1559                         foreach my $sfx (".exe", ".com", ".bat") {
1560                                 my $t = &translate_filename("$d/$_[0]").$sfx;
1561                                 if (-r $t && !-d _) {
1562                                         $rv = $d.$slash.$_[0].$sfx;
1563                                         last;
1564                                         }
1565                                 }
1566                         }
1567                 }
1568         }
1569 $main::has_command_cache{$_[0]} = $rv;
1570 return $rv;
1571 }
1572
1573 =head2 make_date(seconds, [date-only], [fmt])
1574
1575 Converts a Unix date/time in seconds to a human-readable form, by default
1576 formatted like dd/mmm/yyyy hh:mm:ss. Parameters are :
1577
1578 =item seconds - Unix time is seconds to convert.
1579
1580 =item date-only - If set to 1, exclude the time from the returned string.
1581
1582 =item fmt - Optional, one of dd/mon/yyyy, dd/mm/yyyy, mm/dd/yyyy or yyyy/mm/dd
1583
1584 =cut
1585 sub make_date
1586 {
1587 my ($secs, $only, $fmt) = @_;
1588 my @tm = localtime($secs);
1589 my $date;
1590 if (!$fmt) {
1591         $fmt = $gconfig{'dateformat'} || 'dd/mon/yyyy';
1592         }
1593 if ($fmt eq 'dd/mon/yyyy') {
1594         $date = sprintf "%2.2d/%s/%4.4d",
1595                         $tm[3], $text{"smonth_".($tm[4]+1)}, $tm[5]+1900;
1596         }
1597 elsif ($fmt eq 'dd/mm/yyyy') {
1598         $date = sprintf "%2.2d/%2.2d/%4.4d", $tm[3], $tm[4]+1, $tm[5]+1900;
1599         }
1600 elsif ($fmt eq 'mm/dd/yyyy') {
1601         $date = sprintf "%2.2d/%2.2d/%4.4d", $tm[4]+1, $tm[3], $tm[5]+1900;
1602         }
1603 elsif ($fmt eq 'yyyy/mm/dd') {
1604         $date = sprintf "%4.4d/%2.2d/%2.2d", $tm[5]+1900, $tm[4]+1, $tm[3];
1605         }
1606 elsif ($fmt eq 'd. mon yyyy') {
1607         $date = sprintf "%d. %s %4.4d",
1608                         $tm[3], $text{"smonth_".($tm[4]+1)}, $tm[5]+1900;
1609         }
1610 elsif ($fmt eq 'dd.mm.yyyy') {
1611         $date = sprintf "%2.2d.%2.2d.%4.4d", $tm[3], $tm[4]+1, $tm[5]+1900;
1612         }
1613 elsif ($fmt eq 'yyyy-mm-dd') {
1614         $date = sprintf "%4.4d-%2.2d-%2.2d", $tm[5]+1900, $tm[4]+1, $tm[3];
1615         }
1616 if (!$only) {
1617         $date .= sprintf " %2.2d:%2.2d", $tm[2], $tm[1];
1618         }
1619 return $date;
1620 }
1621
1622 =head2 file_chooser_button(input, type, [form], [chroot], [addmode])
1623
1624 Return HTML for a button that pops up a file chooser when clicked, and places
1625 the selected filename into another HTML field. The parameters are :
1626
1627 =item input - Name of the form field to store the filename in.
1628
1629 =item type - 0 for file or directory chooser, or 1 for directory only.
1630
1631 =item form - Index of the form containing the button.
1632
1633 =item chroot - If set to 1, the chooser will be limited to this directory.
1634
1635 =item addmode - If set to 1, the selected filename will be appended to the text box instead of replacing it's contents.
1636
1637 =cut
1638 sub file_chooser_button
1639 {
1640 return &theme_file_chooser_button(@_)
1641         if (defined(&theme_file_chooser_button));
1642 my $form = defined($_[2]) ? $_[2] : 0;
1643 my $chroot = defined($_[3]) ? $_[3] : "/";
1644 my $add = int($_[4]);
1645 my ($w, $h) = (400, 300);
1646 if ($gconfig{'db_sizefile'}) {
1647         ($w, $h) = split(/x/, $gconfig{'db_sizefile'});
1648         }
1649 return "<input type=button onClick='ifield = form.$_[0]; chooser = window.open(\"$gconfig{'webprefix'}/chooser.cgi?add=$add&type=$_[1]&chroot=$chroot&file=\"+escape(ifield.value), \"chooser\", \"toolbar=no,menubar=no,scrollbars=no,resizable=yes,width=$w,height=$h\"); chooser.ifield = ifield; window.ifield = ifield' value=\"...\">\n";
1650 }
1651
1652 =head2 popup_window_button(url, width, height, scrollbars?, &field-mappings)
1653
1654 Returns HTML for a button that will popup a chooser window of some kind. The
1655 parameters are :
1656
1657 =item url - Base URL of the popup window's contents
1658
1659 =item width - Width of the window in pixels
1660
1661 =item height - Height in pixels
1662
1663 =item scrollbars - Set to 1 if the window should have scrollbars
1664
1665 The field-mappings parameter is an array ref of array refs containing
1666
1667 =item - Attribute to assign field to in the popup window
1668
1669 =item - Form field name
1670
1671 =item - CGI parameter to URL for value, if any
1672
1673 =cut
1674 sub popup_window_button
1675 {
1676 return &theme_popup_window_button(@_) if (defined(&theme_popup_window_button));
1677 my ($url, $w, $h, $scroll, $fields) = @_;
1678 my $scrollyn = $scroll ? "yes" : "no";
1679 my $rv = "<input type=button onClick='";
1680 foreach my $m (@$fields) {
1681         $rv .= "$m->[0] = form.$m->[1]; ";
1682         }
1683 my $sep = $url =~ /\?/ ? "&" : "?";
1684 $rv .= "chooser = window.open(\"$url\"";
1685 foreach my $m (@$fields) {
1686         if ($m->[2]) {
1687                 $rv .= "+\"$sep$m->[2]=\"+escape($m->[0].value)";
1688                 $sep = "&";
1689                 }
1690         }
1691 $rv .= ", \"chooser\", \"toolbar=no,menubar=no,scrollbars=$scrollyn,resizable=yes,width=$w,height=$h\"); ";
1692 foreach my $m (@$fields) {
1693         $rv .= "chooser.$m->[0] = $m->[0]; ";
1694         $rv .= "window.$m->[0] = $m->[0]; ";
1695         }
1696 $rv .= "' value=\"...\">";
1697 return $rv;
1698 }
1699
1700 =head2 read_acl(&user-module-hash, &user-list-hash, [&only-users])
1701
1702 Reads the Webmin acl file into the given hash references. The first is indexed
1703 by a combined key of username,module , with the value being set to 1 when
1704 the user has access to that module. The second is indexed by username, with
1705 the value being an array ref of allowed modules.
1706
1707 This function is deprecated in favour of foreign_available, which performs a
1708 more comprehensive check of module availability.
1709
1710 If the only-users array ref parameter is given, the results may be limited to
1711 users in that list of names.
1712
1713 =cut
1714 sub read_acl
1715 {
1716 my ($usermod, $userlist, $only) = @_;
1717 if (!%main::acl_hash_cache) {
1718         # Read from local files
1719         local $_;
1720         open(ACL, &acl_filename());
1721         while(<ACL>) {
1722                 if (/^([^:]+):\s*(.*)/) {
1723                         my $user = $1;
1724                         my @mods = split(/\s+/, $2);
1725                         foreach my $m (@mods) {
1726                                 $main::acl_hash_cache{$user,$m}++;
1727                                 }
1728                         $main::acl_array_cache{$user} = \@mods;
1729                         }
1730                 }
1731         close(ACL);
1732         }
1733 %$usermod = %main::acl_hash_cache if ($usermod);
1734 %$userlist = %main::acl_array_cache if ($userlist);
1735
1736 # Read from user DB
1737 my $userdb = &get_userdb_string();
1738 my ($dbh, $proto, $prefix, $args) =
1739         $userdb ? &connect_userdb($userdb) : ( );
1740 if (ref($dbh)) {
1741         if ($proto eq "mysql" || $proto eq "postgresql") {
1742                 # Select usernames and modules from SQL DB
1743                 my $cmd = $dbh->prepare(
1744                         "select webmin_user.name,webmin_user_attr.value ".
1745                         "from webmin_user,webmin_user_attr ".
1746                         "where webmin_user.id = webmin_user_attr.id ".
1747                         "and webmin_user_attr.attr = 'modules' ".
1748                         ($only ? " and webmin_user.name in (".
1749                                  join(",", map { "'$_'" } @$only).")" : ""));
1750                 if ($cmd && $cmd->execute()) {
1751                         while(my ($user, $mods) = $cmd->fetchrow()) {
1752                                 my @mods = split(/\s+/, $mods);
1753                                 foreach my $m (@mods) {
1754                                         $usermod->{$user,$m}++ if ($usermod);
1755                                         }
1756                                 $userlist->{$user} = \@mods if ($userlist);
1757                                 }
1758                         }
1759                 $cmd->finish() if ($cmd);
1760                 }
1761         elsif ($proto eq "ldap") {
1762                 # Find users in LDAP
1763                 my $filter = '(objectClass='.$args->{'userclass'}.')';
1764                 if ($only) {
1765                         my $ufilter =
1766                                 "(|".join("", map { "(cn=$_)" } @$only).")";
1767                         $filter = "(&".$filter.$ufilter.")";
1768                         }
1769                 my $rv = $dbh->search(
1770                         base => $prefix,
1771                         filter => $filter,
1772                         scope => 'sub',
1773                         attrs => [ 'cn', 'webminModule' ]);
1774                 if ($rv && !$rv->code) {
1775                         foreach my $u ($rv->all_entries) {
1776                                 my $user = $u->get_value('cn');
1777                                 my @mods =$u->get_value('webminModule');
1778                                 foreach my $m (@mods) {
1779                                         $usermod->{$user,$m}++ if ($usermod);
1780                                         }
1781                                 $userlist->{$user} = \@mods if ($userlist);
1782                                 }
1783                         }
1784                 }
1785         &disconnect_userdb($userdb, $dbh);
1786         }
1787 }
1788
1789 =head2 acl_filename
1790
1791 Returns the file containing the webmin ACL, which is usually
1792 /etc/webmin/webmin.acl.
1793
1794 =cut
1795 sub acl_filename
1796 {
1797 return "$config_directory/webmin.acl";
1798 }
1799
1800 =head2 acl_check
1801
1802 Does nothing, but kept around for compatability.
1803
1804 =cut
1805 sub acl_check
1806 {
1807 }
1808
1809 =head2 get_miniserv_config(&hash)
1810
1811 Reads the Webmin webserver's (miniserv.pl) configuration file, usually located
1812 at /etc/webmin/miniserv.conf, and stores its names and values in the given
1813 hash reference.
1814
1815 =cut
1816 sub get_miniserv_config
1817 {
1818 return &read_file_cached(
1819         $ENV{'MINISERV_CONFIG'} || "$config_directory/miniserv.conf", $_[0]);
1820 }
1821
1822 =head2 put_miniserv_config(&hash)
1823
1824 Writes out the Webmin webserver configuration file from the contents of
1825 the given hash ref. This should be initially populated by get_miniserv_config,
1826 like so :
1827
1828  get_miniserv_config(\%miniserv);
1829  $miniserv{'port'} = 10005;
1830  put_miniserv_config(\%miniserv);
1831  restart_miniserv();
1832
1833 =cut
1834 sub put_miniserv_config
1835 {
1836 &write_file($ENV{'MINISERV_CONFIG'} || "$config_directory/miniserv.conf",
1837             $_[0]);
1838 }
1839
1840 =head2 restart_miniserv([nowait])
1841
1842 Kill the old miniserv process and re-start it, then optionally waits for
1843 it to restart. This will apply all configuration settings.
1844
1845 =cut
1846 sub restart_miniserv
1847 {
1848 my ($nowait) = @_;
1849 return undef if (&is_readonly_mode());
1850 my %miniserv;
1851 &get_miniserv_config(\%miniserv) || return;
1852
1853 my $i;
1854 if ($gconfig{'os_type'} ne 'windows') {
1855         # On Unix systems, we can restart with a signal
1856         my ($pid, $addr, $i);
1857         $miniserv{'inetd'} && return;
1858         my @oldst = stat($miniserv{'pidfile'});
1859         $pid = $ENV{'MINISERV_PID'};
1860         if (!$pid) {
1861                 open(PID, $miniserv{'pidfile'}) ||
1862                         &error("Failed to open PID file $miniserv{'pidfile'}");
1863                 chop($pid = <PID>);
1864                 close(PID);
1865                 $pid || &error("Invalid PID file $miniserv{'pidfile'}");
1866                 }
1867
1868         # Just signal miniserv to restart
1869         &kill_logged('HUP', $pid) || &error("Incorrect Webmin PID $pid");
1870
1871         # Wait till new PID is written, indicating a restart
1872         for($i=0; $i<60; $i++) {
1873                 sleep(1);
1874                 my @newst = stat($miniserv{'pidfile'});
1875                 last if ($newst[9] != $oldst[9]);
1876                 }
1877         $i < 60 || &error("Webmin server did not write new PID file");
1878
1879         ## Totally kill the process and re-run it
1880         #$SIG{'TERM'} = 'IGNORE';
1881         #&kill_logged('TERM', $pid);
1882         #&system_logged("$config_directory/start >/dev/null 2>&1 </dev/null");
1883         }
1884 else {
1885         # On Windows, we need to use the flag file
1886         open(TOUCH, ">$miniserv{'restartflag'}");
1887         close(TOUCH);
1888         }
1889
1890 if (!$nowait) {
1891         # Wait for miniserv to come back up
1892         my $addr = $miniserv{'bind'} || "127.0.0.1";
1893         my $ok = 0;
1894         for($i=0; $i<20; $i++) {
1895                 my $err;
1896                 sleep(1);
1897                 &open_socket($addr, $miniserv{'port'}, STEST, \$err);
1898                 close(STEST);
1899                 last if (!$err && ++$ok >= 2);
1900                 }
1901         $i < 20 || &error("Failed to restart Webmin server!");
1902         }
1903 }
1904
1905 =head2 reload_miniserv
1906
1907 Sends a USR1 signal to the miniserv process, telling it to read-read it's
1908 configuration files. Not all changes will be applied though, such as the 
1909 IP addresses and ports to accept connections on.
1910
1911 =cut
1912 sub reload_miniserv
1913 {
1914 return undef if (&is_readonly_mode());
1915 my %miniserv;
1916 &get_miniserv_config(\%miniserv) || return;
1917
1918 if ($gconfig{'os_type'} ne 'windows') {
1919         # Send a USR1 signal to re-read the config
1920         my ($pid, $addr, $i);
1921         $miniserv{'inetd'} && return;
1922         $pid = $ENV{'MINISERV_PID'};
1923         if (!$pid) {
1924                 open(PID, $miniserv{'pidfile'}) ||
1925                         &error("Failed to open PID file $miniserv{'pidfile'}");
1926                 chop($pid = <PID>);
1927                 close(PID);
1928                 $pid || &error("Invalid PID file $miniserv{'pidfile'}");
1929                 }
1930         &kill_logged('USR1', $pid) || &error("Incorrect Webmin PID $pid");
1931
1932         # Make sure this didn't kill Webmin!
1933         sleep(1);
1934         if (!kill(0, $pid)) {
1935                 print STDERR "USR1 signal killed Webmin - restarting\n";
1936                 &system_logged("$config_directory/start >/dev/null 2>&1 </dev/null");
1937                 }
1938         }
1939 else {
1940         # On Windows, we need to use the flag file
1941         open(TOUCH, ">$miniserv{'reloadflag'}");
1942         close(TOUCH);
1943         }
1944 }
1945
1946 =head2 check_os_support(&minfo, [os-type, os-version], [api-only])
1947
1948 Returns 1 if some module is supported on the current operating system, or the
1949 OS supplies as parameters. The parameters are :
1950
1951 =item minfo - A hash ref of module information, as returned by get_module_info
1952
1953 =item os-type - The Webmin OS code to use instead of the system's real OS, such as redhat-linux
1954
1955 =item os-version - The Webmin OS version to use, such as 13.0
1956
1957 =item api-only - If set to 1, considers a module supported if it provides an API to other modules on this OS, even if the majority of its functionality is not supported.
1958
1959 =cut
1960 sub check_os_support
1961 {
1962 my $oss = $_[0]->{'os_support'};
1963 if ($_[3] && $oss && $_[0]->{'api_os_support'}) {
1964         # May provide usable API
1965         $oss .= " ".$_[0]->{'api_os_support'};
1966         }
1967 if ($_[0]->{'nozone'} && &running_in_zone()) {
1968         # Not supported in a Solaris Zone
1969         return 0;
1970         }
1971 if ($_[0]->{'novserver'} && &running_in_vserver()) {
1972         # Not supported in a Linux Vserver
1973         return 0;
1974         }
1975 if ($_[0]->{'noopenvz'} && &running_in_openvz()) {
1976         # Not supported in an OpenVZ container
1977         return 0;
1978         }
1979 return 1 if (!$oss || $oss eq '*');
1980 my $osver = $_[2] || $gconfig{'os_version'};
1981 my $ostype = $_[1] || $gconfig{'os_type'};
1982 my $anyneg = 0;
1983 while(1) {
1984         my ($os, $ver, $codes);
1985         my ($neg) = ($oss =~ s/^!//);   # starts with !
1986         $anyneg++ if ($neg);
1987         if ($oss =~ /^([^\/\s]+)\/([^\{\s]+)\{([^\}]*)\}\s*(.*)$/) {
1988                 # OS/version{code}
1989                 $os = $1; $ver = $2; $codes = $3; $oss = $4;
1990                 }
1991         elsif ($oss =~ /^([^\/\s]+)\/([^\/\s]+)\s*(.*)$/) {
1992                 # OS/version
1993                 $os = $1; $ver = $2; $oss = $3;
1994                 }
1995         elsif ($oss =~ /^([^\{\s]+)\{([^\}]*)\}\s*(.*)$/) {
1996                 # OS/{code}
1997                 $os = $1; $codes = $2; $oss = $3;
1998                 }
1999         elsif ($oss =~ /^\{([^\}]*)\}\s*(.*)$/) {
2000                 # {code}
2001                 $codes = $1; $oss = $2;
2002                 }
2003         elsif ($oss =~ /^(\S+)\s*(.*)$/) {
2004                 # OS
2005                 $os = $1; $oss = $2;
2006                 }
2007         else { last; }
2008         next if ($os && !($os eq $ostype ||
2009                           $ostype =~ /^(\S+)-(\S+)$/ && $os eq "*-$2"));
2010         if ($ver =~ /^([0-9\.]+)\-([0-9\.]+)$/) {
2011                 next if ($osver < $1 || $osver > $2);
2012                 }
2013         elsif ($ver =~ /^([0-9\.]+)\-\*$/) {
2014                 next if ($osver < $1);
2015                 }
2016         elsif ($ver =~ /^\*\-([0-9\.]+)$/) {
2017                 next if ($osver > $1);
2018                 }
2019         elsif ($ver) {
2020                 next if ($ver ne $osver);
2021                 }
2022         next if ($codes && !eval $codes);
2023         return !$neg;
2024         }
2025 return $anyneg;
2026 }
2027
2028 =head2 http_download(host, port, page, destfile, [&error], [&callback], [sslmode], [user, pass], [timeout], [osdn-convert], [no-cache], [&headers])
2029
2030 Downloads data from a HTTP url to a local file or string. The parameters are :
2031
2032 =item host - The hostname part of the URL, such as www.google.com
2033
2034 =item port - The HTTP port number, such as 80
2035
2036 =item page - The filename part of the URL, like /index.html
2037
2038 =item destfile - The local file to save the URL data to, like /tmp/index.html. This can also be a scalar reference, in which case the data will be appended to that scalar.
2039
2040 =item error - If set to a scalar ref, the function will store any error message in this scalar and return 0 on failure, or 1 on success. If not set, it will simply call the error function if the download fails.
2041
2042 =item callback - If set to a function ref, it will be called after each block of data is received. This is typically set to \&progress_callback, for printing download progress.
2043
2044 =item sslmode - If set to 1, an HTTPS connection is used instead of HTTP.
2045
2046 =item user - If set, HTTP authentication is done with this username.
2047
2048 =item pass - The HTTP password to use with the username above.
2049
2050 =item timeout - A timeout in seconds to wait for the TCP connection to be established before failing.
2051
2052 =item osdn-convert - If set to 1, URL for downloads from sourceforge are converted to use an appropriate mirror site.
2053
2054 =item no-cache - If set to 1, Webmin's internal caching for this URL is disabled.
2055
2056 =item headers - If set to a hash ref of additional HTTP headers, they will be added to the request.
2057
2058 =cut
2059 sub http_download
2060 {
2061 my ($host, $port, $page, $dest, $error, $cbfunc, $ssl, $user, $pass,
2062     $timeout, $osdn, $nocache, $headers) = @_;
2063 if ($gconfig{'debug_what_net'}) {
2064         &webmin_debug_log('HTTP', "host=$host port=$port page=$page ssl=$ssl".
2065                                   ($user ? " user=$user pass=$pass" : "").
2066                                   (ref($dest) ? "" : " dest=$dest"));
2067         }
2068 if ($osdn) {
2069         # Convert OSDN URL first
2070         my $prot = $ssl ? "https://" : "http://";
2071         my $portstr = $ssl && $port == 443 ||
2072                          !$ssl && $port == 80 ? "" : ":$port";
2073         ($host, $port, $page, $ssl) = &parse_http_url(
2074                 &convert_osdn_url($prot.$host.$portstr.$page));
2075         }
2076
2077 # Check if we already have cached the URL
2078 my $url = ($ssl ? "https://" : "http://").$host.":".$port.$page;
2079 my $cfile = &check_in_http_cache($url);
2080 if ($cfile && !$nocache) {
2081         # Yes! Copy to dest file or variable
2082         &$cbfunc(6, $url) if ($cbfunc);
2083         if (ref($dest)) {
2084                 &open_readfile(CACHEFILE, $cfile);
2085                 local $/ = undef;
2086                 $$dest = <CACHEFILE>;
2087                 close(CACHEFILE);
2088                 }
2089         else {
2090                 &copy_source_dest($cfile, $dest);
2091                 }
2092         return;
2093         }
2094
2095 # Build headers
2096 my @headers;
2097 push(@headers, [ "Host", $host ]);
2098 push(@headers, [ "User-agent", "Webmin" ]);
2099 push(@headers, [ "Accept-language", "en" ]);
2100 if ($user) {
2101         my $auth = &encode_base64("$user:$pass");
2102         $auth =~ tr/\r\n//d;
2103         push(@headers, [ "Authorization", "Basic $auth" ]);
2104         }
2105 foreach my $hname (keys %$headers) {
2106         push(@headers, [ $hname, $headers->{$hname} ]);
2107         }
2108
2109 # Actually download it
2110 $main::download_timed_out = undef;
2111 local $SIG{ALRM} = \&download_timeout;
2112 alarm($timeout || 60);
2113 my $h = &make_http_connection($host, $port, $ssl, "GET", $page, \@headers);
2114 alarm(0);
2115 $h = $main::download_timed_out if ($main::download_timed_out);
2116 if (!ref($h)) {
2117         if ($error) { $$error = $h; return; }
2118         else { &error($h); }
2119         }
2120 &complete_http_download($h, $dest, $error, $cbfunc, $osdn, $host, $port,
2121                         $headers, $ssl, $nocache);
2122 if ((!$error || !$$error) && !$nocache) {
2123         &write_to_http_cache($url, $dest);
2124         }
2125 }
2126
2127 =head2 complete_http_download(handle, destfile, [&error], [&callback], [osdn], [oldhost], [oldport], [&send-headers], [old-ssl], [no-cache])
2128
2129 Do a HTTP download, after the headers have been sent. For internal use only,
2130 typically called by http_download.
2131
2132 =cut
2133 sub complete_http_download
2134 {
2135 local ($line, %header, @headers, $s);  # Kept local so that callback funcs
2136                                        # can access them.
2137 my $cbfunc = $_[3];
2138
2139 # read headers
2140 alarm(60);
2141 ($line = &read_http_connection($_[0])) =~ tr/\r\n//d;
2142 if ($line !~ /^HTTP\/1\..\s+(200|30[0-9])(\s+|$)/) {
2143         alarm(0);
2144         if ($_[2]) { ${$_[2]} = $line; return; }
2145         else { &error("Download failed : $line"); }
2146         }
2147 my $rcode = $1;
2148 &$cbfunc(1, $rcode >= 300 && $rcode < 400 ? 1 : 0)
2149         if ($cbfunc);
2150 while(1) {
2151         $line = &read_http_connection($_[0]);
2152         $line =~ tr/\r\n//d;
2153         $line =~ /^(\S+):\s+(.*)$/ || last;
2154         $header{lc($1)} = $2;
2155         push(@headers, [ lc($1), $2 ]);
2156         }
2157 alarm(0);
2158 if ($main::download_timed_out) {
2159         if ($_[2]) { ${$_[2]} = $main::download_timed_out; return 0; }
2160         else { &error($main::download_timed_out); }
2161         }
2162 &$cbfunc(2, $header{'content-length'}) if ($cbfunc);
2163 if ($rcode >= 300 && $rcode < 400) {
2164         # follow the redirect
2165         &$cbfunc(5, $header{'location'}) if ($cbfunc);
2166         my ($host, $port, $page, $ssl);
2167         if ($header{'location'} =~ /^(http|https):\/\/([^:]+):(\d+)(\/.*)?$/) {
2168                 $ssl = $1 eq 'https' ? 1 : 0;
2169                 $host = $2; $port = $3; $page = $4 || "/";
2170                 }
2171         elsif ($header{'location'} =~ /^(http|https):\/\/([^:\/]+)(\/.*)?$/) {
2172                 $ssl = $1 eq 'https' ? 1 : 0;
2173                 $host = $2; $port = 80; $page = $3 || "/";
2174                 }
2175         elsif ($header{'location'} =~ /^\// && $_[5]) {
2176                 # Relative to same server
2177                 $host = $_[5];
2178                 $port = $_[6];
2179                 $ssl = $_[8];
2180                 $page = $header{'location'};
2181                 }
2182         elsif ($header{'location'}) {
2183                 # Assume relative to same dir .. not handled
2184                 if ($_[2]) { ${$_[2]} = "Invalid Location header $header{'location'}"; return; }
2185                 else { &error("Invalid Location header $header{'location'}"); }
2186                 }
2187         else {
2188                 if ($_[2]) { ${$_[2]} = "Missing Location header"; return; }
2189                 else { &error("Missing Location header"); }
2190                 }
2191         my $params;
2192         ($page, $params) = split(/\?/, $page);
2193         $page =~ s/ /%20/g;
2194         $page .= "?".$params if (defined($params));
2195         &http_download($host, $port, $page, $_[1], $_[2], $cbfunc, $ssl,
2196                        undef, undef, undef, $_[4], $_[9], $_[7]);
2197         }
2198 else {
2199         # read data
2200         if (ref($_[1])) {
2201                 # Append to a variable
2202                 while(defined($buf = &read_http_connection($_[0], 1024))) {
2203                         ${$_[1]} .= $buf;
2204                         &$cbfunc(3, length(${$_[1]})) if ($cbfunc);
2205                         }
2206                 }
2207         else {
2208                 # Write to a file
2209                 my $got = 0;
2210                 if (!&open_tempfile(PFILE, ">$_[1]", 1)) {
2211                         if ($_[2]) { ${$_[2]} = "Failed to write to $_[1] : $!"; return; }
2212                         else { &error("Failed to write to $_[1] : $!"); }
2213                         }
2214                 binmode(PFILE);         # For windows
2215                 while(defined($buf = &read_http_connection($_[0], 1024))) {
2216                         &print_tempfile(PFILE, $buf);
2217                         $got += length($buf);
2218                         &$cbfunc(3, $got) if ($cbfunc);
2219                         }
2220                 &close_tempfile(PFILE);
2221                 if ($header{'content-length'} &&
2222                     $got != $header{'content-length'}) {
2223                         if ($_[2]) { ${$_[2]} = "Download incomplete"; return; }
2224                         else { &error("Download incomplete"); }
2225                         }
2226                 }
2227         &$cbfunc(4) if ($cbfunc);
2228         }
2229 &close_http_connection($_[0]);
2230 }
2231
2232
2233 =head2 ftp_download(host, file, destfile, [&error], [&callback], [user, pass], [port])
2234
2235 Download data from an FTP site to a local file. The parameters are :
2236
2237 =item host - FTP server hostname
2238
2239 =item file - File on the FTP server to download
2240
2241 =item destfile - File on the Webmin system to download data to
2242
2243 =item error - If set to a string ref, any error message is written into this string and the function returns 0 on failure, 1 on success. Otherwise, error is called on failure.
2244
2245 =item callback - If set to a function ref, it will be called after each block of data is received. This is typically set to \&progress_callback, for printing download progress.
2246
2247 =item user - Username to login to the FTP server as. If missing, Webmin will login as anonymous.
2248
2249 =item pass - Password for the username above.
2250
2251 =item port - FTP server port number, which defaults to 21 if not set.
2252
2253 =cut
2254 sub ftp_download
2255 {
2256 my ($host, $file, $dest, $error, $cbfunc, $user, $pass, $port) = @_;
2257 $port ||= 21;
2258 if ($gconfig{'debug_what_net'}) {
2259         &webmin_debug_log('FTP', "host=$host port=$port file=$file".
2260                                  ($user ? " user=$user pass=$pass" : "").
2261                                  (ref($dest) ? "" : " dest=$dest"));
2262         }
2263 my ($buf, @n);
2264 $cbfunc = $_[4];
2265 if (&is_readonly_mode()) {
2266         if ($_[3]) { ${$_[3]} = "FTP connections not allowed in readonly mode";
2267                      return 0; }
2268         else { &error("FTP connections not allowed in readonly mode"); }
2269         }
2270
2271 # Check if we already have cached the URL
2272 my $url = "ftp://".$host.$file;
2273 my $cfile = &check_in_http_cache($url);
2274 if ($cfile) {
2275         # Yes! Copy to dest file or variable
2276         &$cbfunc(6, $url) if ($cbfunc);
2277         if (ref($dest)) {
2278                 &open_readfile(CACHEFILE, $cfile);
2279                 local $/ = undef;
2280                 $$dest = <CACHEFILE>;
2281                 close(CACHEFILE);
2282                 }
2283         else {
2284                 &copy_source_dest($cfile, $dest);
2285                 }
2286         return;
2287         }
2288
2289 # Actually download it
2290 $main::download_timed_out = undef;
2291 local $SIG{ALRM} = \&download_timeout;
2292 alarm(60);
2293 my $connected;
2294 if ($gconfig{'ftp_proxy'} =~ /^http:\/\/(\S+):(\d+)/ && !&no_proxy($_[0])) {
2295         # download through http-style proxy
2296         my $error;
2297         if (&open_socket($1, $2, "SOCK", \$error)) {
2298                 # Connected OK
2299                 if ($main::download_timed_out) {
2300                         alarm(0);
2301                         if ($_[3]) { ${$_[3]} = $main::download_timed_out; return 0; }
2302                         else { &error($main::download_timed_out); }
2303                         }
2304                 my $esc = $_[1]; $esc =~ s/ /%20/g;
2305                 my $up = "$_[5]:$_[6]\@" if ($_[5]);
2306                 my $portstr = $port == 21 ? "" : ":$port";
2307                 print SOCK "GET ftp://$up$_[0]$portstr$esc HTTP/1.0\r\n";
2308                 print SOCK "User-agent: Webmin\r\n";
2309                 if ($gconfig{'proxy_user'}) {
2310                         my $auth = &encode_base64(
2311                            "$gconfig{'proxy_user'}:$gconfig{'proxy_pass'}");
2312                         $auth =~ tr/\r\n//d;
2313                         print SOCK "Proxy-Authorization: Basic $auth\r\n";
2314                         }
2315                 print SOCK "\r\n";
2316                 &complete_http_download({ 'fh' => "SOCK" }, $_[2], $_[3], $_[4]);
2317                 $connected = 1;
2318                 }
2319         elsif (!$gconfig{'proxy_fallback'}) {
2320                 alarm(0);
2321                 if ($error) { $$error = $main::download_timed_out; return 0; }
2322                 else { &error($main::download_timed_out); }
2323                 }
2324         }
2325
2326 if (!$connected) {
2327         # connect to host and login with real FTP protocol
2328         &open_socket($_[0], $port, "SOCK", $_[3]) || return 0;
2329         alarm(0);
2330         if ($main::download_timed_out) {
2331                 if ($_[3]) { ${$_[3]} = $main::download_timed_out; return 0; }
2332                 else { &error($main::download_timed_out); }
2333                 }
2334         &ftp_command("", 2, $_[3]) || return 0;
2335         if ($_[5]) {
2336                 # Login as supplied user
2337                 my @urv = &ftp_command("USER $_[5]", [ 2, 3 ], $_[3]);
2338                 @urv || return 0;
2339                 if (int($urv[1]/100) == 3) {
2340                         &ftp_command("PASS $_[6]", 2, $_[3]) || return 0;
2341                         }
2342                 }
2343         else {
2344                 # Login as anonymous
2345                 my @urv = &ftp_command("USER anonymous", [ 2, 3 ], $_[3]);
2346                 @urv || return 0;
2347                 if (int($urv[1]/100) == 3) {
2348                         &ftp_command("PASS root\@".&get_system_hostname(), 2,
2349                                      $_[3]) || return 0;
2350                         }
2351                 }
2352         &$cbfunc(1, 0) if ($cbfunc);
2353
2354         if ($_[1]) {
2355                 # get the file size and tell the callback
2356                 &ftp_command("TYPE I", 2, $_[3]) || return 0;
2357                 my $size = &ftp_command("SIZE $_[1]", 2, $_[3]);
2358                 defined($size) || return 0;
2359                 if ($cbfunc) {
2360                         &$cbfunc(2, int($size));
2361                         }
2362
2363                 # request the file
2364                 my $pasv = &ftp_command("PASV", 2, $_[3]);
2365                 defined($pasv) || return 0;
2366                 $pasv =~ /\(([0-9,]+)\)/;
2367                 @n = split(/,/ , $1);
2368                 &open_socket("$n[0].$n[1].$n[2].$n[3]",
2369                         $n[4]*256 + $n[5], "CON", $_[3]) || return 0;
2370                 &ftp_command("RETR $_[1]", 1, $_[3]) || return 0;
2371
2372                 # transfer data
2373                 my $got = 0;
2374                 &open_tempfile(PFILE, ">$_[2]", 1);
2375                 while(read(CON, $buf, 1024) > 0) {
2376                         &print_tempfile(PFILE, $buf);
2377                         $got += length($buf);
2378                         &$cbfunc(3, $got) if ($cbfunc);
2379                         }
2380                 &close_tempfile(PFILE);
2381                 close(CON);
2382                 if ($got != $size) {
2383                         if ($_[3]) { ${$_[3]} = "Download incomplete"; return 0; }
2384                         else { &error("Download incomplete"); }
2385                         }
2386                 &$cbfunc(4) if ($cbfunc);
2387
2388                 &ftp_command("", 2, $_[3]) || return 0;
2389                 }
2390
2391         # finish off..
2392         &ftp_command("QUIT", 2, $_[3]) || return 0;
2393         close(SOCK);
2394         }
2395
2396 &write_to_http_cache($url, $dest);
2397 return 1;
2398 }
2399
2400 =head2 ftp_upload(host, file, srcfile, [&error], [&callback], [user, pass], [port])
2401
2402 Upload data from a local file to an FTP site. The parameters are :
2403
2404 =item host - FTP server hostname
2405
2406 =item file - File on the FTP server to write to
2407
2408 =item srcfile - File on the Webmin system to upload data from
2409
2410 =item error - If set to a string ref, any error message is written into this string and the function returns 0 on failure, 1 on success. Otherwise, error is called on failure.
2411
2412 =item callback - If set to a function ref, it will be called after each block of data is received. This is typically set to \&progress_callback, for printing upload progress.
2413
2414 =item user - Username to login to the FTP server as. If missing, Webmin will login as anonymous.
2415
2416 =item pass - Password for the username above.
2417
2418 =item port - FTP server port number, which defaults to 21 if not set.
2419
2420 =cut
2421 sub ftp_upload
2422 {
2423 my ($buf, @n);
2424 my $cbfunc = $_[4];
2425 if (&is_readonly_mode()) {
2426         if ($_[3]) { ${$_[3]} = "FTP connections not allowed in readonly mode";
2427                      return 0; }
2428         else { &error("FTP connections not allowed in readonly mode"); }
2429         }
2430
2431 $main::download_timed_out = undef;
2432 local $SIG{ALRM} = \&download_timeout;
2433 alarm(60);
2434
2435 # connect to host and login
2436 &open_socket($_[0], $_[7] || 21, "SOCK", $_[3]) || return 0;
2437 alarm(0);
2438 if ($main::download_timed_out) {
2439         if ($_[3]) { ${$_[3]} = $main::download_timed_out; return 0; }
2440         else { &error($main::download_timed_out); }
2441         }
2442 &ftp_command("", 2, $_[3]) || return 0;
2443 if ($_[5]) {
2444         # Login as supplied user
2445         my @urv = &ftp_command("USER $_[5]", [ 2, 3 ], $_[3]);
2446         @urv || return 0;
2447         if (int($urv[1]/100) == 3) {
2448                 &ftp_command("PASS $_[6]", 2, $_[3]) || return 0;
2449                 }
2450         }
2451 else {
2452         # Login as anonymous
2453         my @urv = &ftp_command("USER anonymous", [ 2, 3 ], $_[3]);
2454         @urv || return 0;
2455         if (int($urv[1]/100) == 3) {
2456                 &ftp_command("PASS root\@".&get_system_hostname(), 2,
2457                              $_[3]) || return 0;
2458                 }
2459         }
2460 &$cbfunc(1, 0) if ($cbfunc);
2461
2462 &ftp_command("TYPE I", 2, $_[3]) || return 0;
2463
2464 # get the file size and tell the callback
2465 my @st = stat($_[2]);
2466 if ($cbfunc) {
2467         &$cbfunc(2, $st[7]);
2468         }
2469
2470 # send the file
2471 my $pasv = &ftp_command("PASV", 2, $_[3]);
2472 defined($pasv) || return 0;
2473 $pasv =~ /\(([0-9,]+)\)/;
2474 @n = split(/,/ , $1);
2475 &open_socket("$n[0].$n[1].$n[2].$n[3]", $n[4]*256 + $n[5], "CON", $_[3]) || return 0;
2476 &ftp_command("STOR $_[1]", 1, $_[3]) || return 0;
2477
2478 # transfer data
2479 my $got;
2480 open(PFILE, $_[2]);
2481 while(read(PFILE, $buf, 1024) > 0) {
2482         print CON $buf;
2483         $got += length($buf);
2484         &$cbfunc(3, $got) if ($cbfunc);
2485         }
2486 close(PFILE);
2487 close(CON);
2488 if ($got != $st[7]) {
2489         if ($_[3]) { ${$_[3]} = "Upload incomplete"; return 0; }
2490         else { &error("Upload incomplete"); }
2491         }
2492 &$cbfunc(4) if ($cbfunc);
2493
2494 # finish off..
2495 &ftp_command("", 2, $_[3]) || return 0;
2496 &ftp_command("QUIT", 2, $_[3]) || return 0;
2497 close(SOCK);
2498
2499 return 1;
2500 }
2501
2502 =head2 no_proxy(host)
2503
2504 Checks if some host is on the no proxy list. For internal use by the 
2505 http_download and ftp_download functions.
2506
2507 =cut
2508 sub no_proxy
2509 {
2510 my $ip = &to_ipaddress($_[0]);
2511 foreach my $n (split(/\s+/, $gconfig{'noproxy'})) {
2512         return 1 if ($_[0] =~ /\Q$n\E/ ||
2513                      $ip =~ /\Q$n\E/);
2514         }
2515 return 0;
2516 }
2517
2518 =head2 open_socket(host, port, handle, [&error])
2519
2520 Open a TCP connection to some host and port, using a file handle. The 
2521 parameters are :
2522
2523 =item host - Hostname or IP address to connect to.
2524
2525 =item port - TCP port number.
2526
2527 =item handle - A file handle name to use for the connection.
2528
2529 =item error - A string reference to write any error message into. If not set, the error function is called on failure.
2530
2531 =cut
2532 sub open_socket
2533 {
2534 my ($host, $port, $fh, $err) = @_;
2535 $fh = &callers_package($fh);
2536
2537 if ($gconfig{'debug_what_net'}) {
2538         &webmin_debug_log('TCP', "host=$host port=$port");
2539         }
2540
2541 # Lookup IP address for the host. Try v4 first, and failing that v6
2542 my $ip;
2543 my $proto = getprotobyname("tcp");
2544 if ($ip = &to_ipaddress($host)) {
2545         # Create IPv4 socket and connection
2546         if (!socket($fh, PF_INET(), SOCK_STREAM, $proto)) {
2547                 my $msg = "Failed to create socket : $!";
2548                 if ($err) { $$err = $msg; return 0; }
2549                 else { &error($msg); }
2550                 }
2551         my $addr = inet_aton($ip);
2552         if ($gconfig{'bind_proxy'}) {
2553                 # BIND to outgoing IP
2554                 if (!bind($fh,pack_sockaddr_in(0, inet_aton($gconfig{'bind_proxy'})))) {
2555                         my $msg = "Failed to bind to source address : $!";
2556                         if ($err) { $$err = $msg; return 0; }
2557                         else { &error($msg); }
2558                         }
2559                 }
2560         if (!connect($fh, pack_sockaddr_in($port, $addr))) {
2561                 my $msg = "Failed to connect to $host:$port : $!";
2562                 if ($err) { $$err = $msg; return 0; }
2563                 else { &error($msg); }
2564                 }
2565         }
2566 elsif ($ip = &to_ip6address($host)) {
2567         # Create IPv6 socket and connection
2568         if (!socket($fh, Socket6::PF_INET6(), SOCK_STREAM, $proto)) {
2569                 my $msg = "Failed to create IPv6 socket : $!";
2570                 if ($err) { $$err = $msg; return 0; }
2571                 else { &error($msg); }
2572                 }
2573         my $addr = inet_pton(Socket6::AF_INET6(), $ip);
2574         if (!connect($fh, pack_sockaddr_in6($port, $addr))) {
2575                 my $msg = "Failed to IPv6 connect to $host:$port : $!";
2576                 if ($err) { $$err = $msg; return 0; }
2577                 else { &error($msg); }
2578                 }
2579         }
2580 else {
2581         # Resolution failed
2582         my $msg = "Failed to lookup IP address for $host";
2583         if ($err) { $$err = $msg; return 0; }
2584         else { &error($msg); }
2585         }
2586
2587 # Disable buffering
2588 my $old = select($fh);
2589 $| = 1;
2590 select($old);
2591 return 1;
2592 }
2593
2594 =head2 download_timeout
2595
2596 Called when a download times out. For internal use only.
2597
2598 =cut
2599 sub download_timeout
2600 {
2601 $main::download_timed_out = "Download timed out";
2602 }
2603
2604 =head2 ftp_command(command, expected, [&error], [filehandle])
2605
2606 Send an FTP command, and die if the reply is not what was expected. Mainly
2607 for internal use by the ftp_download and ftp_upload functions.
2608
2609 =cut
2610 sub ftp_command
2611 {
2612 my ($cmd, $expect, $err, $fh) = @_;
2613 $fh ||= "SOCK";
2614 $fh = &callers_package($fh);
2615
2616 my $line;
2617 my $what = $cmd ne "" ? "<i>$cmd</i>" : "initial connection";
2618 if ($cmd ne "") {
2619         print $fh "$cmd\r\n";
2620         }
2621 alarm(60);
2622 if (!($line = <$fh>)) {
2623         alarm(0);
2624         if ($err) { $$err = "Failed to read reply to $what"; return undef; }
2625         else { &error("Failed to read reply to $what"); }
2626         }
2627 $line =~ /^(...)(.)(.*)$/;
2628 my $found = 0;
2629 if (ref($expect)) {
2630         foreach my $c (@$expect) {
2631                 $found++ if (int($1/100) == $c);
2632                 }
2633         }
2634 else {
2635         $found++ if (int($1/100) == $_[1]);
2636         }
2637 if (!$found) {
2638         alarm(0);
2639         if ($err) { $$err = "$what failed : $3"; return undef; }
2640         else { &error("$what failed : $3"); }
2641         }
2642 my $rcode = $1;
2643 my $reply = $3;
2644 if ($2 eq "-") {
2645         # Need to skip extra stuff..
2646         while(1) {
2647                 if (!($line = <$fh>)) {
2648                         alarm(0);
2649                         if ($$err) { $$err = "Failed to read reply to $what";
2650                                      return undef; }
2651                         else { &error("Failed to read reply to $what"); }
2652                         }
2653                 $line =~ /^(....)(.*)$/; $reply .= $2;
2654                 if ($1 eq "$rcode ") { last; }
2655                 }
2656         }
2657 alarm(0);
2658 return wantarray ? ($reply, $rcode) : $reply;
2659 }
2660
2661 =head2 to_ipaddress(hostname)
2662
2663 Converts a hostname to an a.b.c.d format IP address, or returns undef if
2664 it cannot be resolved.
2665
2666 =cut
2667 sub to_ipaddress
2668 {
2669 if (&check_ipaddress($_[0])) {
2670         return $_[0];   # Already in v4 format
2671         }
2672 elsif (&check_ip6address($_[0])) {
2673         return undef;   # A v6 address cannot be converted to v4
2674         }
2675 else {
2676         my $hn = gethostbyname($_[0]);
2677         return undef if (!$hn);
2678         local @ip = unpack("CCCC", $hn);
2679         return join("." , @ip);
2680         }
2681 }
2682
2683 =head2 to_ip6address(hostname)
2684
2685 Converts a hostname to IPv6 address, or returns undef if it cannot be resolved.
2686
2687 =cut
2688 sub to_ip6address
2689 {
2690 if (&check_ip6address($_[0])) {
2691         return $_[0];   # Already in v6 format
2692         }
2693 elsif (&check_ipaddress($_[0])) {
2694         return undef;   # A v4 address cannot be v6
2695         }
2696 elsif (!&supports_ipv6()) {
2697         return undef;   # Cannot lookup
2698         }
2699 else {
2700         # Perform IPv6 DNS lookup
2701         my $inaddr;
2702         (undef, undef, undef, $inaddr) =
2703             getaddrinfo($_[0], undef, Socket6::AF_INET6(), SOCK_STREAM);
2704         return undef if (!$inaddr);
2705         my $addr;
2706         (undef, $addr) = unpack_sockaddr_in6($inaddr);
2707         return inet_ntop(Socket6::AF_INET6(), $addr);
2708         }
2709 }
2710
2711 =head2 to_hostname(ipv4|ipv6-address)
2712
2713 Reverse-resolves an IPv4 or 6 address to a hostname
2714
2715 =cut
2716 sub to_hostname
2717 {
2718 my ($addr) = @_;
2719 if (&check_ip6address($addr) && &supports_ipv6()) {
2720         return gethostbyaddr(inet_pton(Socket6::AF_INET6(), $addr),
2721                              Socket6::AF_INET6());
2722         }
2723 else {
2724         return gethostbyaddr(inet_aton($addr), AF_INET);
2725         }
2726 }
2727
2728 =head2 icons_table(&links, &titles, &icons, [columns], [href], [width], [height], &befores, &afters)
2729
2730 Renders a 4-column table of icons. The useful parameters are :
2731
2732 =item links - An array ref of link destination URLs for the icons.
2733
2734 =item titles - An array ref of titles to appear under the icons.
2735
2736 =item icons - An array ref of URLs for icon images.
2737
2738 =item columns - Number of columns to layout the icons with. Defaults to 4.
2739
2740 =cut
2741 sub icons_table
2742 {
2743 &load_theme_library();
2744 if (defined(&theme_icons_table)) {
2745         &theme_icons_table(@_);
2746         return;
2747         }
2748 my $need_tr;
2749 my $cols = $_[3] ? $_[3] : 4;
2750 my $per = int(100.0 / $cols);
2751 print "<table class='icons_table' width=100% cellpadding=5>\n";
2752 for(my $i=0; $i<@{$_[0]}; $i++) {
2753         if ($i%$cols == 0) { print "<tr>\n"; }
2754         print "<td width=$per% align=center valign=top>\n";
2755         &generate_icon($_[2]->[$i], $_[1]->[$i], $_[0]->[$i],
2756                        ref($_[4]) ? $_[4]->[$i] : $_[4], $_[5], $_[6],
2757                        $_[7]->[$i], $_[8]->[$i]);
2758         print "</td>\n";
2759         if ($i%$cols == $cols-1) { print "</tr>\n"; }
2760         }
2761 while($i++%$cols) { print "<td width=$per%></td>\n"; $need_tr++; }
2762 print "</tr>\n" if ($need_tr);
2763 print "</table>\n";
2764 }
2765
2766 =head2 replace_file_line(file, line, [newline]*)
2767
2768 Replaces one line in some file with 0 or more new lines. The parameters are :
2769
2770 =item file - Full path to some file, like /etc/hosts.
2771
2772 =item line - Line number to replace, starting from 0.
2773
2774 =item newline - Zero or more lines to put into the file at the given line number. These must be newline-terminated strings.
2775
2776 =cut
2777 sub replace_file_line
2778 {
2779 my @lines;
2780 my $realfile = &translate_filename($_[0]);
2781 open(FILE, $realfile);
2782 @lines = <FILE>;
2783 close(FILE);
2784 if (@_ > 2) { splice(@lines, $_[1], 1, @_[2..$#_]); }
2785 else { splice(@lines, $_[1], 1); }
2786 &open_tempfile(FILE, ">$realfile");
2787 &print_tempfile(FILE, @lines);
2788 &close_tempfile(FILE);
2789 }
2790
2791 =head2 read_file_lines(file, [readonly])
2792
2793 Returns a reference to an array containing the lines from some file. This
2794 array can be modified, and will be written out when flush_file_lines()
2795 is called. The parameters are :
2796
2797 =item file - Full path to the file to read.
2798
2799 =item readonly - Should be set 1 if the caller is only going to read the lines, and never write it out.
2800
2801 Example code :
2802
2803  $lref = read_file_lines("/etc/hosts");
2804  push(@$lref, "127.0.0.1 localhost");
2805  flush_file_lines("/etc/hosts");
2806
2807 =cut
2808 sub read_file_lines
2809 {
2810 if (!$_[0]) {
2811         my ($package, $filename, $line) = caller;
2812         print STDERR "Missing file to read at ${package}::${filename} line $line\n";
2813         }
2814 my $realfile = &translate_filename($_[0]);
2815 if (!$main::file_cache{$realfile}) {
2816         my (@lines, $eol);
2817         local $_;
2818         &webmin_debug_log('READ', $_[0]) if ($gconfig{'debug_what_read'});
2819         open(READFILE, $realfile);
2820         while(<READFILE>) {
2821                 if (!$eol) {
2822                         $eol = /\r\n$/ ? "\r\n" : "\n";
2823                         }
2824                 tr/\r\n//d;
2825                 push(@lines, $_);
2826                 }
2827         close(READFILE);
2828         $main::file_cache{$realfile} = \@lines;
2829         $main::file_cache_noflush{$realfile} = $_[1];
2830         $main::file_cache_eol{$realfile} = $eol || "\n";
2831         }
2832 else {
2833         # Make read-write if currently readonly
2834         if (!$_[1]) {
2835                 $main::file_cache_noflush{$realfile} = 0;
2836                 }
2837         }
2838 return $main::file_cache{$realfile};
2839 }
2840
2841 =head2 flush_file_lines([file], [eol])
2842
2843 Write out to a file previously read by read_file_lines to disk (except
2844 for those marked readonly). The parameters are :
2845
2846 =item file - The file to flush out.
2847
2848 =item eof - End-of-line character for each line. Defaults to \n.
2849
2850 =cut
2851 sub flush_file_lines
2852 {
2853 my @files;
2854 if ($_[0]) {
2855         local $trans = &translate_filename($_[0]);
2856         $main::file_cache{$trans} ||
2857                 &error("flush_file_lines called on non-loaded file $trans");
2858         push(@files, $trans);
2859         }
2860 else {
2861         @files = ( keys %main::file_cache );
2862         }
2863 foreach my $f (@files) {
2864         my $eol = $_[1] || $main::file_cache_eol{$f} || "\n";
2865         if (!$main::file_cache_noflush{$f}) {
2866                 no warnings; # XXX Bareword file handles should go away
2867                 &open_tempfile(FLUSHFILE, ">$f");
2868                 foreach my $line (@{$main::file_cache{$f}}) {
2869                         (print FLUSHFILE $line,$eol) ||
2870                                 &error(&text("efilewrite", $f, $!));
2871                         }
2872                 &close_tempfile(FLUSHFILE);
2873                 }
2874         delete($main::file_cache{$f});
2875         delete($main::file_cache_noflush{$f});
2876         }
2877 }
2878
2879 =head2 unflush_file_lines(file)
2880
2881 Clear the internal cache of some given file, previously read by read_file_lines.
2882
2883 =cut
2884 sub unflush_file_lines
2885 {
2886 my $realfile = &translate_filename($_[0]);
2887 delete($main::file_cache{$realfile});
2888 delete($main::file_cache_noflush{$realfile});
2889 }
2890
2891 =head2 unix_user_input(fieldname, user, [form])
2892
2893 Returns HTML for an input to select a Unix user. By default this is a text
2894 box with a user popup button next to it.
2895
2896 =cut
2897 sub unix_user_input
2898 {
2899 if (defined(&theme_unix_user_input)) {
2900         return &theme_unix_user_input(@_);
2901         }
2902 return "<input name=$_[0] size=13 value=\"$_[1]\"> ".
2903        &user_chooser_button($_[0], 0, $_[2] || 0)."\n";
2904 }
2905
2906 =head2 unix_group_input(fieldname, user, [form])
2907
2908 Returns HTML for an input to select a Unix group. By default this is a text
2909 box with a group popup button next to it.
2910
2911 =cut
2912 sub unix_group_input
2913 {
2914 if (defined(&theme_unix_group_input)) {
2915         return &theme_unix_group_input(@_);
2916         }
2917 return "<input name=$_[0] size=13 value=\"$_[1]\"> ".
2918        &group_chooser_button($_[0], 0, $_[2] || 0)."\n";
2919 }
2920
2921 =head2 hlink(text, page, [module], [width], [height])
2922
2923 Returns HTML for a link that when clicked on pops up a window for a Webmin
2924 help page. The parameters are :
2925
2926 =item text - Text for the link.
2927
2928 =item page - Help page code, such as 'intro'.
2929
2930 =item module - Module the help page is in. Defaults to the current module.
2931
2932 =item width - Width of the help popup window. Defaults to 600 pixels.
2933
2934 =item height - Height of the help popup window. Defaults to 400 pixels.
2935
2936 The actual help pages are in each module's help sub-directory, in files with
2937 .html extensions.
2938
2939 =cut
2940 sub hlink
2941 {
2942 if (defined(&theme_hlink)) {
2943         return &theme_hlink(@_);
2944         }
2945 my $mod = $_[2] ? $_[2] : &get_module_name();
2946 my $width = $_[3] || $tconfig{'help_width'} || $gconfig{'help_width'} || 600;
2947 my $height = $_[4] || $tconfig{'help_height'} || $gconfig{'help_height'} || 400;
2948 return "<a onClick='window.open(\"$gconfig{'webprefix'}/help.cgi/$mod/$_[1]\", \"help\", \"toolbar=no,menubar=no,scrollbars=yes,width=$width,height=$height,resizable=yes\"); return false' href=\"$gconfig{'webprefix'}/help.cgi/$mod/$_[1]\">$_[0]</a>";
2949 }
2950
2951 =head2 user_chooser_button(field, multiple, [form])
2952
2953 Returns HTML for a javascript button for choosing a Unix user or users.
2954 The parameters are :
2955
2956 =item field - Name of the HTML field to place the username into.
2957
2958 =item multiple - Set to 1 if multiple users can be selected.
2959
2960 =item form - Index of the form on the page.
2961
2962 =cut
2963 sub user_chooser_button
2964 {
2965 return undef if (!&supports_users());
2966 return &theme_user_chooser_button(@_)
2967         if (defined(&theme_user_chooser_button));
2968 my $form = defined($_[2]) ? $_[2] : 0;
2969 my $w = $_[1] ? 500 : 300;
2970 my $h = 200;
2971 if ($_[1] && $gconfig{'db_sizeusers'}) {
2972         ($w, $h) = split(/x/, $gconfig{'db_sizeusers'});
2973         }
2974 elsif (!$_[1] && $gconfig{'db_sizeuser'}) {
2975         ($w, $h) = split(/x/, $gconfig{'db_sizeuser'});
2976         }
2977 return "<input type=button onClick='ifield = form.$_[0]; chooser = window.open(\"$gconfig{'webprefix'}/user_chooser.cgi?multi=$_[1]&user=\"+escape(ifield.value), \"chooser\", \"toolbar=no,menubar=no,scrollbars=yes,resizable=yes,width=$w,height=$h\"); chooser.ifield = ifield; window.ifield = ifield' value=\"...\">\n";
2978 }
2979
2980 =head2 group_chooser_button(field, multiple, [form])
2981
2982 Returns HTML for a javascript button for choosing a Unix group or groups
2983 The parameters are :
2984
2985 =item field - Name of the HTML field to place the group name into.
2986
2987 =item multiple - Set to 1 if multiple groups can be selected.
2988
2989 =item form - Index of the form on the page.
2990
2991 =cut
2992 sub group_chooser_button
2993 {
2994 return undef if (!&supports_users());
2995 return &theme_group_chooser_button(@_)
2996         if (defined(&theme_group_chooser_button));
2997 my $form = defined($_[2]) ? $_[2] : 0;
2998 my $w = $_[1] ? 500 : 300;
2999 my $h = 200;
3000 if ($_[1] && $gconfig{'db_sizeusers'}) {
3001         ($w, $h) = split(/x/, $gconfig{'db_sizeusers'});
3002         }
3003 elsif (!$_[1] && $gconfig{'db_sizeuser'}) {
3004         ($w, $h) = split(/x/, $gconfig{'db_sizeuser'});
3005         }
3006 return "<input type=button onClick='ifield = form.$_[0]; chooser = window.open(\"$gconfig{'webprefix'}/group_chooser.cgi?multi=$_[1]&group=\"+escape(ifield.value), \"chooser\", \"toolbar=no,menubar=no,scrollbars=yes,resizable=yes,width=$w,height=$h\"); chooser.ifield = ifield; window.ifield = ifield' value=\"...\">\n";
3007 }
3008
3009 =head2 foreign_check(module, [api-only])
3010
3011 Checks if some other module exists and is supported on this OS. The parameters
3012 are :
3013
3014 =item module - Name of the module to check.
3015
3016 =item api-only - Set to 1 if you just want to check if the module provides an API that others can call, instead of the full web UI.
3017
3018 =cut
3019 sub foreign_check
3020 {
3021 my ($mod, $api) = @_;
3022 my %minfo;
3023 my $mdir = &module_root_directory($mod);
3024 &read_file_cached("$mdir/module.info", \%minfo) || return 0;
3025 return &check_os_support(\%minfo, undef, undef, $api);
3026 }
3027
3028 =head2 foreign_exists(module)
3029
3030 Checks if some other module exists. The module parameter is the short module
3031 name.
3032
3033 =cut
3034 sub foreign_exists
3035 {
3036 my $mdir = &module_root_directory($_[0]);
3037 return -r "$mdir/module.info";
3038 }
3039
3040 =head2 foreign_available(module)
3041
3042 Returns 1 if some module is installed, and acessible to the current user. The
3043 module parameter is the module directory name.
3044
3045 =cut
3046 sub foreign_available
3047 {
3048 return 0 if (!&foreign_check($_[0]) &&
3049              !$gconfig{'available_even_if_no_support'});
3050 my %foreign_module_info = &get_module_info($_[0]);
3051
3052 # Check list of allowed modules
3053 my %acl;
3054 &read_acl(\%acl, undef, [ $base_remote_user ]);
3055 return 0 if (!$acl{$base_remote_user,$_[0]} &&
3056              !$acl{$base_remote_user,'*'});
3057
3058 # Check for usermod restrictions
3059 my @usermods = &list_usermods();
3060 return 0 if (!&available_usermods( [ \%foreign_module_info ], \@usermods));
3061
3062 if (&get_product_name() eq "webmin") {
3063         # Check if the user has any RBAC privileges in this module
3064         if (&supports_rbac($_[0]) &&
3065             &use_rbac_module_acl(undef, $_[0])) {
3066                 # RBAC is enabled for this user and module - check if he
3067                 # has any rights
3068                 my $rbacs = &get_rbac_module_acl($remote_user, $_[0]);
3069                 return 0 if (!$rbacs);
3070                 }
3071         elsif ($gconfig{'rbacdeny_'.$base_remote_user}) {
3072                 # If denying access to modules not specifically allowed by
3073                 # RBAC, then prevent access
3074                 return 0;
3075                 }
3076         }
3077
3078 # Check readonly support
3079 if (&is_readonly_mode()) {
3080         return 0 if (!$foreign_module_info{'readonly'});
3081         }
3082
3083 # Check if theme vetos
3084 if (defined(&theme_foreign_available)) {
3085         return 0 if (!&theme_foreign_available($_[0]));
3086         }
3087
3088 # Check if licence module vetos
3089 if ($main::licence_module) {
3090         return 0 if (!&foreign_call($main::licence_module,
3091                                     "check_module_licence", $_[0]));
3092         }
3093
3094 return 1;
3095 }
3096
3097 =head2 foreign_require(module, [file], [package])
3098
3099 Brings in functions from another module, and places them in the Perl namespace
3100 with the same name as the module. The parameters are :
3101
3102 =item module - The source module's directory name, like sendmail.
3103
3104 =item file - The API file in that module, like sendmail-lib.pl. If missing, all API files are loaded.
3105
3106 =item package - Perl package to place the module's functions and global variables in. 
3107
3108 If the original module name contains dashes, they will be replaced with _ in
3109 the package name.
3110
3111 =cut
3112 sub foreign_require
3113 {
3114 my ($mod, $file, $pkg) = @_;
3115 $pkg ||= $mod || "global";
3116 $pkg =~ s/[^A-Za-z0-9]/_/g;
3117 my @files;
3118 if ($file) {
3119         push(@files, $file);
3120         }
3121 else {
3122         # Auto-detect files
3123         my %minfo = &get_module_info($mod);
3124         if ($minfo{'library'}) {
3125                 @files = split(/\s+/, $minfo{'library'});
3126                 }
3127         else {
3128                 @files = ( $mod."-lib.pl" );
3129                 }
3130         }
3131 @files = grep { !$main::done_foreign_require{$pkg,$_} } @files;
3132 return 1 if (!@files);
3133 foreach my $f (@files) {
3134         $main::done_foreign_require{$pkg,$f}++;
3135         }
3136 my @OLDINC = @INC;
3137 my $mdir = &module_root_directory($mod);
3138 @INC = &unique($mdir, @INC);
3139 -d $mdir || &error("Module $mod does not exist");
3140 if (!&get_module_name() && $mod) {
3141         chdir($mdir);
3142         }
3143 my $old_fmn = $ENV{'FOREIGN_MODULE_NAME'};
3144 my $old_frd = $ENV{'FOREIGN_ROOT_DIRECTORY'};
3145 my $code = "package $pkg; ".
3146            "\$ENV{'FOREIGN_MODULE_NAME'} = '$mod'; ".
3147            "\$ENV{'FOREIGN_ROOT_DIRECTORY'} = '$root_directory'; ";
3148 foreach my $f (@files) {
3149         $code .= "do '$mdir/$f' || die \$@; ";
3150         }
3151 eval $code;
3152 if (defined($old_fmn)) {
3153         $ENV{'FOREIGN_MODULE_NAME'} = $old_fmn;
3154         }
3155 else {
3156         delete($ENV{'FOREIGN_MODULE_NAME'});
3157         }
3158 if (defined($old_frd)) {
3159         $ENV{'FOREIGN_ROOT_DIRECTORY'} = $old_frd;
3160         }
3161 else {
3162         delete($ENV{'FOREIGN_ROOT_DIRECTORY'});
3163         }
3164 @INC = @OLDINC;
3165 if ($@) { &error("Require $mod/$files[0] failed : <pre>$@</pre>"); }
3166 return 1;
3167 }
3168
3169 =head2 foreign_call(module, function, [arg]*)
3170
3171 Call a function in another module. The module parameter is the target module
3172 directory name, function is the perl sub to call, and the remaining parameters
3173 are the arguments. However, unless you need to call a function whose name
3174 is dynamic, it is better to use Perl's cross-module function call syntax
3175 like module::function(args).
3176
3177 =cut
3178 sub foreign_call
3179 {
3180 my $pkg = $_[0] || "global";
3181 $pkg =~ s/[^A-Za-z0-9]/_/g;
3182 my @args = @_[2 .. @_-1];
3183 $main::foreign_args = \@args;
3184 my @rv = eval <<EOF;
3185 package $pkg;
3186 &$_[1](\@{\$main::foreign_args});
3187 EOF
3188 if ($@) { &error("$_[0]::$_[1] failed : $@"); }
3189 return wantarray ? @rv : $rv[0];
3190 }
3191
3192 =head2 foreign_config(module, [user-config])
3193
3194 Get the configuration from another module, and return it as a hash. If the
3195 user-config parameter is set to 1, returns the Usermin user-level preferences
3196 for the current user instead.
3197
3198 =cut
3199 sub foreign_config
3200 {
3201 my ($mod, $uc) = @_;
3202 my %fconfig;
3203 if ($uc) {
3204         &read_file_cached("$root_directory/$mod/defaultuconfig", \%fconfig);
3205         &read_file_cached("$config_directory/$mod/uconfig", \%fconfig);
3206         &read_file_cached("$user_config_directory/$mod/config", \%fconfig);
3207         }
3208 else {
3209         &read_file_cached("$config_directory/$mod/config", \%fconfig);
3210         }
3211 return %fconfig;
3212 }
3213
3214 =head2 foreign_installed(module, mode)
3215
3216 Checks if the server for some module is installed, and possibly also checks
3217 if the module has been configured by Webmin.
3218 For mode 1, returns 2 if the server is installed and configured for use by
3219 Webmin, 1 if installed but not configured, or 0 otherwise.
3220 For mode 0, returns 1 if installed, 0 if not.
3221 If the module does not provide an install_check.pl script, assumes that
3222 the server is installed.
3223
3224 =cut
3225 sub foreign_installed
3226 {
3227 my ($mod, $configured) = @_;
3228 if (defined($main::foreign_installed_cache{$mod,$configured})) {
3229         # Already cached..
3230         return $main::foreign_installed_cache{$mod,$configured};
3231         }
3232 else {
3233         my $rv;
3234         if (!&foreign_check($mod)) {
3235                 # Module is missing
3236                 $rv = 0;
3237                 }
3238         else {
3239                 my $mdir = &module_root_directory($mod);
3240                 if (!-r "$mdir/install_check.pl") {
3241                         # Not known, assume OK
3242                         $rv = $configured ? 2 : 1;
3243                         }
3244                 else {
3245                         # Call function to check
3246                         &foreign_require($mod, "install_check.pl");
3247                         $rv = &foreign_call($mod, "is_installed", $configured);
3248                         }
3249                 }
3250         $main::foreign_installed_cache{$mod,$configured} = $rv;
3251         return $rv;
3252         }
3253 }
3254
3255 =head2 foreign_defined(module, function)
3256
3257 Returns 1 if some function is defined in another module. In general, it is
3258 simpler to use the syntax &defined(module::function) instead.
3259
3260 =cut
3261 sub foreign_defined
3262 {
3263 my ($pkg) = @_;
3264 $pkg =~ s/[^A-Za-z0-9]/_/g;
3265 my $func = "${pkg}::$_[1]";
3266 return defined(&$func);
3267 }
3268
3269 =head2 get_system_hostname([short])
3270
3271 Returns the hostname of this system. If the short parameter is set to 1,
3272 then the domain name is not prepended - otherwise, Webmin will attempt to get
3273 the fully qualified hostname, like foo.example.com.
3274
3275 =cut
3276 sub get_system_hostname
3277 {
3278 my $m = int($_[0]);
3279 if (!$main::get_system_hostname[$m]) {
3280         if ($gconfig{'os_type'} ne 'windows') {
3281                 # Try some common Linux hostname files first
3282                 my $fromfile;
3283                 if ($gconfig{'os_type'} eq 'redhat-linux') {
3284                         my %nc;
3285                         &read_env_file("/etc/sysconfig/network", \%nc);
3286                         if ($nc{'HOSTNAME'}) {
3287                                 $fromfile = $nc{'HOSTNAME'};
3288                                 }
3289                         }
3290                 elsif ($gconfig{'os_type'} eq 'debian-linux') {
3291                         my $hn = &read_file_contents("/etc/hostname");
3292                         if ($hn) {
3293                                 $hn =~ s/\r|\n//g;
3294                                 $fromfile = $hn;
3295                                 }
3296                         }
3297                 elsif ($gconfig{'os_type'} eq 'open-linux') {
3298                         my $hn = &read_file_contents("/etc/HOSTNAME");
3299                         if ($hn) {
3300                                 $hn =~ s/\r|\n//g;
3301                                 $fromfile = $hn;
3302                                 }
3303                         }
3304                 elsif ($gconfig{'os_type'} eq 'solaris') {
3305                         my $hn = &read_file_contents("/etc/nodename");
3306                         if ($hn) {
3307                                 $hn =~ s/\r|\n//g;
3308                                 $fromfile = $hn;
3309                                 }
3310                         }
3311
3312                 # If we found a hostname, use it if value
3313                 if ($fromfile && ($m || $fromfile =~ /\./)) {
3314                         if ($m) {
3315                                 $fromfile =~ s/\..*$//;
3316                                 }
3317                         $main::get_system_hostname[$m] = $fromfile;
3318                         return $fromfile;
3319                         }
3320
3321                 # Can use hostname command on Unix
3322                 &execute_command("hostname", undef,
3323                                  \$main::get_system_hostname[$m], undef, 0, 1);
3324                 chop($main::get_system_hostname[$m]);
3325                 if ($?) {
3326                         eval "use Sys::Hostname";
3327                         if (!$@) {
3328                                 $main::get_system_hostname[$m] = eval "hostname()";
3329                                 }
3330                         if ($@ || !$main::get_system_hostname[$m]) {
3331                                 $main::get_system_hostname[$m] = "UNKNOWN";
3332                                 }
3333                         }
3334                 elsif ($main::get_system_hostname[$m] !~ /\./ &&
3335                        $gconfig{'os_type'} =~ /linux$/ &&
3336                        !$gconfig{'no_hostname_f'} && !$_[0]) {
3337                         # Try with -f flag to get fully qualified name
3338                         my $flag;
3339                         my $ex = &execute_command("hostname -f", undef, \$flag,
3340                                                   undef, 0, 1);
3341                         chop($flag);
3342                         if ($ex || $flag eq "") {
3343                                 # -f not supported! We have probably set the
3344                                 # hostname to just '-f'. Fix the problem
3345                                 # (if we are root)
3346                                 if ($< == 0) {
3347                                         &execute_command("hostname ".
3348                                                 quotemeta($main::get_system_hostname[$m]),
3349                                                 undef, undef, undef, 0, 1);
3350                                         }
3351                                 }
3352                         else {
3353                                 $main::get_system_hostname[$m] = $flag;
3354                                 }
3355                         }
3356                 }
3357         else {
3358                 # On Windows, try computername environment variable
3359                 return $ENV{'computername'} if ($ENV{'computername'});
3360                 return $ENV{'COMPUTERNAME'} if ($ENV{'COMPUTERNAME'});
3361
3362                 # Fall back to net name command
3363                 my $out = `net name 2>&1`;
3364                 if ($out =~ /\-+\r?\n(\S+)/) {
3365                         $main::get_system_hostname[$m] = $1;
3366                         }
3367                 else {
3368                         $main::get_system_hostname[$m] = "windows";
3369                         }
3370                 }
3371         }
3372 return $main::get_system_hostname[$m];
3373 }
3374
3375 =head2 get_webmin_version
3376
3377 Returns the version of Webmin currently being run, such as 1.450.
3378
3379 =cut
3380 sub get_webmin_version
3381 {
3382 if (!$get_webmin_version) {
3383         open(VERSION, "$root_directory/version") || return 0;
3384         ($get_webmin_version = <VERSION>) =~ tr/\r|\n//d;
3385         close(VERSION);
3386         }
3387 return $get_webmin_version;
3388 }
3389
3390 =head2 get_module_acl([user], [module], [no-rbac], [no-default])
3391
3392 Returns a hash containing access control options for the given user and module.
3393 By default the current username and module name are used. If the no-rbac flag
3394 is given, the permissions will not be updated based on the user's RBAC role
3395 (as seen on Solaris). If the no-default flag is given, default permissions for
3396 the module will not be included.
3397
3398 =cut
3399 sub get_module_acl
3400 {
3401 my $u = defined($_[0]) ? $_[0] : $base_remote_user;
3402 my $m = defined($_[1]) ? $_[1] : &get_module_name();
3403 my $mdir = &module_root_directory($m);
3404 my %rv;
3405 if (!$_[3]) {
3406         # Read default ACL first, to be overridden by per-user settings
3407         &read_file_cached("$mdir/defaultacl", \%rv);
3408
3409         # If this isn't a master admin user, apply the negative permissions
3410         # so that he doesn't un-expectedly gain access to new features
3411         my %gacccess;
3412         &read_file_cached("$config_directory/$u.acl", \%gaccess);
3413         if ($gaccess{'negative'}) {
3414                 &read_file_cached("$mdir/negativeacl", \%rv);
3415                 }
3416         }
3417 my %usersacl;
3418 if (!$_[2] && &supports_rbac($m) && &use_rbac_module_acl($u, $m)) {
3419         # RBAC overrides exist for this user in this module
3420         my $rbac = &get_rbac_module_acl(
3421                         defined($_[0]) ? $_[0] : $remote_user, $m);
3422         foreach my $r (keys %$rbac) {
3423                 $rv{$r} = $rbac->{$r};
3424                 }
3425         }
3426 elsif ($gconfig{"risk_$u"} && $m) {
3427         # ACL is defined by user's risk level
3428         my $rf = $gconfig{"risk_$u"}.'.risk';
3429         &read_file_cached("$mdir/$rf", \%rv);
3430
3431         my $sf = $gconfig{"skill_$u"}.'.skill';
3432         &read_file_cached("$mdir/$sf", \%rv);
3433         }
3434 elsif ($u ne '') {
3435         # Use normal Webmin ACL, if a user is set
3436         my $userdb = &get_userdb_string();
3437         my $foundindb = 0;
3438         if ($userdb && ($u ne $base_remote_user || $remote_user_proto)) {
3439                 # Look for this user in the user/group DB, if one is defined
3440                 # and if the user might be in the DB
3441                 my ($dbh, $proto, $prefix, $args) = &connect_userdb($userdb);
3442                 ref($dbh) || &error(&text('euserdbacl', $dbh));
3443                 if ($proto eq "mysql" || $proto eq "postgresql") {
3444                         # Find the user in the SQL DB
3445                         my $cmd = $dbh->prepare(
3446                                 "select id from webmin_user where name = ?");
3447                         $cmd && $cmd->execute($u) ||
3448                                 &error(&text('euserdbacl', $dbh->errstr));
3449                         my ($id) = $cmd->fetchrow();
3450                         $foundindb = 1 if (defined($id));
3451                         $cmd->finish();
3452
3453                         # Fetch ACLs with SQL
3454                         if ($foundindb) {
3455                                 my $cmd = $dbh->prepare(
3456                                     "select attr,value from webmin_user_acl ".
3457                                     "where id = ? and module = ?");
3458                                 $cmd && $cmd->execute($id, $m) ||
3459                                     &error(&text('euserdbacl', $dbh->errstr));
3460                                 while(my ($a, $v) = $cmd->fetchrow()) {
3461                                         $rv{$a} = $v;
3462                                         }
3463                                 $cmd->finish();
3464                                 }
3465                         }
3466                 elsif ($proto eq "ldap") {
3467                         # Find user in LDAP
3468                         my $rv = $dbh->search(
3469                                 base => $prefix,
3470                                 filter => '(&(cn='.$u.')(objectClass='.
3471                                           $args->{'userclass'}.'))',
3472                                 scope => 'sub');
3473                         if (!$rv || $rv->code) {
3474                                 &error(&text('euserdbacl',
3475                                      $rv ? $rv->error : "Unknown error"));
3476                                 }
3477                         my ($user) = $rv->all_entries;
3478
3479                         # Find ACL sub-object for the module
3480                         my $ldapm = $m || "global";
3481                         if ($user) {
3482                                 my $rv = $dbh->search(
3483                                         base => $user->dn(),
3484                                         filter => '(cn='.$ldapm.')',
3485                                         scope => 'one');
3486                                 if (!$rv || $rv->code) {
3487                                         &error(&text('euserdbacl',
3488                                            $rv ? $rv->error : "Unknown error"));
3489                                         }
3490                                 my ($acl) = $rv->all_entries;
3491                                 if ($acl) {
3492                                         foreach my $av ($acl->get_value(
3493                                                         'webminAclEntry')) {
3494                                                 my ($a, $v) = split(/=/, $av,2);
3495                                                 $rv{$a} = $v;
3496                                                 }
3497                                         }
3498                                 }
3499                         }
3500                 &disconnect_userdb($userdb, $dbh);
3501                 }
3502
3503         if (!$foundindb) {
3504                 # Read from local files
3505                 &read_file_cached("$config_directory/$m/$u.acl", \%rv);
3506                 if ($remote_user ne $base_remote_user && !defined($_[0])) {
3507                         &read_file_cached(
3508                                 "$config_directory/$m/$remote_user.acl",\%rv);
3509                         }
3510                 }
3511         }
3512 if ($tconfig{'preload_functions'}) {
3513         &load_theme_library();
3514         }
3515 if (defined(&theme_get_module_acl)) {
3516         %rv = &theme_get_module_acl($u, $m, \%rv);
3517         }
3518 return %rv;
3519 }
3520
3521 =head2 get_group_module_acl(group, [module], [no-default])
3522
3523 Returns the ACL for a Webmin group, in an optional module (which defaults to
3524 the current module).
3525
3526 =cut
3527 sub get_group_module_acl
3528 {
3529 my $g = $_[0];
3530 my $m = defined($_[1]) ? $_[1] : &get_module_name();
3531 my $mdir = &module_root_directory($m);
3532 my %rv;
3533 if (!$_[2]) {
3534         &read_file_cached("$mdir/defaultacl", \%rv);
3535         }
3536
3537 my $userdb = &get_userdb_string();
3538 my $foundindb = 0;
3539 if ($userdb) {
3540         # Look for this group in the user/group DB
3541         my ($dbh, $proto, $prefix, $args) = &connect_userdb($userdb);
3542         ref($dbh) || &error(&text('egroupdbacl', $dbh));
3543         if ($proto eq "mysql" || $proto eq "postgresql") {
3544                 # Find the group in the SQL DB
3545                 my $cmd = $dbh->prepare(
3546                         "select id from webmin_group where name = ?");
3547                 $cmd && $cmd->execute($g) ||
3548                         &error(&text('egroupdbacl', $dbh->errstr));
3549                 my ($id) = $cmd->fetchrow();
3550                 $foundindb = 1 if (defined($id));
3551                 $cmd->finish();
3552
3553                 # Fetch ACLs with SQL
3554                 if ($foundindb) {
3555                         my $cmd = $dbh->prepare(
3556                             "select attr,value from webmin_group_acl ".
3557                             "where id = ? and module = ?");
3558                         $cmd && $cmd->execute($id, $m) ||
3559                             &error(&text('egroupdbacl', $dbh->errstr));
3560                         while(my ($a, $v) = $cmd->fetchrow()) {
3561                                 $rv{$a} = $v;
3562                                 }
3563                         $cmd->finish();
3564                         }
3565                 }
3566         elsif ($proto eq "ldap") {
3567                 # Find group in LDAP
3568                 my $rv = $dbh->search(
3569                         base => $prefix,
3570                         filter => '(&(cn='.$g.')(objectClass='.
3571                                   $args->{'groupclass'}.'))',
3572                         scope => 'sub');
3573                 if (!$rv || $rv->code) {
3574                         &error(&text('egroupdbacl',
3575                                      $rv ? $rv->error : "Unknown error"));
3576                         }
3577                 my ($group) = $rv->all_entries;
3578
3579                 # Find ACL sub-object for the module
3580                 my $ldapm = $m || "global";
3581                 if ($group) {
3582                         my $rv = $dbh->search(
3583                                 base => $group->dn(),
3584                                 filter => '(cn='.$ldapm.')',
3585                                 scope => 'one');
3586                         if (!$rv || $rv->code) {
3587                                 &error(&text('egroupdbacl',
3588                                      $rv ? $rv->error : "Unknown error"));
3589                                 }
3590                         my ($acl) = $rv->all_entries;
3591                         if ($acl) {
3592                                 foreach my $av ($acl->get_value(
3593                                                 'webminAclEntry')) {
3594                                         my ($a, $v) = split(/=/, $av, 2);
3595                                         $rv{$a} = $v;
3596                                         }
3597                                 }
3598                         }
3599                 }
3600         &disconnect_userdb($userdb, $dbh);
3601         }
3602 if (!$foundindb) {
3603         # Read from local files
3604         &read_file_cached("$config_directory/$m/$g.gacl", \%rv);
3605         }
3606 if (defined(&theme_get_module_acl)) {
3607         %rv = &theme_get_module_acl($g, $m, \%rv);
3608         }
3609 return %rv;
3610 }
3611
3612 =head2 save_module_acl(&acl, [user], [module], [never-update-group])
3613
3614 Updates the acl hash for some user and module. The parameters are :
3615
3616 =item acl - Hash reference for the new access control options, or undef to clear
3617
3618 =item user - User to update, defaulting to the current user.
3619
3620 =item module - Module to update, defaulting to the caller.
3621
3622 =item never-update-group - Never update the user's group's ACL
3623
3624 =cut
3625 sub save_module_acl
3626 {
3627 my $u = defined($_[1]) ? $_[1] : $base_remote_user;
3628 my $m = defined($_[2]) ? $_[2] : &get_module_name();
3629 if (!$_[3] && &foreign_check("acl")) {
3630         # Check if this user is a member of a group, and if he gets the
3631         # module from a group. If so, update its ACL as well
3632         &foreign_require("acl", "acl-lib.pl");
3633         my $group;
3634         foreach my $g (&acl::list_groups()) {
3635                 if (&indexof($u, @{$g->{'members'}}) >= 0 &&
3636                     &indexof($m, @{$g->{'modules'}}) >= 0) {
3637                         $group = $g;
3638                         last;
3639                         }
3640                 }
3641         if ($group) {
3642                 &save_group_module_acl($_[0], $group->{'name'}, $m);
3643                 }
3644         }
3645
3646 my $userdb = &get_userdb_string();
3647 my $foundindb = 0;
3648 if ($userdb && ($u ne $base_remote_user || $remote_user_proto)) {
3649         # Look for this user in the user/group DB
3650         my ($dbh, $proto, $prefix, $args) = &connect_userdb($userdb);
3651         ref($dbh) || &error(&text('euserdbacl', $dbh));
3652         if ($proto eq "mysql" || $proto eq "postgresql") {
3653                 # Find the user in the SQL DB
3654                 my $cmd = $dbh->prepare(
3655                         "select id from webmin_user where name = ?");
3656                 $cmd && $cmd->execute($u) ||
3657                         &error(&text('euserdbacl2', $dbh->errstr));
3658                 my ($id) = $cmd->fetchrow();
3659                 $foundindb = 1 if (defined($id));
3660                 $cmd->finish();
3661
3662                 # Replace ACLs for user
3663                 if ($foundindb) {
3664                         my $cmd = $dbh->prepare("delete from webmin_user_acl ".
3665                                                 "where id = ? and module = ?");
3666                         $cmd && $cmd->execute($id, $m) ||
3667                             &error(&text('euserdbacl', $dbh->errstr));
3668                         $cmd->finish();
3669                         if ($_[0]) {
3670                                 my $cmd = $dbh->prepare(
3671                                     "insert into webmin_user_acl ".
3672                                     "(id,module,attr,value) values (?,?,?,?)");
3673                                 $cmd || &error(&text('euserdbacl2',
3674                                                      $dbh->errstr));
3675                                 foreach my $a (keys %{$_[0]}) {
3676                                         $cmd->execute($id,$m,$a,$_[0]->{$a}) ||
3677                                             &error(&text('euserdbacl2',
3678                                                          $dbh->errstr));
3679                                         $cmd->finish();
3680                                         }
3681                                 }
3682                         }
3683                 }
3684         elsif ($proto eq "ldap") {
3685                 # Find the user in LDAP
3686                 my $rv = $dbh->search(
3687                         base => $prefix,
3688                         filter => '(&(cn='.$u.')(objectClass='.
3689                                   $args->{'userclass'}.'))',
3690                         scope => 'sub');
3691                 if (!$rv || $rv->code) {
3692                         &error(&text('euserdbacl',
3693                                      $rv ? $rv->error : "Unknown error"));
3694                         }
3695                 my ($user) = $rv->all_entries;
3696
3697                 if ($user) {
3698                         # Find the ACL sub-object for the module
3699                         $foundindb = 1;
3700                         my $ldapm = $m || "global";
3701                         my $rv = $dbh->search(
3702                                 base => $user->dn(),
3703                                 filter => '(cn='.$ldapm.')',
3704                                 scope => 'one');
3705                         if (!$rv || $rv->code) {
3706                                 &error(&text('euserdbacl',
3707                                      $rv ? $rv->error : "Unknown error"));
3708                                 }
3709                         my ($acl) = $rv->all_entries;
3710
3711                         my @al;
3712                         foreach my $a (keys %{$_[0]}) {
3713                                 push(@al, $a."=".$_[0]->{$a});
3714                                 }
3715                         if ($acl) {
3716                                 # Update attributes
3717                                 $rv = $dbh->modify($acl->dn(),
3718                                   replace => { "webminAclEntry", \@al });
3719                                 }
3720                         else {
3721                                 # Add a sub-object
3722                                 my @attrs = ( "cn", $ldapm,
3723                                               "objectClass", "webminAcl",
3724                                               "webminAclEntry", \@al );
3725                                 $rv = $dbh->add("cn=".$ldapm.",".$user->dn(),
3726                                                 attr => \@attrs);
3727                                 }
3728                         if (!$rv || $rv->code) {
3729                                 &error(&text('euserdbacl2',
3730                                      $rv ? $rv->error : "Unknown error"));
3731                                 }
3732                         }
3733                 }
3734         &disconnect_userdb($userdb, $dbh);
3735         }
3736
3737 if (!$foundindb) {
3738         # Save ACL to local file
3739         if (!-d "$config_directory/$m") {
3740                 mkdir("$config_directory/$m", 0755);
3741                 }
3742         if ($_[0]) {
3743                 &write_file("$config_directory/$m/$u.acl", $_[0]);
3744                 }
3745         else {
3746                 &unlink_file("$config_directory/$m/$u.acl");
3747                 }
3748         }
3749 }
3750
3751 =head2 save_group_module_acl(&acl, group, [module], [never-update-group])
3752
3753 Updates the acl hash for some group and module. The parameters are :
3754
3755 =item acl - Hash reference for the new access control options.
3756
3757 =item group - Group name to update.
3758
3759 =item module - Module to update, defaulting to the caller.
3760
3761 =item never-update-group - Never update the parent group's ACL
3762
3763 =cut
3764 sub save_group_module_acl
3765 {
3766 my $g = $_[1];
3767 my $m = defined($_[2]) ? $_[2] : &get_module_name();
3768 if (!$_[3] && &foreign_check("acl")) {
3769         # Check if this group is a member of a group, and if it gets the
3770         # module from a group. If so, update the parent ACL as well
3771         &foreign_require("acl", "acl-lib.pl");
3772         my $group;
3773         foreach my $pg (&acl::list_groups()) {
3774                 if (&indexof('@'.$g, @{$pg->{'members'}}) >= 0 &&
3775                     &indexof($m, @{$pg->{'modules'}}) >= 0) {
3776                         $group = $g;
3777                         last;
3778                         }
3779                 }
3780         if ($group) {
3781                 &save_group_module_acl($_[0], $group->{'name'}, $m);
3782                 }
3783         }
3784
3785 my $userdb = &get_userdb_string();
3786 my $foundindb = 0;
3787 if ($userdb) {
3788         # Look for this group in the user/group DB
3789         my ($dbh, $proto, $prefix, $args) = &connect_userdb($userdb);
3790         ref($dbh) || &error(&text('egroupdbacl', $dbh));
3791         if ($proto eq "mysql" || $proto eq "postgresql") {
3792                 # Find the group in the SQL DB
3793                 my $cmd = $dbh->prepare(
3794                         "select id from webmin_group where name = ?");
3795                 $cmd && $cmd->execute($g) ||
3796                         &error(&text('egroupdbacl2', $dbh->errstr));
3797                 my ($id) = $cmd->fetchrow();
3798                 $foundindb = 1 if (defined($id));
3799                 $cmd->finish();
3800
3801                 # Replace ACLs for group
3802                 if ($foundindb) {
3803                         my $cmd = $dbh->prepare("delete from webmin_group_acl ".
3804                                                 "where id = ? and module = ?");
3805                         $cmd && $cmd->execute($id, $m) ||
3806                             &error(&text('egroupdbacl', $dbh->errstr));
3807                         $cmd->finish();
3808                         if ($_[0]) {
3809                                 my $cmd = $dbh->prepare(
3810                                     "insert into webmin_group_acl ".
3811                                     "(id,module,attr,value) values (?,?,?,?)");
3812                                 $cmd || &error(&text('egroupdbacl2',
3813                                                      $dbh->errstr));
3814                                 foreach my $a (keys %{$_[0]}) {
3815                                         $cmd->execute($id,$m,$a,$_[0]->{$a}) ||
3816                                             &error(&text('egroupdbacl2',
3817                                                          $dbh->errstr));
3818                                         $cmd->finish();
3819                                         }
3820                                 }
3821                         }
3822                 }
3823         elsif ($proto eq "ldap") {
3824                 # Find the group in LDAP
3825                 my $rv = $dbh->search(
3826                         base => $prefix,
3827                         filter => '(&(cn='.$g.')(objectClass='.
3828                                   $args->{'groupclass'}.'))',
3829                         scope => 'sub');
3830                 if (!$rv || $rv->code) {
3831                         &error(&text('egroupdbacl',
3832                                      $rv ? $rv->error : "Unknown error"));
3833                         }
3834                 my ($group) = $rv->all_entries;
3835
3836                 my $ldapm = $m || "global";
3837                 if ($group) {
3838                         # Find the ACL sub-object for the module
3839                         $foundindb = 1;
3840                         my $rv = $dbh->search(
3841                                 base => $group->dn(),
3842                                 filter => '(cn='.$ldapm.')',
3843                                 scope => 'one');
3844                         if (!$rv || $rv->code) {
3845                                 &error(&text('egroupdbacl',
3846                                      $rv ? $rv->error : "Unknown error"));
3847                                 }
3848                         my ($acl) = $rv->all_entries;
3849
3850                         my @al;
3851                         foreach my $a (keys %{$_[0]}) {
3852                                 push(@al, $a."=".$_[0]->{$a});
3853                                 }
3854                         if ($acl) {
3855                                 # Update attributes
3856                                 $rv = $dbh->modify($acl->dn(),
3857                                         replace => { "webminAclEntry", \@al });
3858                                 }
3859                         else {
3860                                 # Add a sub-object
3861                                 my @attrs = ( "cn", $ldapm,
3862                                               "objectClass", "webminAcl",
3863                                               "webminAclEntry", \@al );
3864                                 $rv = $dbh->add("cn=".$ldapm.",".$group->dn(),
3865                                                 attr => \@attrs);
3866                                 }
3867                         if (!$rv || $rv->code) {
3868                                 &error(&text('egroupdbacl2',
3869                                      $rv ? $rv->error : "Unknown error"));
3870                                 }
3871                         }
3872                 }
3873         &disconnect_userdb($userdb, $dbh);
3874         }
3875
3876 if (!$foundindb) {
3877         # Save ACL to local file
3878         if (!-d "$config_directory/$m") {
3879                 mkdir("$config_directory/$m", 0755);
3880                 }
3881         if ($_[0]) {
3882                 &write_file("$config_directory/$m/$g.gacl", $_[0]);
3883                 }
3884         else {
3885                 &unlink_file("$config_directory/$m/$g.gacl");
3886                 }
3887         }
3888 }
3889
3890 =head2 init_config
3891
3892 This function must be called by all Webmin CGI scripts, either directly or
3893 indirectly via a per-module lib.pl file. It performs a number of initialization
3894 and housekeeping tasks, such as working out the module name, checking that the
3895 current user has access to the module, and populating global variables. Some
3896 of the variables set include :
3897
3898 =item $config_directory - Base Webmin config directory, typically /etc/webmin
3899
3900 =item $var_directory - Base logs directory, typically /var/webmin
3901
3902 =item %config - Per-module configuration.
3903
3904 =item %gconfig - Global configuration.
3905
3906 =item $scriptname - Base name of the current perl script.
3907
3908 =item $module_name - The name of the current module.
3909
3910 =item $module_config_directory - The config directory for this module.
3911
3912 =item $module_config_file - The config file for this module.
3913
3914 =item $module_root_directory - This module's code directory.
3915
3916 =item $webmin_logfile - The detailed logfile for webmin.
3917
3918 =item $remote_user - The actual username used to login to webmin.
3919
3920 =item $base_remote_user - The username whose permissions are in effect.
3921
3922 =item $current_theme - The theme currently in use.
3923
3924 =item $root_directory - The first root directory of this webmin install.
3925
3926 =item @root_directories - All root directories for this webmin install.
3927
3928 =cut
3929 sub init_config
3930 {
3931 # Record first process ID that called this, so we know when it exited to clean
3932 # up temp files
3933 $main::initial_process_id ||= $$;
3934
3935 # Configuration and spool directories
3936 if (!defined($ENV{'WEBMIN_CONFIG'})) {
3937         die "WEBMIN_CONFIG not set";
3938         }
3939 $config_directory = $ENV{'WEBMIN_CONFIG'};
3940 if (!defined($ENV{'WEBMIN_VAR'})) {
3941         open(VARPATH, "$config_directory/var-path");
3942         chop($var_directory = <VARPATH>);
3943         close(VARPATH);
3944         }
3945 else {
3946         $var_directory = $ENV{'WEBMIN_VAR'};
3947         }
3948 $main::http_cache_directory = $ENV{'WEBMIN_VAR'}."/cache";
3949 $main::default_debug_log_file = $ENV{'WEBMIN_VAR'}."/webmin.debug";
3950
3951 if ($ENV{'SESSION_ID'}) {
3952         # Hide this variable from called programs, but keep it for internal use
3953         $main::session_id = $ENV{'SESSION_ID'};
3954         delete($ENV{'SESSION_ID'});
3955         }
3956 if ($ENV{'REMOTE_PASS'}) {
3957         # Hide the password too
3958         $main::remote_pass = $ENV{'REMOTE_PASS'};
3959         delete($ENV{'REMOTE_PASS'});
3960         }
3961
3962 if ($> == 0 && $< != 0 && !$ENV{'FOREIGN_MODULE_NAME'}) {
3963         # Looks like we are running setuid, but the real UID hasn't been set.
3964         # Do so now, so that executed programs don't get confused
3965         $( = $);
3966         $< = $>;
3967         }
3968
3969 # Read the webmin global config file. This contains the OS type and version,
3970 # OS specific configuration and global options such as proxy servers
3971 $config_file = "$config_directory/config";
3972 %gconfig = ( );
3973 &read_file_cached($config_file, \%gconfig);
3974 $null_file = $gconfig{'os_type'} eq 'windows' ? "NUL" : "/dev/null";
3975 $path_separator = $gconfig{'os_type'} eq 'windows' ? ';' : ':';
3976
3977 # If debugging is enabled, open the debug log
3978 if ($gconfig{'debug_enabled'} && !$main::opened_debug_log++) {
3979         my $dlog = $gconfig{'debug_file'} || $main::default_debug_log_file;
3980         if ($gconfig{'debug_size'}) {
3981                 my @st = stat($dlog);
3982                 if ($st[7] > $gconfig{'debug_size'}) {
3983                         rename($dlog, $dlog.".0");
3984                         }
3985                 }
3986         open(main::DEBUGLOG, ">>$dlog");
3987         $main::opened_debug_log = 1;
3988
3989         if ($gconfig{'debug_what_start'}) {
3990                 my $script_name = $0 =~ /([^\/]+)$/ ? $1 : '-';
3991                 $main::debug_log_start_time = time();
3992                 &webmin_debug_log("START", "script=$script_name");
3993                 $main::debug_log_start_module = $module_name;
3994                 }
3995         }
3996
3997 # Set PATH and LD_LIBRARY_PATH
3998 if ($gconfig{'path'}) {
3999         if ($gconfig{'syspath'}) {
4000                 # Webmin only
4001                 $ENV{'PATH'} = $gconfig{'path'};
4002                 }
4003         else {
4004                 # Include OS too
4005                 $ENV{'PATH'} = $gconfig{'path'}.$path_separator.$ENV{'PATH'};
4006                 }
4007         }
4008 $ENV{$gconfig{'ld_env'}} = $gconfig{'ld_path'} if ($gconfig{'ld_env'});
4009
4010 # Set http_proxy and ftp_proxy environment variables, based on Webmin settings
4011 if ($gconfig{'http_proxy'}) {
4012         $ENV{'http_proxy'} = $gconfig{'http_proxy'};
4013         }
4014 if ($gconfig{'ftp_proxy'}) {
4015         $ENV{'ftp_proxy'} = $gconfig{'ftp_proxy'};
4016         }
4017 if ($gconfig{'noproxy'}) {
4018         $ENV{'no_proxy'} = $gconfig{'noproxy'};
4019         }
4020
4021 # Find all root directories
4022 my %miniserv;
4023 if (&get_miniserv_config(\%miniserv)) {
4024         @root_directories = ( $miniserv{'root'} );
4025         for($i=0; defined($miniserv{"extraroot_$i"}); $i++) {
4026                 push(@root_directories, $miniserv{"extraroot_$i"});
4027                 }
4028         }
4029
4030 # Work out which module we are in, and read the per-module config file
4031 $0 =~ s/\\/\//g;        # Force consistent path on Windows
4032 if (defined($ENV{'FOREIGN_MODULE_NAME'})) {
4033         # In a foreign call - use the module name given
4034         $root_directory = $ENV{'FOREIGN_ROOT_DIRECTORY'};
4035         $module_name = $ENV{'FOREIGN_MODULE_NAME'};
4036         @root_directories = ( $root_directory ) if (!@root_directories);
4037         }
4038 elsif ($ENV{'SCRIPT_NAME'}) {
4039         my $sn = $ENV{'SCRIPT_NAME'};
4040         $sn =~ s/^$gconfig{'webprefix'}//
4041                 if (!$gconfig{'webprefixnoredir'});
4042         if ($sn =~ /^\/([^\/]+)\//) {
4043                 # Get module name from CGI path
4044                 $module_name = $1;
4045                 }
4046         if ($ENV{'SERVER_ROOT'}) {
4047                 $root_directory = $ENV{'SERVER_ROOT'};
4048                 }
4049         elsif ($ENV{'SCRIPT_FILENAME'}) {
4050                 $root_directory = $ENV{'SCRIPT_FILENAME'};
4051                 $root_directory =~ s/$sn$//;
4052                 }
4053         @root_directories = ( $root_directory ) if (!@root_directories);
4054         }
4055 else {
4056         # Get root directory from miniserv.conf, and deduce module name from $0
4057         $root_directory = $root_directories[0];
4058         my $rok = 0;
4059         foreach my $r (@root_directories) {
4060                 if ($0 =~ /^$r\/([^\/]+)\/[^\/]+$/i) {
4061                         # Under a module directory
4062                         $module_name = $1;
4063                         $rok = 1;
4064                         last;
4065                         }
4066                 elsif ($0 =~ /^$root_directory\/[^\/]+$/i) {
4067                         # At the top level
4068                         $rok = 1;
4069                         last;
4070                         }
4071                 }
4072         &error("Script was not run with full path (failed to find $0 under $root_directory)") if (!$rok);
4073         }
4074
4075 # Work out of this is a web, command line or cron job
4076 if (!$main::webmin_script_type) {
4077         if ($ENV{'SCRIPT_NAME'}) {
4078                 # Run via a CGI
4079                 $main::webmin_script_type = 'web';
4080                 }
4081         else {
4082                 # Cron jobs have no TTY
4083                 if ($gconfig{'os_type'} eq 'windows' ||
4084                     open(DEVTTY, ">/dev/tty")) {
4085                         $main::webmin_script_type = 'cmd';
4086                         close(DEVTTY);
4087                         }
4088                 else {
4089                         $main::webmin_script_type = 'cron';
4090                         }
4091                 }
4092         }
4093
4094 # Set the umask based on config
4095 if ($gconfig{'umask'} && !$main::umask_already++) {
4096         umask(oct($gconfig{'umask'}));
4097         }
4098
4099 # If this is a cron job or other background task, set the nice level
4100 if (!$main::nice_already && $main::webmin_script_type eq 'cron') {
4101         # Set nice level
4102         if ($gconfig{'nice'}) {
4103                 eval 'POSIX::nice($gconfig{\'nice\'});';
4104                 }
4105
4106         # Set IO scheduling class and priority
4107         if ($gconfig{'sclass'} ne '' || $gconfig{'sprio'} ne '') {
4108                 my $cmd = "ionice";
4109                 $cmd .= " -c ".quotemeta($gconfig{'sclass'})
4110                         if ($gconfig{'sclass'} ne '');
4111                 $cmd .= " -n ".quotemeta($gconfig{'sprio'})
4112                         if ($gconfig{'sprio'} ne '');
4113                 $cmd .= " -p $$";
4114                 &execute_command("$cmd >/dev/null 2>&1");
4115                 }
4116         }
4117 $main::nice_already++;
4118
4119 # Get the username
4120 my $u = $ENV{'BASE_REMOTE_USER'} || $ENV{'REMOTE_USER'};
4121 $base_remote_user = $u;
4122 $remote_user = $ENV{'REMOTE_USER'};
4123
4124 # Work out if user is definitely in the DB, and if so get his attrs
4125 $remote_user_proto = $ENV{"REMOTE_USER_PROTO"};
4126 %remote_user_attrs = ( );
4127 if ($remote_user_proto) {
4128         my $userdb = &get_userdb_string();
4129         my ($dbh, $proto, $prefix, $args) =
4130                 $userdb ? &connect_userdb($userdb) : ( );
4131         if (ref($dbh)) {
4132                 if ($proto eq "mysql" || $proto eq "postgresql") {
4133                         # Read attrs from SQL
4134                         my $cmd = $dbh->prepare("select webmin_user_attr.attr,webmin_user_attr.value from webmin_user_attr,webmin_user where webmin_user_attr.id = webmin_user.id and webmin_user.name = ?");
4135                         if ($cmd && $cmd->execute($base_remote_user)) {
4136                                 while(my ($attr, $value) = $cmd->fetchrow()) {
4137                                         $remote_user_attrs{$attr} = $value;
4138                                         }
4139                                 $cmd->finish();
4140                                 }
4141                         }
4142                 elsif ($proto eq "ldap") {
4143                         # Read attrs from LDAP
4144                         my $rv = $dbh->search(
4145                                 base => $prefix,
4146                                 filter => '(&(cn='.$base_remote_user.')'.
4147                                           '(objectClass='.
4148                                           $args->{'userclass'}.'))',
4149                                 scope => 'sub');
4150                         my ($u) = $rv && !$rv->code ? $rv->all_entries : ( );
4151                         if ($u) {
4152                                 foreach $la ($u->get_value('webminAttr')) {
4153                                         my ($attr, $value) = split(/=/, $la, 2);
4154                                         $remote_user_attrs{$attr} = $value;
4155                                         }
4156                                 }
4157                         }
4158                 &disconnect_userdb($userdb, $dbh);
4159                 }
4160         }
4161
4162 if ($module_name) {
4163         # Find and load the configuration file for this module
4164         my (@ruinfo, $rgroup);
4165         $module_config_directory = "$config_directory/$module_name";
4166         if (&get_product_name() eq "usermin" &&
4167             -r "$module_config_directory/config.$remote_user") {
4168                 # Based on username
4169                 $module_config_file = "$module_config_directory/config.$remote_user";
4170                 }
4171         elsif (&get_product_name() eq "usermin" &&
4172             (@ruinfo = getpwnam($remote_user)) &&
4173             ($rgroup = getgrgid($ruinfo[3])) &&
4174             -r "$module_config_directory/config.\@$rgroup") {
4175                 # Based on group name
4176                 $module_config_file = "$module_config_directory/config.\@$rgroup";
4177                 }
4178         else {
4179                 # Global config
4180                 $module_config_file = "$module_config_directory/config";
4181                 }
4182         %config = ( );
4183         &read_file_cached($module_config_file, \%config);
4184
4185         # Fix up windows-specific substitutions in values
4186         foreach my $k (keys %config) {
4187                 if ($config{$k} =~ /\$\{systemroot\}/) {
4188                         my $root = &get_windows_root();
4189                         $config{$k} =~ s/\$\{systemroot\}/$root/g;
4190                         }
4191                 }
4192         }
4193
4194 # Record the initial module
4195 $main::initial_module_name ||= $module_name;
4196
4197 # Set some useful variables
4198 my $current_themes;
4199 $current_themes = $ENV{'MOBILE_DEVICE'} && defined($gconfig{'mobile_theme'}) ?
4200                     $gconfig{'mobile_theme'} :
4201                   defined($remote_user_attrs{'theme'}) ?
4202                     $remote_user_attrs{'theme'} :
4203                   defined($gconfig{'theme_'.$remote_user}) ?
4204                     $gconfig{'theme_'.$remote_user} :
4205                   defined($gconfig{'theme_'.$base_remote_user}) ?
4206                     $gconfig{'theme_'.$base_remote_user} :
4207                     $gconfig{'theme'};
4208 @current_themes = split(/\s+/, $current_themes);
4209 $current_theme = $current_themes[0];
4210 @theme_root_directories = map { "$root_directory/$_" } @current_themes;
4211 $theme_root_directory = $theme_root_directories[0];
4212 @theme_configs = ( );
4213 foreach my $troot (@theme_root_directories) {
4214         my %onetconfig;
4215         &read_file_cached("$troot/config", \%onetconfig);
4216         &read_file_cached("$troot/config", \%tconfig);
4217         push(@theme_configs, \%onetconfig);
4218         }
4219 $tb = defined($tconfig{'cs_header'}) ? "bgcolor=#$tconfig{'cs_header'}" :
4220       defined($gconfig{'cs_header'}) ? "bgcolor=#$gconfig{'cs_header'}" :
4221                                        "bgcolor=#9999ff";
4222 $cb = defined($tconfig{'cs_table'}) ? "bgcolor=#$tconfig{'cs_table'}" :
4223       defined($gconfig{'cs_table'}) ? "bgcolor=#$gconfig{'cs_table'}" :
4224                                       "bgcolor=#cccccc";
4225 $tb .= ' '.$tconfig{'tb'} if ($tconfig{'tb'});
4226 $cb .= ' '.$tconfig{'cb'} if ($tconfig{'cb'});
4227 if ($tconfig{'preload_functions'}) {
4228         # Force load of theme functions right now, if requested
4229         &load_theme_library();
4230         }
4231 if ($tconfig{'oofunctions'} && !$main::loaded_theme_oo_library++) {
4232         # Load the theme's Webmin:: package classes
4233         do "$theme_root_directory/$tconfig{'oofunctions'}";
4234         }
4235
4236 $0 =~ /([^\/]+)$/;
4237 $scriptname = $1;
4238 $webmin_logfile = $gconfig{'webmin_log'} ? $gconfig{'webmin_log'}
4239                                          : "$var_directory/webmin.log";
4240
4241 # Load language strings into %text
4242 my @langs = &list_languages();
4243 my $accepted_lang;
4244 if ($gconfig{'acceptlang'}) {
4245         foreach my $a (split(/,/, $ENV{'HTTP_ACCEPT_LANGUAGE'})) {
4246                 my ($al) = grep { $_->{'lang'} eq $a } @langs;
4247                 if ($al) {
4248                         $accepted_lang = $al->{'lang'};
4249                         last;
4250                         }
4251                 }
4252         }
4253 $current_lang = $force_lang ? $force_lang :
4254     $accepted_lang ? $accepted_lang :
4255     $remote_user_attrs{'lang'} ? $remote_user_attrs{'lang'} :
4256     $gconfig{"lang_$remote_user"} ? $gconfig{"lang_$remote_user"} :
4257     $gconfig{"lang_$base_remote_user"} ? $gconfig{"lang_$base_remote_user"} :
4258     $gconfig{"lang"} ? $gconfig{"lang"} : $default_lang;
4259 foreach my $l (@langs) {
4260         $current_lang_info = $l if ($l->{'lang'} eq $current_lang);
4261         }
4262 @lang_order_list = &unique($default_lang,
4263                            split(/:/, $current_lang_info->{'fallback'}),
4264                            $current_lang);
4265 %text = &load_language($module_name);
4266 %text || &error("Failed to determine Webmin root from SERVER_ROOT, SCRIPT_FILENAME or the full command line");
4267
4268 # Get the %module_info for this module
4269 if ($module_name) {
4270         my ($mi) = grep { $_->{'dir'} eq $module_name }
4271                          &get_all_module_infos(2);
4272         %module_info = %$mi;
4273         $module_root_directory = &module_root_directory($module_name);
4274         }
4275
4276 if ($module_name && !$main::no_acl_check &&
4277     !defined($ENV{'FOREIGN_MODULE_NAME'})) {
4278         # Check if the HTTP user can access this module
4279         if (!&foreign_available($module_name)) {
4280                 if (!&foreign_check($module_name)) {
4281                         &error(&text('emodulecheck',
4282                                      "<i>$module_info{'desc'}</i>"));
4283                         }
4284                 else {
4285                         &error(&text('emodule', "<i>$u</i>",
4286                                      "<i>$module_info{'desc'}</i>"));
4287                         }
4288                 }
4289         $main::no_acl_check++;
4290         }
4291
4292 # Check the Referer: header for nasty redirects
4293 my @referers = split(/\s+/, $gconfig{'referers'});
4294 my $referer_site;
4295 my $r = $ENV{'HTTP_REFERER'};
4296 if ($r =~ /^(http|https|ftp):\/\/([^:\/]+:[^@\/]+@)?\[([^\]]+)\]/ ||
4297     $r =~ /^(http|https|ftp):\/\/([^:\/]+:[^@\/]+@)?([^\/:@]+)/) {
4298         $referer_site = $3;
4299         }
4300 my $http_host = $ENV{'HTTP_HOST'};
4301 $http_host =~ s/:\d+$//;
4302 $http_host =~ s/^\[(\S+)\]$/$1/;
4303 my $unsafe_index = $unsafe_index_cgi ||
4304                    &get_module_variable('$unsafe_index_cgi');
4305 if ($0 &&
4306     ($ENV{'SCRIPT_NAME'} !~ /^\/(index.cgi)?$/ || $unsafe_index) &&
4307     ($ENV{'SCRIPT_NAME'} !~ /^\/([a-z0-9\_\-]+)\/(index.cgi)?$/i ||
4308      $unsafe_index) &&
4309     $0 !~ /(session_login|pam_login)\.cgi$/ && !$gconfig{'referer'} &&
4310     $ENV{'MINISERV_CONFIG'} && !$main::no_referers_check &&
4311     $ENV{'HTTP_USER_AGENT'} !~ /^Webmin/i &&
4312     ($referer_site && $referer_site ne $http_host &&
4313      &indexof($referer_site, @referers) < 0 ||
4314     !$referer_site && $gconfig{'referers_none'}) &&
4315     !$trust_unknown_referers &&
4316     !&get_module_variable('$trust_unknown_referers')) {
4317         # Looks like a link from elsewhere .. show an error
4318         &header($text{'referer_title'}, "", undef, 0, 1, 1);
4319
4320         $prot = lc($ENV{'HTTPS'}) eq 'on' ? "https" : "http";
4321         my $url = "<tt>".&html_escape("$prot://$ENV{'HTTP_HOST'}$ENV{'REQUEST_URI'}")."</tt>";
4322         if ($referer_site) {
4323                 # From a known host
4324                 print &text('referer_warn',
4325                             "<tt>".&html_escape($r)."</tt>", $url);
4326                 print "<p>\n";
4327                 print &text('referer_fix1', &html_escape($http_host)),"<p>\n";
4328                 print &text('referer_fix2', &html_escape($http_host)),"<p>\n";
4329                 }
4330         else {
4331                 # No referer info given
4332                 print &text('referer_warn_unknown', $url),"<p>\n";
4333                 print &text('referer_fix1u'),"<p>\n";
4334                 print &text('referer_fix2u'),"<p>\n";
4335                 }
4336         print "<p>\n";
4337
4338         &footer("/", $text{'index'});
4339         exit;
4340         }
4341 $main::no_referers_check++;
4342 $main::completed_referers_check++;
4343
4344 # Call theme post-init
4345 if (defined(&theme_post_init_config)) {
4346         &theme_post_init_config(@_);
4347         }
4348
4349 # Record that we have done the calling library in this package
4350 my ($callpkg, $lib) = caller();
4351 $lib =~ s/^.*\///;
4352 $main::done_foreign_require{$callpkg,$lib} = 1;
4353
4354 # If a licence checking is enabled, do it now
4355 if ($gconfig{'licence_module'} && !$main::done_licence_module_check &&
4356     &foreign_check($gconfig{'licence_module'}) &&
4357     -r "$root_directory/$gconfig{'licence_module'}/licence_check.pl") {
4358         my $oldpwd = &get_current_dir();
4359         $main::done_licence_module_check++;
4360         $main::licence_module = $gconfig{'licence_module'};
4361         &foreign_require($main::licence_module, "licence_check.pl");
4362         ($main::licence_status, $main::licence_message) =
4363                 &foreign_call($main::licence_module, "check_licence");
4364         chdir($oldpwd);
4365         }
4366
4367 # Export global variables to caller
4368 if ($main::export_to_caller) {
4369         foreach my $v ('$config_file', '%gconfig', '$null_file',
4370                        '$path_separator', '@root_directories',
4371                        '$root_directory', '$module_name',
4372                        '$base_remote_user', '$remote_user',
4373                        '$remote_user_proto', '%remote_user_attrs',
4374                        '$module_config_directory', '$module_config_file',
4375                        '%config', '@current_themes', '$current_theme',
4376                        '@theme_root_directories', '$theme_root_directory',
4377                        '%tconfig','@theme_configs', '$tb', '$cb', '$scriptname',
4378                        '$webmin_logfile', '$current_lang',
4379                        '$current_lang_info', '@lang_order_list', '%text',
4380                        '%module_info', '$module_root_directory') {
4381                 my ($vt, $vn) = split('', $v, 2);
4382                 eval "${vt}${callpkg}::${vn} = ${vt}${vn}";
4383                 }
4384         }
4385
4386 return 1;
4387 }
4388
4389 =head2 load_language([module], [directory])
4390
4391 Returns a hashtable mapping text codes to strings in the appropriate language,
4392 based on the $current_lang global variable, which is in turn set based on
4393 the Webmin user's selection. The optional module parameter tells the function
4394 which module to load strings for, and defaults to the calling module. The
4395 optional directory parameter can be used to load strings from a directory
4396 other than lang.
4397
4398 In regular module development you will never need to call this function
4399 directly, as init_config calls it for you, and places the module's strings
4400 into the %text hash.
4401
4402 =cut
4403 sub load_language
4404 {
4405 my %text;
4406 my $root = $root_directory;
4407 my $ol = $gconfig{'overlang'};
4408 my ($dir) = ($_[1] || "lang");
4409
4410 # Read global lang files
4411 foreach my $o (@lang_order_list) {
4412         my $ok = &read_file_cached("$root/$dir/$o", \%text);
4413         return () if (!$ok && $o eq $default_lang);
4414         }
4415 if ($ol) {
4416         foreach my $o (@lang_order_list) {
4417                 &read_file_cached("$root/$ol/$o", \%text);
4418                 }
4419         }
4420 &read_file_cached("$config_directory/custom-lang", \%text);
4421
4422 if ($_[0]) {
4423         # Read module's lang files
4424         my $mdir = &module_root_directory($_[0]);
4425         foreach my $o (@lang_order_list) {
4426                 &read_file_cached("$mdir/$dir/$o", \%text);
4427                 }
4428         if ($ol) {
4429                 foreach $o (@lang_order_list) {
4430                         &read_file_cached("$mdir/$ol/$o", \%text);
4431                         }
4432                 }
4433         &read_file_cached("$config_directory/$_[0]/custom-lang", \%text);
4434         }
4435 foreach $k (keys %text) {
4436         $text{$k} =~ s/\$(\{([^\}]+)\}|([A-Za-z0-9\.\-\_]+))/text_subs($2 || $3,\%text)/ge;
4437         }
4438
4439 if (defined(&theme_load_language)) {
4440         &theme_load_language(\%text, $_[0]);
4441         }
4442 return %text;
4443 }
4444
4445 =head2 text_subs(string)
4446
4447 Used internally by load_language to expand $code substitutions in language
4448 files.
4449
4450 =cut
4451 sub text_subs
4452 {
4453 if (substr($_[0], 0, 8) eq "include:") {
4454         local $_;
4455         my $rv;
4456         open(INCLUDE, substr($_[0], 8));
4457         while(<INCLUDE>) {
4458                 $rv .= $_;
4459                 }
4460         close(INCLUDE);
4461         return $rv;
4462         }
4463 else {
4464         my $t = $_[1]->{$_[0]};
4465         return defined($t) ? $t : '$'.$_[0];
4466         }
4467 }
4468
4469 =head2 text(message, [substitute]+)
4470
4471 Returns a translated message from %text, but with $1, $2, etc.. replaced with
4472 the substitute parameters. This makes it easy to use strings with placeholders
4473 that get replaced with programmatically generated text. For example :
4474
4475  print &text('index_hello', $remote_user),"<p>\n";
4476
4477 =cut
4478 sub text
4479 {
4480 my $t = &get_module_variable('%text', 1);
4481 my $rv = exists($t->{$_[0]}) ? $t->{$_[0]} : $text{$_[0]};
4482 for(my $i=1; $i<@_; $i++) {
4483         $rv =~ s/\$$i/$_[$i]/g;
4484         }
4485 return $rv;
4486 }
4487
4488 =head2 encode_base64(string)
4489
4490 Encodes a string into base64 format, for use in MIME email or HTTP
4491 authorization headers.
4492
4493 =cut
4494 sub encode_base64
4495 {
4496 my $res;
4497 pos($_[0]) = 0;                          # ensure start at the beginning
4498 while ($_[0] =~ /(.{1,57})/gs) {
4499         $res .= substr(pack('u57', $1), 1)."\n";
4500         chop($res);
4501         }
4502 $res =~ tr|\` -_|AA-Za-z0-9+/|;
4503 my $padding = (3 - length($_[0]) % 3) % 3;
4504 $res =~ s/.{$padding}$/'=' x $padding/e if ($padding);
4505 return $res;
4506 }
4507
4508 =head2 decode_base64(string)
4509
4510 Converts a base64-encoded string into plain text. The opposite of encode_base64.
4511
4512 =cut
4513 sub decode_base64
4514 {
4515 my ($str) = @_;
4516 my $res;
4517 $str =~ tr|A-Za-z0-9+=/||cd;            # remove non-base64 chars
4518 if (length($str) % 4) {
4519         return undef;
4520 }
4521 $str =~ s/=+$//;                        # remove padding
4522 $str =~ tr|A-Za-z0-9+/| -_|;            # convert to uuencoded format
4523 while ($str =~ /(.{1,60})/gs) {
4524         my $len = chr(32 + length($1)*3/4); # compute length byte
4525         $res .= unpack("u", $len . $1 );    # uudecode
4526         }
4527 return $res;
4528 }
4529
4530 =head2 get_module_info(module, [noclone], [forcache])
4531
4532 Returns a hash containg details of the given module. Some useful keys are :
4533
4534 =item dir - The module directory, like sendmail.
4535
4536 =item desc - Human-readable description, in the current users' language.
4537
4538 =item version - Optional module version number.
4539
4540 =item os_support - List of supported operating systems and versions.
4541
4542 =item category - Category on Webmin's left menu, like net.
4543
4544 =cut
4545 sub get_module_info
4546 {
4547 return () if ($_[0] =~ /^\./);
4548 my (%rv, $clone, $o);
4549 my $mdir = &module_root_directory($_[0]);
4550 &read_file_cached("$mdir/module.info", \%rv) || return ();
4551 if (-l $mdir) {
4552         # A clone is a module that links to another directory under the root
4553         foreach my $r (@root_directories) {
4554                 if (&is_under_directory($r, $mdir)) {
4555                         $clone = 1;
4556                         last;
4557                         }
4558                 }
4559         }
4560 foreach $o (@lang_order_list) {
4561         $rv{"desc"} = $rv{"desc_$o"} if ($rv{"desc_$o"});
4562         $rv{"longdesc"} = $rv{"longdesc_$o"} if ($rv{"longdesc_$o"});
4563         }
4564 if ($clone && !$_[1] && $config_directory) {
4565         $rv{'clone'} = $rv{'desc'};
4566         &read_file("$config_directory/$_[0]/clone", \%rv);
4567         }
4568 $rv{'dir'} = $_[0];
4569 my %module_categories;
4570 &read_file_cached("$config_directory/webmin.cats", \%module_categories);
4571 my $pn = &get_product_name();
4572 if (defined($rv{'category_'.$pn})) {
4573         # Can override category for webmin/usermin
4574         $rv{'category'} = $rv{'category_'.$pn};
4575         }
4576 $rv{'realcategory'} = $rv{'category'};
4577 $rv{'category'} = $module_categories{$_[0]}
4578         if (defined($module_categories{$_[0]}));
4579
4580 # Apply description overrides
4581 $rv{'realdesc'} = $rv{'desc'};
4582 my %descs;
4583 &read_file_cached("$config_directory/webmin.descs", \%descs);
4584 if ($descs{$_[0]." ".$current_lang}) {
4585         $rv{'desc'} = $descs{$_[0]." ".$current_lang};
4586         }
4587 elsif ($descs{$_[0]}) {
4588         $rv{'desc'} = $descs{$_[0]};
4589         }
4590
4591 if (!$_[2]) {
4592         # Apply per-user description overridde
4593         my %gaccess = &get_module_acl(undef, "");
4594         if ($gaccess{'desc_'.$_[0]}) {
4595                 $rv{'desc'} = $gaccess{'desc_'.$_[0]};
4596                 }
4597         }
4598
4599 if ($rv{'longdesc'}) {
4600         # All standard modules have an index.cgi
4601         $rv{'index_link'} = 'index.cgi';
4602         }
4603
4604 # Call theme-specific override function
4605 if (defined(&theme_get_module_info)) {
4606         %rv = &theme_get_module_info(\%rv, $_[0], $_[1], $_[2]);
4607         }
4608
4609 return %rv;
4610 }
4611
4612 =head2 get_all_module_infos(cachemode)
4613
4614 Returns a list contains the information on all modules in this webmin
4615 install, including clones. Uses caching to reduce the number of module.info
4616 files that need to be read. Each element of the array is a hash reference
4617 in the same format as returned by get_module_info. The cache mode flag can be :
4618 0 = read and write, 1 = don't read or write, 2 = read only
4619
4620 =cut
4621 sub get_all_module_infos
4622 {
4623 my (%cache, @rv);
4624
4625 # Is the cache out of date? (ie. have any of the root's changed?)
4626 my $cache_file = "$config_directory/module.infos.cache";
4627 my $changed = 0;
4628 if (&read_file_cached($cache_file, \%cache)) {
4629         foreach my $r (@root_directories) {
4630                 my @st = stat($r);
4631                 if ($st[9] != $cache{'mtime_'.$r}) {
4632                         $changed = 2;
4633                         last;
4634                         }
4635                 }
4636         }
4637 else {
4638         $changed = 1;
4639         }
4640
4641 if ($_[0] != 1 && !$changed && $cache{'lang'} eq $current_lang) {
4642         # Can use existing module.info cache
4643         my %mods;
4644         foreach my $k (keys %cache) {
4645                 if ($k =~ /^(\S+) (\S+)$/) {
4646                         $mods{$1}->{$2} = $cache{$k};
4647                         }
4648                 }
4649         @rv = map { $mods{$_} } (keys %mods) if (%mods);
4650         }
4651 else {
4652         # Need to rebuild cache
4653         %cache = ( );
4654         foreach my $r (@root_directories) {
4655                 opendir(DIR, $r);
4656                 foreach my $m (readdir(DIR)) {
4657                         next if ($m =~ /^(config-|\.)/ || $m =~ /\.(cgi|pl)$/);
4658                         my %minfo = &get_module_info($m, 0, 1);
4659                         next if (!%minfo || !$minfo{'dir'});
4660                         push(@rv, \%minfo);
4661                         foreach $k (keys %minfo) {
4662                                 $cache{"${m} ${k}"} = $minfo{$k};
4663                                 }
4664                         }
4665                 closedir(DIR);
4666                 my @st = stat($r);
4667                 $cache{'mtime_'.$r} = $st[9];
4668                 }
4669         $cache{'lang'} = $current_lang;
4670         &write_file($cache_file, \%cache) if (!$_[0] && $< == 0 && $> == 0);
4671         }
4672
4673 # Override descriptions for modules for current user
4674 my %gaccess = &get_module_acl(undef, "");
4675 foreach my $m (@rv) {
4676         if ($gaccess{"desc_".$m->{'dir'}}) {
4677                 $m->{'desc'} = $gaccess{"desc_".$m->{'dir'}};
4678                 }
4679         }
4680
4681 # Apply installed flags
4682 my %installed;
4683 &read_file_cached("$config_directory/installed.cache", \%installed);
4684 foreach my $m (@rv) {
4685         $m->{'installed'} = $installed{$m->{'dir'}};
4686         }
4687
4688 return @rv;
4689 }
4690
4691 =head2 get_theme_info(theme)
4692
4693 Returns a hash containing a theme's details, taken from it's theme.info file.
4694 Some useful keys are :
4695
4696 =item dir - The theme directory, like blue-theme.
4697
4698 =item desc - Human-readable description, in the current users' language.
4699
4700 =item version - Optional module version number.
4701
4702 =item os_support - List of supported operating systems and versions.
4703
4704 =cut
4705 sub get_theme_info
4706 {
4707 return () if ($_[0] =~ /^\./);
4708 my %rv;
4709 my $tdir = &module_root_directory($_[0]);
4710 &read_file("$tdir/theme.info", \%rv) || return ();
4711 foreach my $o (@lang_order_list) {
4712         $rv{"desc"} = $rv{"desc_$o"} if ($rv{"desc_$o"});
4713         }
4714 $rv{"dir"} = $_[0];
4715 return %rv;
4716 }
4717
4718 =head2 list_languages
4719
4720 Returns an array of supported languages, taken from Webmin's os_list.txt file.
4721 Each is a hash reference with the following keys :
4722
4723 =item lang - The short language code, like es for Spanish.
4724
4725 =item desc - A human-readable description, in English.
4726
4727 =item charset - An optional character set to use when displaying the language.
4728
4729 =item titles - Set to 1 only if Webmin has title images for the language.
4730
4731 =item fallback - The code for another language to use if a string does not exist in this one. For all languages, English is the ultimate fallback.
4732
4733 =cut
4734 sub list_languages
4735 {
4736 if (!@main::list_languages_cache) {
4737         my $o;
4738         local $_;
4739         open(LANG, "$root_directory/lang_list.txt");
4740         while(<LANG>) {
4741                 if (/^(\S+)\s+(.*)/) {
4742                         my $l = { 'desc' => $2 };
4743                         foreach $o (split(/,/, $1)) {
4744                                 if ($o =~ /^([^=]+)=(.*)$/) {
4745                                         $l->{$1} = $2;
4746                                         }
4747                                 }
4748                         $l->{'index'} = scalar(@rv);
4749                         push(@main::list_languages_cache, $l);
4750                         }
4751                 }
4752         close(LANG);
4753         @main::list_languages_cache = sort { $a->{'desc'} cmp $b->{'desc'} }
4754                                      @main::list_languages_cache;
4755         }
4756 return @main::list_languages_cache;
4757 }
4758
4759 =head2 read_env_file(file, &hash)
4760
4761 Similar to Webmin's read_file function, but handles files containing shell
4762 environment variables formatted like :
4763
4764   export FOO=bar
4765   SMEG="spod"
4766
4767 The file parameter is the full path to the file to read, and hash a Perl hash
4768 ref to read names and values into.
4769
4770 =cut
4771 sub read_env_file
4772 {
4773 local $_;
4774 &open_readfile(FILE, $_[0]) || return 0;
4775 while(<FILE>) {
4776         s/#.*$//g;
4777         if (/^\s*(export\s*)?([A-Za-z0-9_\.]+)\s*=\s*"(.*)"/i ||
4778             /^\s*(export\s*)?([A-Za-z0-9_\.]+)\s*=\s*'(.*)'/i ||
4779             /^\s*(export\s*)?([A-Za-z0-9_\.]+)\s*=\s*(.*)/i) {
4780                 $_[1]->{$2} = $3;
4781                 }
4782         }
4783 close(FILE);
4784 return 1;
4785 }
4786
4787 =head2 write_env_file(file, &hash, [export])
4788
4789 Writes out a hash to a file in name='value' format, suitable for use in a shell
4790 script. The parameters are :
4791
4792 =item file - Full path for a file to write to
4793
4794 =item hash - Hash reference of names and values to write.
4795
4796 =item export - If set to 1, preceed each variable setting with the word 'export'.
4797
4798 =cut
4799 sub write_env_file
4800 {
4801 my $exp = $_[2] ? "export " : "";
4802 &open_tempfile(FILE, ">$_[0]");
4803 foreach my $k (keys %{$_[1]}) {
4804         my $v = $_[1]->{$k};
4805         if ($v =~ /^\S+$/) {
4806                 &print_tempfile(FILE, "$exp$k=$v\n");
4807                 }
4808         else {
4809                 &print_tempfile(FILE, "$exp$k=\"$v\"\n");
4810                 }
4811         }
4812 &close_tempfile(FILE);
4813 }
4814
4815 =head2 lock_file(filename, [readonly], [forcefile])
4816
4817 Lock a file for exclusive access. If the file is already locked, spin
4818 until it is freed. Uses a .lock file, which is not 100% reliable, but seems
4819 to work OK. The parameters are :
4820
4821 =item filename - File or directory to lock.
4822
4823 =item readonly - If set, the lock is for reading the file only. More than one script can have a readonly lock, but only one can hold a write lock.
4824
4825 =item forcefile - Force the file to be considered as a real file and not a symlink for Webmin actions logging purposes.
4826
4827 =cut
4828 sub lock_file
4829 {
4830 my $realfile = &translate_filename($_[0]);
4831 return 0 if (!$_[0] || defined($main::locked_file_list{$realfile}));
4832 my $no_lock = !&can_lock_file($realfile);
4833 my $lock_tries_count = 0;
4834 while(1) {
4835         my $pid;
4836         if (!$no_lock && open(LOCKING, "$realfile.lock")) {
4837                 $pid = <LOCKING>;
4838                 $pid = int($pid);
4839                 close(LOCKING);
4840                 }
4841         if ($no_lock || !$pid || !kill(0, $pid) || $pid == $$) {
4842                 # Got the lock!
4843                 if (!$no_lock) {
4844                         # Create the .lock file
4845                         open(LOCKING, ">$realfile.lock") || return 0;
4846                         my $lck = eval "flock(LOCKING, 2+4)";
4847                         if (!$lck && !$@) {
4848                                 # Lock of lock file failed! Wait till later
4849                                 goto tryagain;
4850                                 }
4851                         print LOCKING $$,"\n";
4852                         eval "flock(LOCKING, 8)";
4853                         close(LOCKING);
4854                         }
4855                 $main::locked_file_list{$realfile} = int($_[1]);
4856                 push(@main::temporary_files, "$realfile.lock");
4857                 if (($gconfig{'logfiles'} || $gconfig{'logfullfiles'}) &&
4858                     !&get_module_variable('$no_log_file_changes') &&
4859                     !$_[1]) {
4860                         # Grab a copy of this file for later diffing
4861                         my $lnk;
4862                         $main::locked_file_data{$realfile} = undef;
4863                         if (-d $realfile) {
4864                                 $main::locked_file_type{$realfile} = 1;
4865                                 $main::locked_file_data{$realfile} = '';
4866                                 }
4867                         elsif (!$_[2] && ($lnk = readlink($realfile))) {
4868                                 $main::locked_file_type{$realfile} = 2;
4869                                 $main::locked_file_data{$realfile} = $lnk;
4870                                 }
4871                         elsif (open(ORIGFILE, $realfile)) {
4872                                 $main::locked_file_type{$realfile} = 0;
4873                                 $main::locked_file_data{$realfile} = '';
4874                                 local $_;
4875                                 while(<ORIGFILE>) {
4876                                         $main::locked_file_data{$realfile} .=$_;
4877                                         }
4878                                 close(ORIGFILE);
4879                                 }
4880                         }
4881                 last;
4882                 }
4883 tryagain:
4884         sleep(1);
4885         if ($lock_tries_count++ > 5*60) {
4886                 # Give up after 5 minutes
4887                 &error(&text('elock_tries', "<tt>$realfile</tt>", 5));
4888                 }
4889         }
4890 return 1;
4891 }
4892
4893 =head2 unlock_file(filename)
4894
4895 Release a lock on a file taken out by lock_file. If Webmin actions logging of
4896 file changes is enabled, then at unlock file a diff will be taken between the
4897 old and new contents, and stored under /var/webmin/diffs when webmin_log is
4898 called. This can then be viewed in the Webmin Actions Log module.
4899
4900 =cut
4901 sub unlock_file
4902 {
4903 my $realfile = &translate_filename($_[0]);
4904 return if (!$_[0] || !defined($main::locked_file_list{$realfile}));
4905 unlink("$realfile.lock") if (&can_lock_file($realfile));
4906 delete($main::locked_file_list{$realfile});
4907 if (exists($main::locked_file_data{$realfile})) {
4908         # Diff the new file with the old
4909         stat($realfile);
4910         my $lnk = readlink($realfile);
4911         my $type = -d _ ? 1 : $lnk ? 2 : 0;
4912         my $oldtype = $main::locked_file_type{$realfile};
4913         my $new = !defined($main::locked_file_data{$realfile});
4914         if ($new && !-e _) {
4915                 # file doesn't exist, and never did! do nothing ..
4916                 }
4917         elsif ($new && $type == 1 || !$new && $oldtype == 1) {
4918                 # is (or was) a directory ..
4919                 if (-d _ && !defined($main::locked_file_data{$realfile})) {
4920                         push(@main::locked_file_diff,
4921                              { 'type' => 'mkdir', 'object' => $realfile });
4922                         }
4923                 elsif (!-d _ && defined($main::locked_file_data{$realfile})) {
4924                         push(@main::locked_file_diff,
4925                              { 'type' => 'rmdir', 'object' => $realfile });
4926                         }
4927                 }
4928         elsif ($new && $type == 2 || !$new && $oldtype == 2) {
4929                 # is (or was) a symlink ..
4930                 if ($lnk && !defined($main::locked_file_data{$realfile})) {
4931                         push(@main::locked_file_diff,
4932                              { 'type' => 'symlink', 'object' => $realfile,
4933                                'data' => $lnk });
4934                         }
4935                 elsif (!$lnk && defined($main::locked_file_data{$realfile})) {
4936                         push(@main::locked_file_diff,
4937                              { 'type' => 'unsymlink', 'object' => $realfile,
4938                                'data' => $main::locked_file_data{$realfile} });
4939                         }
4940                 elsif ($lnk ne $main::locked_file_data{$realfile}) {
4941                         push(@main::locked_file_diff,
4942                              { 'type' => 'resymlink', 'object' => $realfile,
4943                                'data' => $lnk });
4944                         }
4945                 }
4946         else {
4947                 # is a file, or has changed type?!
4948                 my ($diff, $delete_file);
4949                 my $type = "modify";
4950                 if (!-r _) {
4951                         open(NEWFILE, ">$realfile");
4952                         close(NEWFILE);
4953                         $delete_file++;
4954                         $type = "delete";
4955                         }
4956                 if (!defined($main::locked_file_data{$realfile})) {
4957                         $type = "create";
4958                         }
4959                 open(ORIGFILE, ">$realfile.webminorig");
4960                 print ORIGFILE $main::locked_file_data{$realfile};
4961                 close(ORIGFILE);
4962                 $diff = &backquote_command(
4963                         "diff ".quotemeta("$realfile.webminorig")." ".
4964                                 quotemeta($realfile)." 2>/dev/null");
4965                 push(@main::locked_file_diff,
4966                      { 'type' => $type, 'object' => $realfile,
4967                        'data' => $diff } ) if ($diff);
4968                 unlink("$realfile.webminorig");
4969                 unlink($realfile) if ($delete_file);
4970                 }
4971
4972         if ($gconfig{'logfullfiles'}) {
4973                 # Add file details to list of those to fully log
4974                 $main::orig_file_data{$realfile} ||=
4975                         $main::locked_file_data{$realfile};
4976                 $main::orig_file_type{$realfile} ||=
4977                         $main::locked_file_type{$realfile};
4978                 }
4979
4980         delete($main::locked_file_data{$realfile});
4981         delete($main::locked_file_type{$realfile});
4982         }
4983 }
4984
4985 =head2 test_lock(file)
4986
4987 Returns 1 if some file is currently locked, 0 if not.
4988
4989 =cut
4990 sub test_lock
4991 {
4992 my $realfile = &translate_filename($_[0]);
4993 return 0 if (!$_[0]);
4994 return 1 if (defined($main::locked_file_list{$realfile}));
4995 return 0 if (!&can_lock_file($realfile));
4996 my $pid;
4997 if (open(LOCKING, "$realfile.lock")) {
4998         $pid = <LOCKING>;
4999         $pid = int($pid);
5000         close(LOCKING);
5001         }
5002 return $pid && kill(0, $pid);
5003 }
5004
5005 =head2 unlock_all_files
5006
5007 Unlocks all files locked by the current script.
5008
5009 =cut
5010 sub unlock_all_files
5011 {
5012 foreach $f (keys %main::locked_file_list) {
5013         &unlock_file($f);
5014         }
5015 }
5016
5017 =head2 can_lock_file(file)
5018
5019 Returns 1 if some file should be locked, based on the settings in the 
5020 Webmin Configuration module. For internal use by lock_file only.
5021
5022 =cut
5023 sub can_lock_file
5024 {
5025 if (&is_readonly_mode()) {
5026         return 0;       # never lock in read-only mode
5027         }
5028 elsif ($gconfig{'lockmode'} == 0) {
5029         return 1;       # always
5030         }
5031 elsif ($gconfig{'lockmode'} == 1) {
5032         return 0;       # never
5033         }
5034 else {
5035         # Check if under any of the directories
5036         my $match;
5037         foreach my $d (split(/\t+/, $gconfig{'lockdirs'})) {
5038                 if (&same_file($d, $_[0]) ||
5039                     &is_under_directory($d, $_[0])) {
5040                         $match = 1;
5041                         }
5042                 }
5043         return $gconfig{'lockmode'} == 2 ? $match : !$match;
5044         }
5045 }
5046
5047 =head2 webmin_log(action, type, object, &params, [module], [host, script-on-host, client-ip])
5048
5049 Log some action taken by a user. This is typically called at the end of a
5050 script, once all file changes are complete and all commands run. The 
5051 parameters are :
5052
5053 =item action - A short code for the action being performed, like 'create'.
5054
5055 =item type - A code for the type of object the action is performed to, like 'user'.
5056
5057 =item object - A short name for the object, like 'joe' if the Unix user 'joe' was just created.
5058
5059 =item params - A hash ref of additional information about the action.
5060
5061 =item module - Name of the module in which the action was performed, which defaults to the current module.
5062
5063 =item host - Remote host on which the action was performed. You should never need to set this (or the following two parameters), as they are used only for remote Webmin logging.
5064
5065 =item script-on-host - Script name like create_user.cgi on the host the action was performed on.
5066
5067 =item client-ip - IP address of the browser that performed the action.
5068
5069 =cut
5070 sub webmin_log
5071 {
5072 return if (!$gconfig{'log'} || &is_readonly_mode());
5073 my $m = $_[4] ? $_[4] : &get_module_name();
5074
5075 if ($gconfig{'logclear'}) {
5076         # check if it is time to clear the log
5077         my @st = stat("$webmin_logfile.time");
5078         my $write_logtime = 0;
5079         if (@st) {
5080                 if ($st[9]+$gconfig{'logtime'}*60*60 < time()) {
5081                         # clear logfile and all diff files
5082                         &unlink_file("$ENV{'WEBMIN_VAR'}/diffs");
5083                         &unlink_file("$ENV{'WEBMIN_VAR'}/files");
5084                         &unlink_file("$ENV{'WEBMIN_VAR'}/annotations");
5085                         unlink($webmin_logfile);
5086                         $write_logtime = 1;
5087                         }
5088                 }
5089         else {
5090                 $write_logtime = 1;
5091                 }
5092         if ($write_logtime) {
5093                 open(LOGTIME, ">$webmin_logfile.time");
5094                 print LOGTIME time(),"\n";
5095                 close(LOGTIME);
5096                 }
5097         }
5098
5099 # If an action script directory is defined, call the appropriate scripts
5100 if ($gconfig{'action_script_dir'}) {
5101     my ($action, $type, $object) = ($_[0], $_[1], $_[2]);
5102     my ($basedir) = $gconfig{'action_script_dir'};
5103
5104     for my $dir ($basedir/$type/$action, $basedir/$type, $basedir) {
5105         if (-d $dir) {
5106             my ($file);
5107             opendir(DIR, $dir) or die "Can't open $dir: $!";
5108             while (defined($file = readdir(DIR))) {
5109                 next if ($file =~ /^\.\.?$/); # skip '.' and '..'
5110                 if (-x "$dir/$file") {
5111                     # Call a script notifying it of the action
5112                     my %OLDENV = %ENV;
5113                     $ENV{'ACTION_MODULE'} = &get_module_name();
5114                     $ENV{'ACTION_ACTION'} = $_[0];
5115                     $ENV{'ACTION_TYPE'} = $_[1];
5116                     $ENV{'ACTION_OBJECT'} = $_[2];
5117                     $ENV{'ACTION_SCRIPT'} = $script_name;
5118                     foreach my $p (keys %param) {
5119                             $ENV{'ACTION_PARAM_'.uc($p)} = $param{$p};
5120                             }
5121                     system("$dir/$file", @_,
5122                            "<$null_file", ">$null_file", "2>&1");
5123                     %ENV = %OLDENV;
5124                     }
5125                 }
5126             }
5127         }
5128     }
5129
5130 # should logging be done at all?
5131 return if ($gconfig{'logusers'} && &indexof($base_remote_user,
5132            split(/\s+/, $gconfig{'logusers'})) < 0);
5133 return if ($gconfig{'logmodules'} && &indexof($m,
5134            split(/\s+/, $gconfig{'logmodules'})) < 0);
5135
5136 # log the action
5137 my $now = time();
5138 my @tm = localtime($now);
5139 my $script_name = $0 =~ /([^\/]+)$/ ? $1 : '-';
5140 my $id = sprintf "%d.%d.%d", $now, $$, $main::action_id_count;
5141 $main::action_id_count++;
5142 my $line = sprintf "%s [%2.2d/%s/%4.4d %2.2d:%2.2d:%2.2d] %s %s %s %s %s \"%s\" \"%s\" \"%s\"",
5143         $id, $tm[3], $text{"smonth_".($tm[4]+1)}, $tm[5]+1900,
5144         $tm[2], $tm[1], $tm[0],
5145         $remote_user || '-',
5146         $main::session_id || '-',
5147         $_[7] || $ENV{'REMOTE_HOST'} || '-',
5148         $m, $_[5] ? "$_[5]:$_[6]" : $script_name,
5149         $_[0], $_[1] ne '' ? $_[1] : '-', $_[2] ne '' ? $_[2] : '-';
5150 my %param;
5151 foreach my $k (sort { $a cmp $b } keys %{$_[3]}) {
5152         my $v = $_[3]->{$k};
5153         my @pv;
5154         if ($v eq '') {
5155                 $line .= " $k=''";
5156                 @rv = ( "" );
5157                 }
5158         elsif (ref($v) eq 'ARRAY') {
5159                 foreach $vv (@$v) {
5160                         next if (ref($vv));
5161                         push(@pv, $vv);
5162                         $vv =~ s/(['"\\\r\n\t\%])/sprintf("%%%2.2X",ord($1))/ge;
5163                         $line .= " $k='$vv'";
5164                         }
5165                 }
5166         elsif (!ref($v)) {
5167                 foreach $vv (split(/\0/, $v)) {
5168                         push(@pv, $vv);
5169                         $vv =~ s/(['"\\\r\n\t\%])/sprintf("%%%2.2X",ord($1))/ge;
5170                         $line .= " $k='$vv'";
5171                         }
5172                 }
5173         $param{$k} = join(" ", @pv);
5174         }
5175 open(WEBMINLOG, ">>$webmin_logfile");
5176 print WEBMINLOG $line,"\n";
5177 close(WEBMINLOG);
5178 if ($gconfig{'logperms'}) {
5179         chmod(oct($gconfig{'logperms'}), $webmin_logfile);
5180         }
5181 else {
5182         chmod(0600, $webmin_logfile);
5183         }
5184
5185 if ($gconfig{'logfiles'} && !&get_module_variable('$no_log_file_changes')) {
5186         # Find and record the changes made to any locked files, or commands run
5187         my $i = 0;
5188         mkdir("$ENV{'WEBMIN_VAR'}/diffs", 0700);
5189         foreach my $d (@main::locked_file_diff) {
5190                 mkdir("$ENV{'WEBMIN_VAR'}/diffs/$id", 0700);
5191                 open(DIFFLOG, ">$ENV{'WEBMIN_VAR'}/diffs/$id/$i");
5192                 print DIFFLOG "$d->{'type'} $d->{'object'}\n";
5193                 print DIFFLOG $d->{'data'};
5194                 close(DIFFLOG);
5195                 if ($d->{'input'}) {
5196                         open(DIFFLOG, ">$ENV{'WEBMIN_VAR'}/diffs/$id/$i.input");
5197                         print DIFFLOG $d->{'input'};
5198                         close(DIFFLOG);
5199                         }
5200                 if ($gconfig{'logperms'}) {
5201                         chmod(oct($gconfig{'logperms'}),
5202                               "$ENV{'WEBMIN_VAR'}/diffs/$id/$i",
5203                               "$ENV{'WEBMIN_VAR'}/diffs/$id/$i.input");
5204                         }
5205                 $i++;
5206                 }
5207         @main::locked_file_diff = undef;
5208         }
5209
5210 if ($gconfig{'logfullfiles'}) {
5211         # Save the original contents of any modified files
5212         my $i = 0;
5213         mkdir("$ENV{'WEBMIN_VAR'}/files", 0700);
5214         foreach my $f (keys %main::orig_file_data) {
5215                 mkdir("$ENV{'WEBMIN_VAR'}/files/$id", 0700);
5216                 open(ORIGLOG, ">$ENV{'WEBMIN_VAR'}/files/$id/$i");
5217                 if (!defined($main::orig_file_type{$f})) {
5218                         print ORIGLOG -1," ",$f,"\n";
5219                         }
5220                 else {
5221                         print ORIGLOG $main::orig_file_type{$f}," ",$f,"\n";
5222                         }
5223                 print ORIGLOG $main::orig_file_data{$f};
5224                 close(ORIGLOG);
5225                 if ($gconfig{'logperms'}) {
5226                         chmod(oct($gconfig{'logperms'}),
5227                               "$ENV{'WEBMIN_VAR'}/files/$id.$i");
5228                         }
5229                 $i++;
5230                 }
5231         %main::orig_file_data = undef;
5232         %main::orig_file_type = undef;
5233         }
5234
5235 if ($miniserv::page_capture_out) {
5236         # Save the whole page output
5237         mkdir("$ENV{'WEBMIN_VAR'}/output", 0700);
5238         open(PAGEOUT, ">$ENV{'WEBMIN_VAR'}/output/$id");
5239         print PAGEOUT $miniserv::page_capture_out;
5240         close(PAGEOUT);
5241         if ($gconfig{'logperms'}) {
5242                 chmod(oct($gconfig{'logperms'}),
5243                       "$ENV{'WEBMIN_VAR'}/output/$id");
5244                 }
5245         $miniserv::page_capture_out = undef;
5246         }
5247
5248 # Log to syslog too
5249 if ($gconfig{'logsyslog'}) {
5250         eval 'use Sys::Syslog qw(:DEFAULT setlogsock);
5251               openlog(&get_product_name(), "cons,pid,ndelay", "daemon");
5252               setlogsock("inet");';
5253         if (!$@) {
5254                 # Syslog module is installed .. try to convert to a
5255                 # human-readable form
5256                 my $msg;
5257                 my $mod = &get_module_name();
5258                 my $mdir = module_root_directory($mod);
5259                 if (-r "$mdir/log_parser.pl") {
5260                         &foreign_require($mod, "log_parser.pl");
5261                         my %params;
5262                         foreach my $k (keys %{$_[3]}) {
5263                                 my $v = $_[3]->{$k};
5264                                 if (ref($v) eq 'ARRAY') {
5265                                         $params{$k} = join("\0", @$v);
5266                                         }
5267                                 else {
5268                                         $params{$k} = $v;
5269                                         }
5270                                 }
5271                         $msg = &foreign_call($mod, "parse_webmin_log",
5272                                 $remote_user, $script_name,
5273                                 $_[0], $_[1], $_[2], \%params);
5274                         $msg =~ s/<[^>]*>//g;   # Remove tags
5275                         }
5276                 elsif ($_[0] eq "_config_") {
5277                         my %wtext = &load_language("webminlog");
5278                         $msg = $wtext{'search_config'};
5279                         }
5280                 $msg ||= "$_[0] $_[1] $_[2]";
5281                 my %info = &get_module_info($m);
5282                 eval { syslog("info", "%s", "[$info{'desc'}] $msg"); };
5283                 }
5284         }
5285 }
5286
5287 =head2 additional_log(type, object, data, [input])
5288
5289 Records additional log data for an upcoming call to webmin_log, such
5290 as a command that was run or SQL that was executed. Typically you will never
5291 need to call this function directory.
5292
5293 =cut
5294 sub additional_log
5295 {
5296 if ($gconfig{'logfiles'} && !&get_module_variable('$no_log_file_changes')) {
5297         push(@main::locked_file_diff,
5298              { 'type' => $_[0], 'object' => $_[1], 'data' => $_[2],
5299                'input' => $_[3] } );
5300         }
5301 }
5302
5303 =head2 webmin_debug_log(type, message)
5304
5305 Write something to the Webmin debug log. For internal use only.
5306
5307 =cut
5308 sub webmin_debug_log
5309 {
5310 my ($type, $msg) = @_;
5311 return 0 if (!$main::opened_debug_log);
5312 return 0 if ($gconfig{'debug_no'.$main::webmin_script_type});
5313 if ($gconfig{'debug_modules'}) {
5314         my @dmods = split(/\s+/, $gconfig{'debug_modules'});
5315         return 0 if (&indexof($main::initial_module_name, @dmods) < 0);
5316         }
5317 my $now = time();
5318 my @tm = localtime($now);
5319 my $line = sprintf
5320         "%s [%2.2d/%s/%4.4d %2.2d:%2.2d:%2.2d] %s %s %s %s \"%s\"",
5321         $$, $tm[3], $text{"smonth_".($tm[4]+1)}, $tm[5]+1900,
5322         $tm[2], $tm[1], $tm[0],
5323         $remote_user || "-",
5324         $ENV{'REMOTE_HOST'} || "-",
5325         &get_module_name() || "-",
5326         $type,
5327         $msg;
5328 seek(main::DEBUGLOG, 0, 2);
5329 print main::DEBUGLOG $line."\n";
5330 return 1;
5331 }
5332
5333 =head2 system_logged(command)
5334
5335 Just calls the Perl system() function, but also logs the command run.
5336
5337 =cut
5338 sub system_logged
5339 {
5340 if (&is_readonly_mode()) {
5341         print STDERR "Vetoing command $_[0]\n";
5342         return 0;
5343         }
5344 my @realcmd = ( &translate_command($_[0]), @_[1..$#_] );
5345 my $cmd = join(" ", @realcmd);
5346 my $and;
5347 if ($cmd =~ s/(\s*&\s*)$//) {
5348         $and = $1;
5349         }
5350 while($cmd =~ s/(\d*)(<|>)((\/(tmp|dev)\S+)|&\d+)\s*$//) { }
5351 $cmd =~ s/^\((.*)\)\s*$/$1/;
5352 $cmd .= $and;
5353 &additional_log('exec', undef, $cmd);
5354 return system(@realcmd);
5355 }
5356
5357 =head2 backquote_logged(command)
5358
5359 Executes a command and returns the output (like `command`), but also logs it.
5360
5361 =cut
5362 sub backquote_logged
5363 {
5364 if (&is_readonly_mode()) {
5365         $? = 0;
5366         print STDERR "Vetoing command $_[0]\n";
5367         return undef;
5368         }
5369 my $realcmd = &translate_command($_[0]);
5370 my $cmd = $realcmd;
5371 my $and;
5372 if ($cmd =~ s/(\s*&\s*)$//) {
5373         $and = $1;
5374         }
5375 while($cmd =~ s/(\d*)(<|>)((\/(tmp\/.webmin|dev)\S+)|&\d+)\s*$//) { }
5376 $cmd =~ s/^\((.*)\)\s*$/$1/;
5377 $cmd .= $and;
5378 &additional_log('exec', undef, $cmd);
5379 &webmin_debug_log('CMD', "cmd=$cmd") if ($gconfig{'debug_what_cmd'});
5380 return `$realcmd`;
5381 }
5382
5383 =head2 backquote_with_timeout(command, timeout, safe?, [maxlines])
5384
5385 Runs some command, waiting at most the given number of seconds for it to
5386 complete, and returns the output. The maxlines parameter sets the number
5387 of lines of output to capture. The safe parameter should be set to 1 if the
5388 command is safe for read-only mode users to run.
5389
5390 =cut
5391 sub backquote_with_timeout
5392 {
5393 my $realcmd = &translate_command($_[0]);
5394 &webmin_debug_log('CMD', "cmd=$realcmd timeout=$_[1]")
5395         if ($gconfig{'debug_what_cmd'});
5396 my $out;
5397 my $pid = &open_execute_command(OUT, "($realcmd) <$null_file", 1, $_[2]);
5398 my $start = time();
5399 my $timed_out = 0;
5400 my $linecount = 0;
5401 while(1) {
5402         my $elapsed = time() - $start;
5403         last if ($elapsed > $_[1]);
5404         my $rmask;
5405         vec($rmask, fileno(OUT), 1) = 1;
5406         my $sel = select($rmask, undef, undef, $_[1] - $elapsed);
5407         last if (!$sel || $sel < 0);
5408         my $line = <OUT>;
5409         last if (!defined($line));
5410         $out .= $line;
5411         $linecount++;
5412         if ($_[3] && $linecount >= $_[3]) {
5413                 # Got enough lines
5414                 last;
5415                 }
5416         }
5417 if (kill('TERM', $pid) && time() - $start >= $_[1]) {
5418         $timed_out = 1;
5419         }
5420 close(OUT);
5421 return wantarray ? ($out, $timed_out) : $out;
5422 }
5423
5424 =head2 backquote_command(command, safe?)
5425
5426 Executes a command and returns the output (like `command`), subject to
5427 command translation. The safe parameter should be set to 1 if the command
5428 is safe for read-only mode users to run.
5429
5430 =cut
5431 sub backquote_command
5432 {
5433 if (&is_readonly_mode() && !$_[1]) {
5434         print STDERR "Vetoing command $_[0]\n";
5435         $? = 0;
5436         return undef;
5437         }
5438 my $realcmd = &translate_command($_[0]);
5439 &webmin_debug_log('CMD', "cmd=$realcmd") if ($gconfig{'debug_what_cmd'});
5440 return `$realcmd`;
5441 }
5442
5443 =head2 kill_logged(signal, pid, ...)
5444
5445 Like Perl's built-in kill function, but also logs the fact that some process
5446 was killed. On Windows, falls back to calling process.exe to terminate a
5447 process.
5448
5449 =cut
5450 sub kill_logged
5451 {
5452 return scalar(@_)-1 if (&is_readonly_mode());
5453 &webmin_debug_log('KILL', "signal=$_[0] pids=".join(" ", @_[1..@_-1]))
5454         if ($gconfig{'debug_what_procs'});
5455 &additional_log('kill', $_[0], join(" ", @_[1..@_-1])) if (@_ > 1);
5456 if ($gconfig{'os_type'} eq 'windows') {
5457         # Emulate some kills with process.exe
5458         my $arg = $_[0] eq "KILL" ? "-k" :
5459                   $_[0] eq "TERM" ? "-q" :
5460                   $_[0] eq "STOP" ? "-s" :
5461                   $_[0] eq "CONT" ? "-r" : undef;
5462         my $ok = 0;
5463         foreach my $p (@_[1..@_-1]) {
5464                 if ($p < 0) {
5465                         $ok ||= kill($_[0], $p);
5466                         }
5467                 elsif ($arg) {
5468                         &execute_command("process $arg $p");
5469                         $ok = 1;
5470                         }
5471                 }
5472         return $ok;
5473         }
5474 else {
5475         # Normal Unix kill
5476         return kill(@_);
5477         }
5478 }
5479
5480 =head2 rename_logged(old, new)
5481
5482 Re-names a file and logs the rename. If the old and new files are on different
5483 filesystems, calls mv or the Windows rename function to do the job.
5484
5485 =cut
5486 sub rename_logged
5487 {
5488 &additional_log('rename', $_[0], $_[1]) if ($_[0] ne $_[1]);
5489 return &rename_file($_[0], $_[1]);
5490 }
5491
5492 =head2 rename_file(old, new)
5493
5494 Renames a file or directory. If the old and new files are on different
5495 filesystems, calls mv or the Windows rename function to do the job.
5496
5497 =cut
5498 sub rename_file
5499 {
5500 if (&is_readonly_mode()) {
5501         print STDERR "Vetoing rename from $_[0] to $_[1]\n";
5502         return 1;
5503         }
5504 my $src = &translate_filename($_[0]);
5505 my $dst = &translate_filename($_[1]);
5506 &webmin_debug_log('RENAME', "src=$src dst=$dst")
5507         if ($gconfig{'debug_what_ops'});
5508 my $ok = rename($src, $dst);
5509 if (!$ok && $! !~ /permission/i) {
5510         # Try the mv command, in case this is a cross-filesystem rename
5511         if ($gconfig{'os_type'} eq 'windows') {
5512                 # Need to use rename
5513                 my $out = &backquote_command("rename ".quotemeta($_[0]).
5514                                              " ".quotemeta($_[1])." 2>&1");
5515                 $ok = !$?;
5516                 $! = $out if (!$ok);
5517                 }
5518         else {
5519                 # Can use mv
5520                 my $out = &backquote_command("mv ".quotemeta($_[0]).
5521                                              " ".quotemeta($_[1])." 2>&1");
5522                 $ok = !$?;
5523                 $! = $out if (!$ok);
5524                 }
5525         }
5526 return $ok;
5527 }
5528
5529 =head2 symlink_logged(src, dest)
5530
5531 Create a symlink, and logs it. Effectively does the same thing as the Perl
5532 symlink function.
5533
5534 =cut
5535 sub symlink_logged
5536 {
5537 &lock_file($_[1]);
5538 my $rv = &symlink_file($_[0], $_[1]);
5539 &unlock_file($_[1]);
5540 return $rv;
5541 }
5542
5543 =head2 symlink_file(src, dest)
5544
5545 Creates a soft link, unless in read-only mode. Effectively does the same thing
5546 as the Perl symlink function.
5547
5548 =cut
5549 sub symlink_file
5550 {
5551 if (&is_readonly_mode()) {
5552         print STDERR "Vetoing symlink from $_[0] to $_[1]\n";
5553         return 1;
5554         }
5555 my $src = &translate_filename($_[0]);
5556 my $dst = &translate_filename($_[1]);
5557 &webmin_debug_log('SYMLINK', "src=$src dst=$dst")
5558         if ($gconfig{'debug_what_ops'});
5559 return symlink($src, $dst);
5560 }
5561
5562 =head2 link_file(src, dest)
5563
5564 Creates a hard link, unless in read-only mode. The existing new link file
5565 will be deleted if necessary. Effectively the same as Perl's link function.
5566
5567 =cut
5568 sub link_file
5569 {
5570 if (&is_readonly_mode()) {
5571         print STDERR "Vetoing link from $_[0] to $_[1]\n";
5572         return 1;
5573         }
5574 my $src = &translate_filename($_[0]);
5575 my $dst = &translate_filename($_[1]);
5576 &webmin_debug_log('LINK', "src=$src dst=$dst")
5577         if ($gconfig{'debug_what_ops'});
5578 unlink($dst);                   # make sure link works
5579 return link($src, $dst);
5580 }
5581
5582 =head2 make_dir(dir, perms, recursive)
5583
5584 Creates a directory and sets permissions on it, unless in read-only mode.
5585 The perms parameter sets the octal permissions to apply, which unlike Perl's
5586 mkdir will really get set. The recursive flag can be set to 1 to have the
5587 function create parent directories too.
5588
5589 =cut
5590 sub make_dir
5591 {
5592 my ($dir, $perms, $recur) = @_;
5593 if (&is_readonly_mode()) {
5594         print STDERR "Vetoing directory $dir\n";
5595         return 1;
5596         }
5597 $dir = &translate_filename($dir);
5598 my $exists = -d $dir ? 1 : 0;
5599 return 1 if ($exists && $recur);        # already exists
5600 &webmin_debug_log('MKDIR', $dir) if ($gconfig{'debug_what_ops'});
5601 my $rv = mkdir($dir, $perms);
5602 if (!$rv && $recur) {
5603         # Failed .. try mkdir -p
5604         my $param = $gconfig{'os_type'} eq 'windows' ? "" : "-p";
5605         my $ex = &execute_command("mkdir $param ".&quote_path($dir));
5606         if ($ex) {
5607                 return 0;
5608                 }
5609         }
5610 if (!$exists) {
5611         chmod($perms, $dir);
5612         }
5613 return 1;
5614 }
5615
5616 =head2 set_ownership_permissions(user, group, perms, file, ...)
5617
5618 Sets the user, group owner and permissions on some files. The parameters are :
5619
5620 =item user - UID or username to change the file owner to. If undef, then the owner is not changed.
5621
5622 =item group - GID or group name to change the file group to. If undef, then the group is set to the user's primary group.
5623
5624 =item perms - Octal permissions set to set on the file. If undef, they are left alone.
5625
5626 =item file - One or more files or directories to modify.
5627
5628 =cut
5629 sub set_ownership_permissions
5630 {
5631 my ($user, $group, $perms, @files) = @_;
5632 if (&is_readonly_mode()) {
5633         print STDERR "Vetoing permission changes on ",join(" ", @files),"\n";
5634         return 1;
5635         }
5636 @files = map { &translate_filename($_) } @files;
5637 if ($gconfig{'debug_what_ops'}) {
5638         foreach my $f (@files) {
5639                 &webmin_debug_log('PERMS',
5640                         "file=$f user=$user group=$group perms=$perms");
5641                 }
5642         }
5643 my $rv = 1;
5644 if (defined($user)) {
5645         my $uid = $user !~ /^\d+$/ ? getpwnam($user) : $user;
5646         my $gid;
5647         if (defined($group)) {
5648                 $gid = $group !~ /^\d+$/ ? getgrnam($group) : $group;
5649                 }
5650         else {
5651                 my @uinfo = getpwuid($uid);
5652                 $gid = $uinfo[3];
5653                 }
5654         $rv = chown($uid, $gid, @files);
5655         }
5656 if ($rv && defined($perms)) {
5657         $rv = chmod($perms, @files);
5658         }
5659 return $rv;
5660 }
5661
5662 =head2 unlink_logged(file, ...)
5663
5664 Like Perl's unlink function, but locks the files beforehand and un-locks them
5665 after so that the deletion is logged by Webmin.
5666
5667 =cut
5668 sub unlink_logged
5669 {
5670 my %locked;
5671 foreach my $f (@_) {
5672         if (!&test_lock($f)) {
5673                 &lock_file($f);
5674                 $locked{$f} = 1;
5675                 }
5676         }
5677 my @rv = &unlink_file(@_);
5678 foreach my $f (@_) {
5679         if ($locked{$f}) {
5680                 &unlock_file($f);
5681                 }
5682         }
5683 return wantarray ? @rv : $rv[0];
5684 }
5685
5686 =head2 unlink_file(file, ...)
5687
5688 Deletes some files or directories. Like Perl's unlink function, but also
5689 recursively deletes directories with the rm command if needed.
5690
5691 =cut
5692 sub unlink_file
5693 {
5694 return 1 if (&is_readonly_mode());
5695 my $rv = 1;
5696 my $err;
5697 foreach my $f (@_) {
5698         &unflush_file_lines($f);
5699         my $realf = &translate_filename($f);
5700         &webmin_debug_log('UNLINK', $realf) if ($gconfig{'debug_what_ops'});
5701         if (-d $realf) {
5702                 if (!rmdir($realf)) {
5703                         my $out;
5704                         if ($gconfig{'os_type'} eq 'windows') {
5705                                 # Call del and rmdir commands
5706                                 my $qm = $realf;
5707                                 $qm =~ s/\//\\/g;
5708                                 my $out = `del /q "$qm" 2>&1`;
5709                                 if (!$?) {
5710                                         $out = `rmdir "$qm" 2>&1`;
5711                                         }
5712                                 }
5713                         else {
5714                                 # Use rm command
5715                                 my $qm = quotemeta($realf);
5716                                 $out = `rm -rf $qm 2>&1`;
5717                                 }
5718                         if ($?) {
5719                                 $rv = 0;
5720                                 $err = $out;
5721                                 }
5722                         }
5723                 }
5724         else {
5725                 if (!unlink($realf)) {
5726                         $rv = 0;
5727                         $err = $!;
5728                         }
5729                 }
5730         }
5731 return wantarray ? ($rv, $err) : $rv;
5732 }
5733
5734 =head2 copy_source_dest(source, dest)
5735
5736 Copy some file or directory to a new location. Returns 1 on success, or 0
5737 on failure - also sets $! on failure. If the source is a directory, uses
5738 piped tar commands to copy a whole directory structure including permissions
5739 and special files.
5740
5741 =cut
5742 sub copy_source_dest
5743 {
5744 return (1, undef) if (&is_readonly_mode());
5745 my ($src, $dst) = @_;
5746 my $ok = 1;
5747 my ($err, $out);
5748 &webmin_debug_log('COPY', "src=$src dst=$dst")
5749         if ($gconfig{'debug_what_ops'});
5750 if ($gconfig{'os_type'} eq 'windows') {
5751         # No tar or cp on windows, so need to use copy command
5752         $src =~ s/\//\\/g;
5753         $dst =~ s/\//\\/g;
5754         if (-d $src) {
5755                 $out = &backquote_logged("xcopy \"$src\" \"$dst\" /Y /E /I 2>&1");
5756                 }
5757         else {
5758                 $out = &backquote_logged("copy /Y \"$src\" \"$dst\" 2>&1");
5759                 }
5760         if ($?) {
5761                 $ok = 0;
5762                 $err = $out;
5763                 }
5764         }
5765 elsif (-d $src) {
5766         # A directory .. need to copy with tar command
5767         my @st = stat($src);
5768         unlink($dst);
5769         mkdir($dst, 0755);
5770         &set_ownership_permissions($st[4], $st[5], $st[2], $dst);
5771         $out = &backquote_logged("(cd ".quotemeta($src)." ; tar cf - . | (cd ".quotemeta($dst)." ; tar xf -)) 2>&1");
5772         if ($?) {
5773                 $ok = 0;
5774                 $err = $out;
5775                 }
5776         }
5777 else {
5778         # Can just copy with cp
5779         my $out = &backquote_logged("cp -p ".quotemeta($src).
5780                                     " ".quotemeta($dst)." 2>&1");
5781         if ($?) {
5782                 $ok = 0;
5783                 $err = $out;
5784                 }
5785         }
5786 return wantarray ? ($ok, $err) : $ok;
5787 }
5788
5789 =head2 remote_session_name(host|&server)
5790
5791 Generates a session ID for some server. For this server, this will always
5792 be an empty string. For a server object it will include the hostname and
5793 port and PID. For a server name, it will include the hostname and PID. For
5794 internal use only.
5795
5796 =cut
5797 sub remote_session_name
5798 {
5799 return ref($_[0]) && $_[0]->{'host'} && $_[0]->{'port'} ?
5800                 "$_[0]->{'host'}:$_[0]->{'port'}.$$" :
5801        $_[0] eq "" || ref($_[0]) && $_[0]->{'id'} == 0 ? "" :
5802        ref($_[0]) ? "" : "$_[0].$$";
5803 }
5804
5805 =head2 remote_foreign_require(server, module, file)
5806
5807 Connects to rpc.cgi on a remote webmin server and have it open a session
5808 to a process that will actually do the require and run functions. This is the
5809 equivalent for foreign_require, but for a remote Webmin system. The server
5810 parameter can either be a hostname of a system registered in the Webmin
5811 Servers Index module, or a hash reference for a system from that module.
5812
5813 =cut
5814 sub remote_foreign_require
5815 {
5816 my $call = { 'action' => 'require',
5817              'module' => $_[1],
5818              'file' => $_[2] };
5819 my $sn = &remote_session_name($_[0]);
5820 if ($remote_session{$sn}) {
5821         $call->{'session'} = $remote_session{$sn};
5822         }
5823 else {
5824         $call->{'newsession'} = 1;
5825         }
5826 my $rv = &remote_rpc_call($_[0], $call);
5827 if ($rv->{'session'}) {
5828         $remote_session{$sn} = $rv->{'session'};
5829         $remote_session_server{$sn} = $_[0];
5830         }
5831 }
5832
5833 =head2 remote_foreign_call(server, module, function, [arg]*)
5834
5835 Call a function on a remote server. Must have been setup first with
5836 remote_foreign_require for the same server and module. Equivalent to
5837 foreign_call, but with the extra server parameter to specify the remote
5838 system's hostname.
5839
5840 =cut
5841 sub remote_foreign_call
5842 {
5843 return undef if (&is_readonly_mode());
5844 my $sn = &remote_session_name($_[0]);
5845 return &remote_rpc_call($_[0], { 'action' => 'call',
5846                                  'module' => $_[1],
5847                                  'func' => $_[2],
5848                                  'session' => $remote_session{$sn},
5849                                  'args' => [ @_[3 .. $#_] ] } );
5850 }
5851
5852 =head2 remote_foreign_check(server, module, [api-only])
5853
5854 Checks if some module is installed and supported on a remote server. Equivilant
5855 to foreign_check, but for the remote Webmin system specified by the server
5856 parameter.
5857
5858 =cut
5859 sub remote_foreign_check
5860 {
5861 return &remote_rpc_call($_[0], { 'action' => 'check',
5862                                  'module' => $_[1],
5863                                  'api' => $_[2] });
5864 }
5865
5866 =head2 remote_foreign_config(server, module)
5867
5868 Gets the configuration for some module from a remote server, as a hash.
5869 Equivalent to foreign_config, but for a remote system.
5870
5871 =cut
5872 sub remote_foreign_config
5873 {
5874 return &remote_rpc_call($_[0], { 'action' => 'config',
5875                                  'module' => $_[1] });
5876 }
5877
5878 =head2 remote_eval(server, module, code)
5879
5880 Evaluates some perl code in the context of a module on a remote webmin server.
5881 The server parameter must be the hostname of a remote system, module must
5882 be a module directory name, and code a string of Perl code to run. This can
5883 only be called after remote_foreign_require for the same server and module.
5884
5885 =cut
5886 sub remote_eval
5887 {
5888 return undef if (&is_readonly_mode());
5889 my $sn = &remote_session_name($_[0]);
5890 return &remote_rpc_call($_[0], { 'action' => 'eval',
5891                                  'module' => $_[1],
5892                                  'code' => $_[2],
5893                                  'session' => $remote_session{$sn} });
5894 }
5895
5896 =head2 remote_write(server, localfile, [remotefile], [remotebasename])
5897
5898 Transfers some local file to another server via Webmin's RPC protocol, and
5899 returns the resulting remote filename. If the remotefile parameter is given,
5900 that is the destination filename which will be used. Otherwise a randomly
5901 selected temporary filename will be used, and returned by the function.
5902
5903 =cut
5904 sub remote_write
5905 {
5906 return undef if (&is_readonly_mode());
5907 my ($data, $got);
5908 my $sn = &remote_session_name($_[0]);
5909 if (!$_[0] || $remote_server_version{$sn} >= 0.966) {
5910         # Copy data over TCP connection
5911         my $rv = &remote_rpc_call($_[0], { 'action' => 'tcpwrite',
5912                                            'file' => $_[2],
5913                                            'name' => $_[3] } );
5914         my $error;
5915         my $serv = ref($_[0]) ? $_[0]->{'host'} : $_[0];
5916         &open_socket($serv || "localhost", $rv->[1], TWRITE, \$error);
5917         return &$main::remote_error_handler("Failed to transfer file : $error")
5918                 if ($error);
5919         open(FILE, $_[1]);
5920         while(read(FILE, $got, 1024) > 0) {
5921                 print TWRITE $got;
5922                 }
5923         close(FILE);
5924         shutdown(TWRITE, 1);
5925         $error = <TWRITE>;
5926         if ($error && $error !~ /^OK/) {
5927                 # Got back an error!
5928                 return &$main::remote_error_handler("Failed to transfer file : $error");
5929                 }
5930         close(TWRITE);
5931         return $rv->[0];
5932         }
5933 else {
5934         # Just pass file contents as parameters
5935         open(FILE, $_[1]);
5936         while(read(FILE, $got, 1024) > 0) {
5937                 $data .= $got;
5938                 }
5939         close(FILE);
5940         return &remote_rpc_call($_[0], { 'action' => 'write',
5941                                          'data' => $data,
5942                                          'file' => $_[2],
5943                                          'session' => $remote_session{$sn} });
5944         }
5945 }
5946
5947 =head2 remote_read(server, localfile, remotefile)
5948
5949 Transfers a file from a remote server to this system, using Webmin's RPC
5950 protocol. The server parameter must be the hostname of a system registered
5951 in the Webmin Servers Index module, localfile is the destination path on this
5952 system, and remotefile is the file to fetch from the remote server.
5953
5954 =cut
5955 sub remote_read
5956 {
5957 my $sn = &remote_session_name($_[0]);
5958 if (!$_[0] || $remote_server_version{$sn} >= 0.966) {
5959         # Copy data over TCP connection
5960         my $rv = &remote_rpc_call($_[0], { 'action' => 'tcpread',
5961                                            'file' => $_[2] } );
5962         if (!$rv->[0]) {
5963                 return &$main::remote_error_handler("Failed to transfer file : $rv->[1]");
5964                 }
5965         my $error;
5966         my $serv = ref($_[0]) ? $_[0]->{'host'} : $_[0];
5967         &open_socket($serv || "localhost", $rv->[1], TREAD, \$error);
5968         return &$main::remote_error_handler("Failed to transfer file : $error")
5969                 if ($error);
5970         my $got;
5971         open(FILE, ">$_[1]");
5972         while(read(TREAD, $got, 1024) > 0) {
5973                 print FILE $got;
5974                 }
5975         close(FILE);
5976         close(TREAD);
5977         }
5978 else {
5979         # Just get data as return value
5980         my $d = &remote_rpc_call($_[0], { 'action' => 'read',
5981                                           'file' => $_[2],
5982                                           'session' => $remote_session{$sn} });
5983         open(FILE, ">$_[1]");
5984         print FILE $d;
5985         close(FILE);
5986         }
5987 }
5988
5989 =head2 remote_finished
5990
5991 Close all remote sessions. This happens automatically after a while
5992 anyway, but this function should be called to clean things up faster.
5993
5994 =cut
5995 sub remote_finished
5996 {
5997 foreach my $sn (keys %remote_session) {
5998         my $server = $remote_session_server{$sn};
5999         &remote_rpc_call($server, { 'action' => 'quit',
6000                                     'session' => $remote_session{$sn} } );
6001         delete($remote_session{$sn});
6002         delete($remote_session_server{$sn});
6003         }
6004 foreach my $fh (keys %fast_fh_cache) {
6005         close($fh);
6006         delete($fast_fh_cache{$fh});
6007         }
6008 }
6009
6010 =head2 remote_error_setup(&function)
6011
6012 Sets a function to be called instead of &error when a remote RPC operation
6013 fails. Useful if you want to have more control over your remote operations.
6014
6015 =cut
6016 sub remote_error_setup
6017 {
6018 $main::remote_error_handler = $_[0] || \&error;
6019 }
6020
6021 =head2 remote_rpc_call(server, &structure)
6022
6023 Calls rpc.cgi on some server and passes it a perl structure (hash,array,etc)
6024 and then reads back a reply structure. This is mainly for internal use only,
6025 and is called by the other remote_* functions.
6026
6027 =cut
6028 sub remote_rpc_call
6029 {
6030 my $serv;
6031 my $sn = &remote_session_name($_[0]);   # Will be undef for local connection
6032 if (ref($_[0])) {
6033         # Server structure was given
6034         $serv = $_[0];
6035         $serv->{'user'} || $serv->{'id'} == 0 ||
6036                 return &$main::remote_error_handler(
6037                         "No Webmin login set for server");
6038         }
6039 elsif ($_[0]) {
6040         # lookup the server in the webmin servers module if needed
6041         if (!%main::remote_servers_cache) {
6042                 &foreign_require("servers", "servers-lib.pl");
6043                 foreach $s (&foreign_call("servers", "list_servers")) {
6044                         $main::remote_servers_cache{$s->{'host'}} = $s;
6045                         $main::remote_servers_cache{$s->{'host'}.":".$s->{'port'}} = $s;
6046                         }
6047                 }
6048         $serv = $main::remote_servers_cache{$_[0]};
6049         $serv || return &$main::remote_error_handler(
6050                                 "No Webmin Servers entry for $_[0]");
6051         $serv->{'user'} || return &$main::remote_error_handler(
6052                                 "No login set for server $_[0]");
6053         }
6054 my $ip = $serv->{'ip'} || $serv->{'host'};
6055
6056 # Work out the username and password
6057 my ($user, $pass);
6058 if ($serv->{'sameuser'}) {
6059         $user = $remote_user;
6060         defined($main::remote_pass) || return &$main::remote_error_handler(
6061                                    "Password for this server is not available");
6062         $pass = $main::remote_pass;
6063         }
6064 else {
6065         $user = $serv->{'user'};
6066         $pass = $serv->{'pass'};
6067         }
6068
6069 if ($serv->{'fast'} || !$sn) {
6070         # Make TCP connection call to fastrpc.cgi
6071         if (!$fast_fh_cache{$sn} && $sn) {
6072                 # Need to open the connection
6073                 my $con = &make_http_connection(
6074                         $ip, $serv->{'port'}, $serv->{'ssl'},
6075                         "POST", "/fastrpc.cgi");
6076                 return &$main::remote_error_handler(
6077                     "Failed to connect to $serv->{'host'} : $con")
6078                         if (!ref($con));
6079                 &write_http_connection($con, "Host: $serv->{'host'}\r\n");
6080                 &write_http_connection($con, "User-agent: Webmin\r\n");
6081                 my $auth = &encode_base64("$user:$pass");
6082                 $auth =~ tr/\n//d;
6083                 &write_http_connection($con, "Authorization: basic $auth\r\n");
6084                 &write_http_connection($con, "Content-length: ",
6085                                              length($tostr),"\r\n");
6086                 &write_http_connection($con, "\r\n");
6087                 &write_http_connection($con, $tostr);
6088
6089                 # read back the response
6090                 my $line = &read_http_connection($con);
6091                 $line =~ tr/\r\n//d;
6092                 if ($line =~ /^HTTP\/1\..\s+401\s+/) {
6093                         return &$main::remote_error_handler("Login to RPC server as $user rejected");
6094                         }
6095                 $line =~ /^HTTP\/1\..\s+200\s+/ ||
6096                         return &$main::remote_error_handler("HTTP error : $line");
6097                 do {
6098                         $line = &read_http_connection($con);
6099                         $line =~ tr/\r\n//d;
6100                         } while($line);
6101                 $line = &read_http_connection($con);
6102                 if ($line =~ /^0\s+(.*)/) {
6103                         return &$main::remote_error_handler("RPC error : $1");
6104                         }
6105                 elsif ($line =~ /^1\s+(\S+)\s+(\S+)\s+(\S+)/ ||
6106                        $line =~ /^1\s+(\S+)\s+(\S+)/) {
6107                         # Started ok .. connect and save SID
6108                         &close_http_connection($con);
6109                         my ($port, $sid, $version, $error) = ($1, $2, $3);
6110                         &open_socket($ip, $port, $sid, \$error);
6111                         return &$main::remote_error_handler("Failed to connect to fastrpc.cgi : $error")
6112                                 if ($error);
6113                         $fast_fh_cache{$sn} = $sid;
6114                         $remote_server_version{$sn} = $version;
6115                         }
6116                 else {
6117                         while($stuff = &read_http_connection($con)) {
6118                                 $line .= $stuff;
6119                                 }
6120                         return &$main::remote_error_handler("Bad response from fastrpc.cgi : $line");
6121                         }
6122                 }
6123         elsif (!$fast_fh_cache{$sn}) {
6124                 # Open the connection by running fastrpc.cgi locally
6125                 pipe(RPCOUTr, RPCOUTw);
6126                 if (!fork()) {
6127                         untie(*STDIN);
6128                         untie(*STDOUT);
6129                         open(STDOUT, ">&RPCOUTw");
6130                         close(STDIN);
6131                         close(RPCOUTr);
6132                         $| = 1;
6133                         $ENV{'REQUEST_METHOD'} = 'GET';
6134                         $ENV{'SCRIPT_NAME'} = '/fastrpc.cgi';
6135                         $ENV{'SERVER_ROOT'} ||= $root_directory;
6136                         my %acl;
6137                         if ($base_remote_user ne 'root' &&
6138                             $base_remote_user ne 'admin') {
6139                                 # Need to fake up a login for the CGI!
6140                                 &read_acl(undef, \%acl, [ 'root' ]);
6141                                 $ENV{'BASE_REMOTE_USER'} =
6142                                         $ENV{'REMOTE_USER'} =
6143                                                 $acl{'root'} ? 'root' : 'admin';
6144                                 }
6145                         delete($ENV{'FOREIGN_MODULE_NAME'});
6146                         delete($ENV{'FOREIGN_ROOT_DIRECTORY'});
6147                         chdir($root_directory);
6148                         if (!exec("$root_directory/fastrpc.cgi")) {
6149                                 print "exec failed : $!\n";
6150                                 exit 1;
6151                                 }
6152                         }
6153                 close(RPCOUTw);
6154                 my $line;
6155                 do {
6156                         ($line = <RPCOUTr>) =~ tr/\r\n//d;
6157                         } while($line);
6158                 $line = <RPCOUTr>;
6159                 #close(RPCOUTr);
6160                 if ($line =~ /^0\s+(.*)/) {
6161                         return &$main::remote_error_handler("RPC error : $2");
6162                         }
6163                 elsif ($line =~ /^1\s+(\S+)\s+(\S+)/) {
6164                         # Started ok .. connect and save SID
6165                         close(SOCK);
6166                         my ($port, $sid, $error) = ($1, $2, undef);
6167                         &open_socket("localhost", $port, $sid, \$error);
6168                         return &$main::remote_error_handler("Failed to connect to fastrpc.cgi : $error") if ($error);
6169                         $fast_fh_cache{$sn} = $sid;
6170                         }
6171                 else {
6172                         local $_;
6173                         while(<RPCOUTr>) {
6174                                 $line .= $_;
6175                                 }
6176                         &error("Bad response from fastrpc.cgi : $line");
6177                         }
6178                 }
6179         # Got a connection .. send off the request
6180         my $fh = $fast_fh_cache{$sn};
6181         my $tostr = &serialise_variable($_[1]);
6182         print $fh length($tostr)," $fh\n";
6183         print $fh $tostr;
6184         my $rlen = int(<$fh>);
6185         my ($fromstr, $got);
6186         while(length($fromstr) < $rlen) {
6187                 return &$main::remote_error_handler("Failed to read from fastrpc.cgi")
6188                         if (read($fh, $got, $rlen - length($fromstr)) <= 0);
6189                 $fromstr .= $got;
6190                 }
6191         my $from = &unserialise_variable($fromstr);
6192         if (!$from) {
6193                 return &$main::remote_error_handler("Remote Webmin error");
6194                 }
6195         if (defined($from->{'arv'})) {
6196                 return @{$from->{'arv'}};
6197                 }
6198         else {
6199                 return $from->{'rv'};
6200                 }
6201         }
6202 else {
6203         # Call rpc.cgi on remote server
6204         my $tostr = &serialise_variable($_[1]);
6205         my $error = 0;
6206         my $con = &make_http_connection($ip, $serv->{'port'},
6207                                         $serv->{'ssl'}, "POST", "/rpc.cgi");
6208         return &$main::remote_error_handler("Failed to connect to $serv->{'host'} : $con") if (!ref($con));
6209
6210         &write_http_connection($con, "Host: $serv->{'host'}\r\n");
6211         &write_http_connection($con, "User-agent: Webmin\r\n");
6212         my $auth = &encode_base64("$user:$pass");
6213         $auth =~ tr/\n//d;
6214         &write_http_connection($con, "Authorization: basic $auth\r\n");
6215         &write_http_connection($con, "Content-length: ",length($tostr),"\r\n");
6216         &write_http_connection($con, "\r\n");
6217         &write_http_connection($con, $tostr);
6218
6219         # read back the response
6220         my $line = &read_http_connection($con);
6221         $line =~ tr/\r\n//d;
6222         if ($line =~ /^HTTP\/1\..\s+401\s+/) {
6223                 return &$main::remote_error_handler("Login to RPC server as $user rejected");
6224                 }
6225         $line =~ /^HTTP\/1\..\s+200\s+/ || return &$main::remote_error_handler("RPC HTTP error : $line");
6226         do {
6227                 $line = &read_http_connection($con);
6228                 $line =~ tr/\r\n//d;
6229                 } while($line);
6230         my $fromstr;
6231         while($line = &read_http_connection($con)) {
6232                 $fromstr .= $line;
6233                 }
6234         close(SOCK);
6235         my $from = &unserialise_variable($fromstr);
6236         return &$main::remote_error_handler("Invalid RPC login to $serv->{'host'}") if (!$from->{'status'});
6237         if (defined($from->{'arv'})) {
6238                 return @{$from->{'arv'}};
6239                 }
6240         else {
6241                 return $from->{'rv'};
6242                 }
6243         }
6244 }
6245
6246 =head2 remote_multi_callback(&servers, parallel, &function, arg|&args, &returns, &errors, [module, library])
6247
6248 Executes some function in parallel on multiple servers at once. Fills in
6249 the returns and errors arrays respectively. If the module and library
6250 parameters are given, that module is remotely required on the server first,
6251 to check if it is connectable. The parameters are :
6252
6253 =item servers - A list of Webmin system hash references.
6254
6255 =item parallel - Number of parallel operations to perform.
6256
6257 =item function - Reference to function to call for each system.
6258
6259 =item args - Additional parameters to the function.
6260
6261 =item returns - Array ref to place return values into, in same order as servers.
6262
6263 =item errors - Array ref to place error messages into.
6264
6265 =item module - Optional module to require on the remote system first.
6266
6267 =item library - Optional library to require in the module.
6268
6269 =cut
6270 sub remote_multi_callback
6271 {
6272 my ($servs, $parallel, $func, $args, $rets, $errs, $mod, $lib) = @_;
6273 &remote_error_setup(\&remote_multi_callback_error);
6274
6275 # Call the functions
6276 my $p = 0;
6277 foreach my $g (@$servs) {
6278         my $rh = "READ$p";
6279         my $wh = "WRITE$p";
6280         pipe($rh, $wh);
6281         if (!fork()) {
6282                 close($rh);
6283                 $remote_multi_callback_err = undef;
6284                 if ($mod) {
6285                         # Require the remote lib
6286                         &remote_foreign_require($g->{'host'}, $mod, $lib);
6287                         if ($remote_multi_callback_err) {
6288                                 # Failed .. return error
6289                                 print $wh &serialise_variable(
6290                                         [ undef, $remote_multi_callback_err ]);
6291                                 exit(0);
6292                                 }
6293                         }
6294
6295                 # Call the function
6296                 my $a = ref($args) ? $args->[$p] : $args;
6297                 my $rv = &$func($g, $a);
6298
6299                 # Return the result
6300                 print $wh &serialise_variable(
6301                         [ $rv, $remote_multi_callback_err ]);
6302                 close($wh);
6303                 exit(0);
6304                 }
6305         close($wh);
6306         $p++;
6307         }
6308
6309 # Read back the results
6310 $p = 0;
6311 foreach my $g (@$servs) {
6312         my $rh = "READ$p";
6313         my $line = <$rh>;
6314         if (!$line) {
6315                 $errs->[$p] = "Failed to read response from $g->{'host'}";
6316                 }
6317         else {
6318                 my $rv = &unserialise_variable($line);
6319                 close($rh);
6320                 $rets->[$p] = $rv->[0];
6321                 $errs->[$p] = $rv->[1];
6322                 }
6323         $p++;
6324         }
6325
6326 &remote_error_setup(undef);
6327 }
6328
6329 sub remote_multi_callback_error
6330 {
6331 $remote_multi_callback_err = $_[0];
6332 }
6333
6334 =head2 serialise_variable(variable)
6335
6336 Converts some variable (maybe a scalar, hash ref, array ref or scalar ref)
6337 into a url-encoded string. In the cases of arrays and hashes, it is recursively
6338 called on each member to serialize the entire object.
6339
6340 =cut
6341 sub serialise_variable
6342 {
6343 if (!defined($_[0])) {
6344         return 'UNDEF';
6345         }
6346 my $r = ref($_[0]);
6347 my $rv;
6348 if (!$r) {
6349         $rv = &urlize($_[0]);
6350         }
6351 elsif ($r eq 'SCALAR') {
6352         $rv = &urlize(${$_[0]});
6353         }
6354 elsif ($r eq 'ARRAY') {
6355         $rv = join(",", map { &urlize(&serialise_variable($_)) } @{$_[0]});
6356         }
6357 elsif ($r eq 'HASH') {
6358         $rv = join(",", map { &urlize(&serialise_variable($_)).",".
6359                               &urlize(&serialise_variable($_[0]->{$_})) }
6360                             keys %{$_[0]});
6361         }
6362 elsif ($r eq 'REF') {
6363         $rv = &serialise_variable(${$_[0]});
6364         }
6365 elsif ($r eq 'CODE') {
6366         # Code not handled
6367         $rv = undef;
6368         }
6369 elsif ($r) {
6370         # An object - treat as a hash
6371         $r = "OBJECT ".&urlize($r);
6372         $rv = join(",", map { &urlize(&serialise_variable($_)).",".
6373                               &urlize(&serialise_variable($_[0]->{$_})) }
6374                             keys %{$_[0]});
6375         }
6376 return ($r ? $r : 'VAL').",".$rv;
6377 }
6378
6379 =head2 unserialise_variable(string)
6380
6381 Converts a string created by serialise_variable() back into the original
6382 scalar, hash ref, array ref or scalar ref. If the original variable was a Perl
6383 object, the same class is used on this system, if available.
6384
6385 =cut
6386 sub unserialise_variable
6387 {
6388 my @v = split(/,/, $_[0]);
6389 my $rv;
6390 if ($v[0] eq 'VAL') {
6391         @v = split(/,/, $_[0], -1);
6392         $rv = &un_urlize($v[1]);
6393         }
6394 elsif ($v[0] eq 'SCALAR') {
6395         local $r = &un_urlize($v[1]);
6396         $rv = \$r;
6397         }
6398 elsif ($v[0] eq 'ARRAY') {
6399         $rv = [ ];
6400         for(my $i=1; $i<@v; $i++) {
6401                 push(@$rv, &unserialise_variable(&un_urlize($v[$i])));
6402                 }
6403         }
6404 elsif ($v[0] eq 'HASH') {
6405         $rv = { };
6406         for(my $i=1; $i<@v; $i+=2) {
6407                 $rv->{&unserialise_variable(&un_urlize($v[$i]))} =
6408                         &unserialise_variable(&un_urlize($v[$i+1]));
6409                 }
6410         }
6411 elsif ($v[0] eq 'REF') {
6412         local $r = &unserialise_variable($v[1]);
6413         $rv = \$r;
6414         }
6415 elsif ($v[0] eq 'UNDEF') {
6416         $rv = undef;
6417         }
6418 elsif ($v[0] =~ /^OBJECT\s+(.*)$/) {
6419         # An object hash that we have to re-bless
6420         my $cls = $1;
6421         $rv = { };
6422         for(my $i=1; $i<@v; $i+=2) {
6423                 $rv->{&unserialise_variable(&un_urlize($v[$i]))} =
6424                         &unserialise_variable(&un_urlize($v[$i+1]));
6425                 }
6426         eval "use $cls";
6427         bless $rv, $cls;
6428         }
6429 return $rv;
6430 }
6431
6432 =head2 other_groups(user)
6433
6434 Returns a list of secondary groups a user is a member of, as a list of
6435 group names.
6436
6437 =cut
6438 sub other_groups
6439 {
6440 my ($user) = @_;
6441 my @rv;
6442 setgrent();
6443 while(my @g = getgrent()) {
6444         my @m = split(/\s+/, $g[3]);
6445         push(@rv, $g[2]) if (&indexof($user, @m) >= 0);
6446         }
6447 endgrent() if ($gconfig{'os_type'} ne 'hpux');
6448 return @rv;
6449 }
6450
6451 =head2 date_chooser_button(dayfield, monthfield, yearfield)
6452
6453 Returns HTML for a button that pops up a data chooser window. The parameters
6454 are :
6455
6456 =item dayfield - Name of the text field to place the day of the month into.
6457
6458 =item monthfield - Name of the select field to select the month of the year in, indexed from 1.
6459
6460 =item yearfield - Name of the text field to place the year into.
6461
6462 =cut
6463 sub date_chooser_button
6464 {
6465 return &theme_date_chooser_button(@_)
6466         if (defined(&theme_date_chooser_button));
6467 my ($w, $h) = (250, 225);
6468 if ($gconfig{'db_sizedate'}) {
6469         ($w, $h) = split(/x/, $gconfig{'db_sizedate'});
6470         }
6471 return "<input type=button onClick='window.dfield = form.$_[0]; window.mfield = form.$_[1]; window.yfield = form.$_[2]; window.open(\"$gconfig{'webprefix'}/date_chooser.cgi?day=\"+escape(dfield.value)+\"&month=\"+escape(mfield.selectedIndex)+\"&year=\"+yfield.value, \"chooser\", \"toolbar=no,menubar=no,scrollbars=yes,width=$w,height=$h\")' value=\"...\">\n";
6472 }
6473
6474 =head2 help_file(module, file)
6475
6476 Returns the path to a module's help file of some name, typically under the
6477 help directory with a .html extension.
6478
6479 =cut
6480 sub help_file
6481 {
6482 my $mdir = &module_root_directory($_[0]);
6483 my $dir = "$mdir/help";
6484 foreach my $o (@lang_order_list) {
6485         my $lang = "$dir/$_[1].$o.html";
6486         return $lang if (-r $lang);
6487         }
6488 return "$dir/$_[1].html";
6489 }
6490
6491 =head2 seed_random
6492
6493 Seeds the random number generator, if not already done in this script. On Linux
6494 this makes use of the current time, process ID and a read from /dev/urandom.
6495 On other systems, only the current time and process ID are used.
6496
6497 =cut
6498 sub seed_random
6499 {
6500 if (!$main::done_seed_random) {
6501         if (open(RANDOM, "/dev/urandom")) {
6502                 my $buf;
6503                 read(RANDOM, $buf, 4);
6504                 close(RANDOM);
6505                 srand(time() ^ $$ ^ $buf);
6506                 }
6507         else {
6508                 srand(time() ^ $$);
6509                 }
6510         $main::done_seed_random = 1;
6511         }
6512 }
6513
6514 =head2 disk_usage_kb(directory)
6515
6516 Returns the number of kB used by some directory and all subdirs. Implemented
6517 by calling the C<du -k> command.
6518
6519 =cut
6520 sub disk_usage_kb
6521 {
6522 my $dir = &translate_filename($_[0]);
6523 my $out;
6524 my $ex = &execute_command("du -sk ".quotemeta($dir), undef, \$out, undef, 0, 1);
6525 if ($ex) {
6526         &execute_command("du -s ".quotemeta($dir), undef, \$out, undef, 0, 1);
6527         }
6528 return $out =~ /^([0-9]+)/ ? $1 : "???";
6529 }
6530
6531 =head2 recursive_disk_usage(directory, [skip-regexp], [only-regexp])
6532
6533 Returns the number of bytes taken up by all files in some directory and all
6534 sub-directories, by summing up their lengths. The disk_usage_kb is more
6535 reflective of reality, as the filesystem typically pads file sizes to 1k or
6536 4k blocks.
6537
6538 =cut
6539 sub recursive_disk_usage
6540 {
6541 my $dir = &translate_filename($_[0]);
6542 my $skip = $_[1];
6543 my $only = $_[2];
6544 if (-l $dir) {
6545         return 0;
6546         }
6547 elsif (!-d $dir) {
6548         my @st = stat($dir);
6549         return $st[7];
6550         }
6551 else {
6552         my $rv = 0;
6553         opendir(DIR, $dir);
6554         my @files = readdir(DIR);
6555         closedir(DIR);
6556         foreach my $f (@files) {
6557                 next if ($f eq "." || $f eq "..");
6558                 next if ($skip && $f =~ /$skip/);
6559                 next if ($only && $f !~ /$only/);
6560                 $rv += &recursive_disk_usage("$dir/$f", $skip, $only);
6561                 }
6562         return $rv;
6563         }
6564 }
6565
6566 =head2 help_search_link(term, [ section, ... ] )
6567
6568 Returns HTML for a link to the man module for searching local and online
6569 docs for various search terms. The term parameter can either be a single
6570 word like 'bind', or a space-separated list of words. This function is typically
6571 used by modules that want to refer users to additional documentation in man
6572 pages or local system doc files.
6573
6574 =cut
6575 sub help_search_link
6576 {
6577 if (&foreign_available("man") && !$tconfig{'nosearch'}) {
6578         my $for = &urlize(shift(@_));
6579         return "<a href='$gconfig{'webprefix'}/man/search.cgi?".
6580                join("&", map { "section=$_" } @_)."&".
6581                "for=$for&exact=1&check=".&get_module_name()."'>".
6582                $text{'helpsearch'}."</a>\n";
6583         }
6584 else {
6585         return "";
6586         }
6587 }
6588
6589 =head2 make_http_connection(host, port, ssl, method, page, [&headers])
6590
6591 Opens a connection to some HTTP server, maybe through a proxy, and returns
6592 a handle object. The handle can then be used to send additional headers
6593 and read back a response. If anything goes wrong, returns an error string.
6594 The parameters are :
6595
6596 =item host - Hostname or IP address of the webserver to connect to.
6597
6598 =item port - HTTP port number to connect to.
6599
6600 =item ssl - Set to 1 to connect in SSL mode.
6601
6602 =item method - HTTP method, like GET or POST.
6603
6604 =item page - Page to request on the webserver, like /foo/index.html
6605
6606 =item headers - Array ref of additional HTTP headers, each of which is a 2-element array ref.
6607
6608 =cut
6609 sub make_http_connection
6610 {
6611 my ($host, $port, $ssl, $method, $page, $headers) = @_;
6612 my $htxt;
6613 if ($headers) {
6614         foreach my $h (@$headers) {
6615                 $htxt .= $h->[0].": ".$h->[1]."\r\n";
6616                 }
6617         $htxt .= "\r\n";
6618         }
6619 if (&is_readonly_mode()) {
6620         return "HTTP connections not allowed in readonly mode";
6621         }
6622 my $rv = { 'fh' => time().$$ };
6623 if ($ssl) {
6624         # Connect using SSL
6625         eval "use Net::SSLeay";
6626         $@ && return $text{'link_essl'};
6627         eval "Net::SSLeay::SSLeay_add_ssl_algorithms()";
6628         eval "Net::SSLeay::load_error_strings()";
6629         $rv->{'ssl_ctx'} = Net::SSLeay::CTX_new() ||
6630                 return "Failed to create SSL context";
6631         $rv->{'ssl_con'} = Net::SSLeay::new($rv->{'ssl_ctx'}) ||
6632                 return "Failed to create SSL connection";
6633         my $connected;
6634         if ($gconfig{'http_proxy'} =~ /^http:\/\/(\S+):(\d+)/ &&
6635             !&no_proxy($host)) {
6636                 # Via proxy
6637                 my $error;
6638                 &open_socket($1, $2, $rv->{'fh'}, \$error);
6639                 if (!$error) {
6640                         # Connected OK
6641                         my $fh = $rv->{'fh'};
6642                         print $fh "CONNECT $host:$port HTTP/1.0\r\n";
6643                         if ($gconfig{'proxy_user'}) {
6644                                 my $auth = &encode_base64(
6645                                    "$gconfig{'proxy_user'}:".
6646                                    "$gconfig{'proxy_pass'}");
6647                                 $auth =~ tr/\r\n//d;
6648                                 print $fh "Proxy-Authorization: Basic $auth\r\n";
6649                                 }
6650                         print $fh "\r\n";
6651                         my $line = <$fh>;
6652                         if ($line =~ /^HTTP(\S+)\s+(\d+)\s+(.*)/) {
6653                                 return "Proxy error : $3" if ($2 != 200);
6654                                 }
6655                         else {
6656                                 return "Proxy error : $line";
6657                                 }
6658                         $line = <$fh>;
6659                         $connected = 1;
6660                         }
6661                 elsif (!$gconfig{'proxy_fallback'}) {
6662                         # Connection to proxy failed - give up
6663                         return $error;
6664                         }
6665                 }
6666         if (!$connected) {
6667                 # Direct connection
6668                 my $error;
6669                 &open_socket($host, $port, $rv->{'fh'}, \$error);
6670                 return $error if ($error);
6671                 }
6672         Net::SSLeay::set_fd($rv->{'ssl_con'}, fileno($rv->{'fh'}));
6673         Net::SSLeay::connect($rv->{'ssl_con'}) ||
6674                 return "SSL connect() failed";
6675         my $rtxt = "$method $page HTTP/1.0\r\n".$htxt;
6676         Net::SSLeay::write($rv->{'ssl_con'}, $rtxt);
6677         }
6678 else {
6679         # Plain HTTP request
6680         my $connected;
6681         if ($gconfig{'http_proxy'} =~ /^http:\/\/(\S+):(\d+)/ &&
6682             !&no_proxy($host)) {
6683                 # Via a proxy
6684                 my $error;
6685                 &open_socket($1, $2, $rv->{'fh'}, \$error);
6686                 if (!$error) {
6687                         # Connected OK
6688                         $connected = 1;
6689                         my $fh = $rv->{'fh'};
6690                         my $rtxt = $method." ".
6691                                    "http://$host:$port$page HTTP/1.0\r\n";
6692                         if ($gconfig{'proxy_user'}) {
6693                                 my $auth = &encode_base64(
6694                                    "$gconfig{'proxy_user'}:".
6695                                    "$gconfig{'proxy_pass'}");
6696                                 $auth =~ tr/\r\n//d;
6697                                 $rtxt .= "Proxy-Authorization: Basic $auth\r\n";
6698                                 }
6699                         $rtxt .= $htxt;
6700                         print $fh $rtxt;
6701                         }
6702                 elsif (!$gconfig{'proxy_fallback'}) {
6703                         return $error;
6704                         }
6705                 }
6706         if (!$connected) {
6707                 # Connecting directly
6708                 my $error;
6709                 &open_socket($host, $port, $rv->{'fh'}, \$error);
6710                 return $error if ($error);
6711                 my $fh = $rv->{'fh'};
6712                 my $rtxt = "$method $page HTTP/1.0\r\n".$htxt;
6713                 print $fh $rtxt;
6714                 }
6715         }
6716 return $rv;
6717 }
6718
6719 =head2 read_http_connection(&handle, [bytes])
6720
6721 Reads either one line or up to the specified number of bytes from the handle,
6722 originally supplied by make_http_connection. 
6723
6724 =cut
6725 sub read_http_connection
6726 {
6727 my ($h) = @_;
6728 my $rv;
6729 if ($h->{'ssl_con'}) {
6730         if (!$_[1]) {
6731                 my ($idx, $more);
6732                 while(($idx = index($h->{'buffer'}, "\n")) < 0) {
6733                         # need to read more..
6734                         if (!($more = Net::SSLeay::read($h->{'ssl_con'}))) {
6735                                 # end of the data
6736                                 $rv = $h->{'buffer'};
6737                                 delete($h->{'buffer'});
6738                                 return $rv;
6739                                 }
6740                         $h->{'buffer'} .= $more;
6741                         }
6742                 $rv = substr($h->{'buffer'}, 0, $idx+1);
6743                 $h->{'buffer'} = substr($h->{'buffer'}, $idx+1);
6744                 }
6745         else {
6746                 if (length($h->{'buffer'})) {
6747                         $rv = $h->{'buffer'};
6748                         delete($h->{'buffer'});
6749                         }
6750                 else {
6751                         $rv = Net::SSLeay::read($h->{'ssl_con'}, $_[1]);
6752                         }
6753                 }
6754         }
6755 else {
6756         if ($_[1]) {
6757                 read($h->{'fh'}, $rv, $_[1]) > 0 || return undef;
6758                 }
6759         else {
6760                 my $fh = $h->{'fh'};
6761                 $rv = <$fh>;
6762                 }
6763         }
6764 $rv = undef if ($rv eq "");
6765 return $rv;
6766 }
6767
6768 =head2 write_http_connection(&handle, [data+])
6769
6770 Writes the given data to the given HTTP connection handle.
6771
6772 =cut
6773 sub write_http_connection
6774 {
6775 my $h = shift(@_);
6776 my $fh = $h->{'fh'};
6777 my $allok = 1;
6778 if ($h->{'ssl_ctx'}) {
6779         foreach my $s (@_) {
6780                 my $ok = Net::SSLeay::write($h->{'ssl_con'}, $s);
6781                 $allok = 0 if (!$ok);
6782                 }
6783         }
6784 else {
6785         my $ok = (print $fh @_);
6786         $allok = 0 if (!$ok);
6787         }
6788 return $allok;
6789 }
6790
6791 =head2 close_http_connection(&handle)
6792
6793 Closes a connection to an HTTP server, identified by the given handle.
6794
6795 =cut
6796 sub close_http_connection
6797 {
6798 my ($h) = @_;
6799 close($h->{'fh'});
6800 }
6801
6802 =head2 clean_environment
6803
6804 Deletes any environment variables inherited from miniserv so that they
6805 won't be passed to programs started by webmin. This is useful when calling
6806 programs that check for CGI-related environment variables and modify their
6807 behaviour, and to avoid passing sensitive variables to un-trusted programs.
6808
6809 =cut
6810 sub clean_environment
6811 {
6812 %UNCLEAN_ENV = %ENV;
6813 foreach my $k (keys %ENV) {
6814         if ($k =~ /^(HTTP|VIRTUALSERVER|QUOTA|USERADMIN)_/) {
6815                 delete($ENV{$k});
6816                 }
6817         }
6818 foreach my $e ('WEBMIN_CONFIG', 'SERVER_NAME', 'CONTENT_TYPE', 'REQUEST_URI',
6819             'PATH_INFO', 'WEBMIN_VAR', 'REQUEST_METHOD', 'GATEWAY_INTERFACE',
6820             'QUERY_STRING', 'REMOTE_USER', 'SERVER_SOFTWARE', 'SERVER_PROTOCOL',
6821             'REMOTE_HOST', 'SERVER_PORT', 'DOCUMENT_ROOT', 'SERVER_ROOT',
6822             'MINISERV_CONFIG', 'SCRIPT_NAME', 'SERVER_ADMIN', 'CONTENT_LENGTH',
6823             'HTTPS', 'FOREIGN_MODULE_NAME', 'FOREIGN_ROOT_DIRECTORY',
6824             'SCRIPT_FILENAME', 'PATH_TRANSLATED', 'BASE_REMOTE_USER',
6825             'DOCUMENT_REALROOT', 'MINISERV_CONFIG', 'MYSQL_PWD',
6826             'MINISERV_PID') {
6827         delete($ENV{$e});
6828         }
6829 }
6830
6831 =head2 reset_environment
6832
6833 Puts the environment back how it was before clean_environment was callled.
6834
6835 =cut
6836 sub reset_environment
6837 {
6838 if (%UNCLEAN_ENV) {
6839         foreach my $k (keys %UNCLEAN_ENV) {
6840                 $ENV{$k} = $UNCLEAN_ENV{$k};
6841                 }
6842         undef(%UNCLEAN_ENV);
6843         }
6844 }
6845
6846 =head2 progress_callback
6847
6848 Never called directly, but useful for passing to &http_download to print
6849 out progress of an HTTP request.
6850
6851 =cut
6852 sub progress_callback
6853 {
6854 if (defined(&theme_progress_callback)) {
6855         # Call the theme override
6856         return &theme_progress_callback(@_);
6857         }
6858 if ($_[0] == 2) {
6859         # Got size
6860         print $progress_callback_prefix;
6861         if ($_[1]) {
6862                 $progress_size = $_[1];
6863                 $progress_step = int($_[1] / 10);
6864                 print &text('progress_size2', $progress_callback_url,
6865                             &nice_size($progress_size)),"<br>\n";
6866                 }
6867         else {
6868                 print &text('progress_nosize', $progress_callback_url),"<br>\n";
6869                 }
6870         $last_progress_time = $last_progress_size = undef;
6871         }
6872 elsif ($_[0] == 3) {
6873         # Got data update
6874         my $sp = $progress_callback_prefix.("&nbsp;" x 5);
6875         if ($progress_size) {
6876                 # And we have a size to compare against
6877                 my $st = int(($_[1] * 10) / $progress_size);
6878                 my $time_now = time();
6879                 if ($st != $progress_step ||
6880                     $time_now - $last_progress_time > 60) {
6881                         # Show progress every 10% or 60 seconds
6882                         print $sp,&text('progress_datan', &nice_size($_[1]),
6883                                         int($_[1]*100/$progress_size)),"<br>\n";
6884                         $last_progress_time = $time_now;
6885                         }
6886                 $progress_step = $st;
6887                 }
6888         else {
6889                 # No total size .. so only show in 100k jumps
6890                 if ($_[1] > $last_progress_size+100*1024) {
6891                         print $sp,&text('progress_data2n',
6892                                         &nice_size($_[1])),"<br>\n";
6893                         $last_progress_size = $_[1];
6894                         }
6895                 }
6896         }
6897 elsif ($_[0] == 4) {
6898         # All done downloading
6899         print $progress_callback_prefix,&text('progress_done'),"<br>\n";
6900         }
6901 elsif ($_[0] == 5) {
6902         # Got new location after redirect
6903         $progress_callback_url = $_[1];
6904         }
6905 elsif ($_[0] == 6) {
6906         # URL is in cache
6907         $progress_callback_url = $_[1];
6908         print &text('progress_incache', $progress_callback_url),"<br>\n";
6909         }
6910 }
6911
6912 =head2 switch_to_remote_user
6913
6914 Changes the user and group of the current process to that of the unix user
6915 with the same name as the current webmin login, or fails if there is none.
6916 This should be called by Usermin module scripts that only need to run with
6917 limited permissions.
6918
6919 =cut
6920 sub switch_to_remote_user
6921 {
6922 @remote_user_info = $remote_user ? getpwnam($remote_user) :
6923                                    getpwuid($<);
6924 @remote_user_info || &error(&text('switch_remote_euser', $remote_user));
6925 &create_missing_homedir(\@remote_user_info);
6926 if ($< == 0) {
6927         &switch_to_unix_user(\@remote_user_info);
6928         $ENV{'USER'} = $ENV{'LOGNAME'} = $remote_user;
6929         $ENV{'HOME'} = $remote_user_info[7];
6930         }
6931 # Export global variables to caller
6932 if ($main::export_to_caller) {
6933         my ($callpkg) = caller();
6934         eval "\@${callpkg}::remote_user_info = \@remote_user_info";
6935         }
6936 }
6937
6938 =head2 switch_to_unix_user(&user-details)
6939
6940 Switches the current process to the UID and group ID from the given list
6941 of user details, which must be in the format returned by getpwnam.
6942
6943 =cut
6944 sub switch_to_unix_user
6945 {
6946 my ($uinfo) = @_;
6947 if (!defined($uinfo->[0])) {
6948         # No username given, so just use given GID
6949         ($(, $)) = ( $uinfo->[3], "$uinfo->[3] $uinfo->[3]" );
6950         }
6951 else {
6952         # Use all groups from user
6953         ($(, $)) = ( $uinfo->[3],
6954                      "$uinfo->[3] ".join(" ", $uinfo->[3],
6955                                          &other_groups($uinfo->[0])) );
6956         }
6957 eval {
6958         POSIX::setuid($uinfo->[2]);
6959         };
6960 if ($< != $uinfo->[2] || $> != $uinfo->[2]) {
6961         ($>, $<) = ( $uinfo->[2], $uinfo->[2] );
6962         }
6963 }
6964
6965 =head2 eval_as_unix_user(username, &code)
6966
6967 Runs some code fragment with the effective UID and GID switch to that
6968 of the given Unix user, so that file IO takes place with his permissions.
6969
6970 =cut
6971
6972 sub eval_as_unix_user
6973 {
6974 my ($user, $code) = @_;
6975 my @uinfo = getpwnam($user);
6976 if (!scalar(@uinfo)) {
6977         &error("eval_as_unix_user called with invalid user $user");
6978         }
6979 $) = $uinfo[3]." ".join(" ", $uinfo[3], &other_groups($user));
6980 $> = $uinfo[2];
6981 my @rv;
6982 eval {
6983         local $main::error_must_die = 1;
6984         @rv = &$code();
6985         };
6986 my $err = $@;
6987 $) = 0;
6988 $> = 0;
6989 if ($err) {
6990         $err =~ s/\s+at\s+(\/\S+)\s+line\s+(\d+)\.?//;
6991         &error($err);
6992         }
6993 return wantarray ? @rv : $rv[0];
6994 }
6995
6996 =head2 create_user_config_dirs
6997
6998 Creates per-user config directories and sets $user_config_directory and
6999 $user_module_config_directory to them. Also reads per-user module configs
7000 into %userconfig. This should be called by Usermin module scripts that need
7001 to store per-user preferences or other settings.
7002
7003 =cut
7004 sub create_user_config_dirs
7005 {
7006 return if (!$gconfig{'userconfig'});
7007 my @uinfo = @remote_user_info ? @remote_user_info : getpwnam($remote_user);
7008 return if (!@uinfo || !$uinfo[7]);
7009 &create_missing_homedir(\@uinfo);
7010 $user_config_directory = "$uinfo[7]/$gconfig{'userconfig'}";
7011 if (!-d $user_config_directory) {
7012         mkdir($user_config_directory, 0700) ||
7013                 &error("Failed to create $user_config_directory : $!");
7014         if ($< == 0 && $uinfo[2]) {
7015                 chown($uinfo[2], $uinfo[3], $user_config_directory);
7016                 }
7017         }
7018 if (&get_module_name()) {
7019         $user_module_config_directory = $user_config_directory."/".
7020                                         &get_module_name();
7021         if (!-d $user_module_config_directory) {
7022                 mkdir($user_module_config_directory, 0700) ||
7023                         &error("Failed to create $user_module_config_directory : $!");
7024                 if ($< == 0 && $uinfo[2]) {
7025                         chown($uinfo[2], $uinfo[3], $user_config_directory);
7026                         }
7027                 }
7028         undef(%userconfig);
7029         &read_file_cached("$module_root_directory/defaultuconfig",
7030                           \%userconfig);
7031         &read_file_cached("$module_config_directory/uconfig", \%userconfig);
7032         &read_file_cached("$user_module_config_directory/config",
7033                           \%userconfig);
7034         }
7035
7036 # Export global variables to caller
7037 if ($main::export_to_caller) {
7038         my ($callpkg) = caller();
7039         foreach my $v ('$user_config_directory',
7040                        '$user_module_config_directory', '%userconfig') {
7041                 my ($vt, $vn) = split('', $v, 2);
7042                 eval "${vt}${callpkg}::${vn} = ${vt}${vn}";
7043                 }
7044         }
7045 }
7046
7047 =head2 create_missing_homedir(&uinfo)
7048
7049 If auto homedir creation is enabled, create one for this user if needed.
7050 For internal use only.
7051
7052 =cut
7053 sub create_missing_homedir
7054 {
7055 my ($uinfo) = @_;
7056 if (!-e $uinfo->[7] && $gconfig{'create_homedir'}) {
7057         # Use has no home dir .. make one
7058         system("mkdir -p ".quotemeta($uinfo->[7]));
7059         chown($uinfo->[2], $uinfo->[3], $uinfo->[7]);
7060         if ($gconfig{'create_homedir_perms'} ne '') {
7061                 chmod(oct($gconfig{'create_homedir_perms'}), $uinfo->[7]);
7062                 }
7063         }
7064 }
7065
7066 =head2 filter_javascript(text)
7067
7068 Disables all javascript <script>, onClick= and so on tags in the given HTML,
7069 and returns the new HTML. Useful for displaying HTML from an un-trusted source.
7070
7071 =cut
7072 sub filter_javascript
7073 {
7074 my ($rv) = @_;
7075 $rv =~ s/<\s*script[^>]*>([\000-\377]*?)<\s*\/script\s*>//gi;
7076 $rv =~ s/(on(Abort|Blur|Change|Click|DblClick|DragDrop|Error|Focus|KeyDown|KeyPress|KeyUp|Load|MouseDown|MouseMove|MouseOut|MouseOver|MouseUp|Move|Reset|Resize|Select|Submit|Unload)=)/x$1/gi;
7077 $rv =~ s/(javascript:)/x$1/gi;
7078 $rv =~ s/(vbscript:)/x$1/gi;
7079 return $rv;
7080 }
7081
7082 =head2 resolve_links(path)
7083
7084 Given a path that may contain symbolic links, returns the real path.
7085
7086 =cut
7087 sub resolve_links
7088 {
7089 my ($path) = @_;
7090 $path =~ s/\/+/\//g;
7091 $path =~ s/\/$// if ($path ne "/");
7092 my @p = split(/\/+/, $path);
7093 shift(@p);
7094 for(my $i=0; $i<@p; $i++) {
7095         my $sofar = "/".join("/", @p[0..$i]);
7096         my $lnk = readlink($sofar);
7097         if ($lnk eq $sofar) {
7098                 # Link to itself! Cannot do anything more really ..
7099                 last;
7100                 }
7101         elsif ($lnk =~ /^\//) {
7102                 # Link is absolute..
7103                 return &resolve_links($lnk."/".join("/", @p[$i+1 .. $#p]));
7104                 }
7105         elsif ($lnk) {
7106                 # Link is relative
7107                 return &resolve_links("/".join("/", @p[0..$i-1])."/".$lnk."/".join("/", @p[$i+1 .. $#p]));
7108                 }
7109         }
7110 return $path;
7111 }
7112
7113 =head2 simplify_path(path, bogus)
7114
7115 Given a path, maybe containing elements ".." and "." , convert it to a
7116 clean, absolute form. Returns undef if this is not possible.
7117
7118 =cut
7119 sub simplify_path
7120 {
7121 my ($dir) = @_;
7122 $dir =~ s/^\/+//g;
7123 $dir =~ s/\/+$//g;
7124 my @bits = split(/\/+/, $dir);
7125 my @fixedbits = ();
7126 $_[1] = 0;
7127 foreach my $b (@bits) {
7128         if ($b eq ".") {
7129                 # Do nothing..
7130                 }
7131         elsif ($b eq "..") {
7132                 # Remove last dir
7133                 if (scalar(@fixedbits) == 0) {
7134                         # Cannot! Already at root!
7135                         return undef;
7136                         }
7137                 pop(@fixedbits);
7138                 }
7139         else {
7140                 # Add dir to list
7141                 push(@fixedbits, $b);
7142                 }
7143         }
7144 return "/".join('/', @fixedbits);
7145 }
7146
7147 =head2 same_file(file1, file2)
7148
7149 Returns 1 if two files are actually the same
7150
7151 =cut
7152 sub same_file
7153 {
7154 return 1 if ($_[0] eq $_[1]);
7155 return 0 if ($_[0] !~ /^\// || $_[1] !~ /^\//);
7156 my @stat1 = $stat_cache{$_[0]} ? @{$stat_cache{$_[0]}}
7157                                : (@{$stat_cache{$_[0]}} = stat($_[0]));
7158 my @stat2 = $stat_cache{$_[1]} ? @{$stat_cache{$_[1]}}
7159                                : (@{$stat_cache{$_[1]}} = stat($_[1]));
7160 return 0 if (!@stat1 || !@stat2);
7161 return $stat1[0] == $stat2[0] && $stat1[1] == $stat2[1];
7162 }
7163
7164 =head2 flush_webmin_caches
7165
7166 Clears all in-memory and on-disk caches used by Webmin.
7167
7168 =cut
7169 sub flush_webmin_caches
7170 {
7171 undef(%main::read_file_cache);
7172 undef(%main::acl_hash_cache);
7173 undef(%main::acl_array_cache);
7174 undef(%main::has_command_cache);
7175 undef(@main::list_languages_cache);
7176 undef($main::got_list_usermods_cache);
7177 undef(@main::list_usermods_cache);
7178 undef(%main::foreign_installed_cache);
7179 unlink("$config_directory/module.infos.cache");
7180 &get_all_module_infos();
7181 }
7182
7183 =head2 list_usermods
7184
7185 Returns a list of additional module restrictions. For internal use in
7186 Usermin only.
7187
7188 =cut
7189 sub list_usermods
7190 {
7191 if (!$main::got_list_usermods_cache) {
7192         @main::list_usermods_cache = ( );
7193         local $_;
7194         open(USERMODS, "$config_directory/usermin.mods");
7195         while(<USERMODS>) {
7196                 if (/^([^:]+):(\+|-|):(.*)/) {
7197                         push(@main::list_usermods_cache,
7198                              [ $1, $2, [ split(/\s+/, $3) ] ]);
7199                         }
7200                 }
7201         close(USERMODS);
7202         $main::got_list_usermods_cache = 1;
7203         }
7204 return @main::list_usermods_cache;
7205 }
7206
7207 =head2 available_usermods(&allmods, &usermods)
7208
7209 Returns a list of modules that are available to the given user, based
7210 on usermod additional/subtractions. For internal use by Usermin only.
7211
7212 =cut
7213 sub available_usermods
7214 {
7215 return @{$_[0]} if (!@{$_[1]});
7216
7217 my %mods = map { $_->{'dir'}, 1 } @{$_[0]};
7218 my @uinfo = @remote_user_info;
7219 @uinfo = getpwnam($remote_user) if (!@uinfo);
7220 foreach my $u (@{$_[1]}) {
7221         my $applies;
7222         if ($u->[0] eq "*" || $u->[0] eq $remote_user) {
7223                 $applies++;
7224                 }
7225         elsif ($u->[0] =~ /^\@(.*)$/) {
7226                 # Check for group membership
7227                 my @ginfo = getgrnam($1);
7228                 $applies++ if (@ginfo && ($ginfo[2] == $uinfo[3] ||
7229                         &indexof($remote_user, split(/\s+/, $ginfo[3])) >= 0));
7230                 }
7231         elsif ($u->[0] =~ /^\//) {
7232                 # Check users and groups in file
7233                 local $_;
7234                 open(USERFILE, $u->[0]);
7235                 while(<USERFILE>) {
7236                         tr/\r\n//d;
7237                         if ($_ eq $remote_user) {
7238                                 $applies++;
7239                                 }
7240                         elsif (/^\@(.*)$/) {
7241                                 my @ginfo = getgrnam($1);
7242                                 $applies++
7243                                   if (@ginfo && ($ginfo[2] == $uinfo[3] ||
7244                                       &indexof($remote_user,
7245                                                split(/\s+/, $ginfo[3])) >= 0));
7246                                 }
7247                         last if ($applies);
7248                         }
7249                 close(USERFILE);
7250                 }
7251         if ($applies) {
7252                 if ($u->[1] eq "+") {
7253                         map { $mods{$_}++ } @{$u->[2]};
7254                         }
7255                 elsif ($u->[1] eq "-") {
7256                         map { delete($mods{$_}) } @{$u->[2]};
7257                         }
7258                 else {
7259                         undef(%mods);
7260                         map { $mods{$_}++ } @{$u->[2]};
7261                         }
7262                 }
7263         }
7264 return grep { $mods{$_->{'dir'}} } @{$_[0]};
7265 }
7266
7267 =head2 get_available_module_infos(nocache)
7268
7269 Returns a list of modules available to the current user, based on
7270 operating system support, access control and usermod restrictions. Useful
7271 in themes that need to display a list of modules the user can use.
7272 Each element of the returned array is a hash reference in the same format as
7273 returned by get_module_info.
7274
7275 =cut
7276 sub get_available_module_infos
7277 {
7278 my (%acl, %uacl);
7279 &read_acl(\%acl, \%uacl, [ $base_remote_user ]);
7280 my $risk = $gconfig{'risk_'.$base_remote_user};
7281 my @rv;
7282 foreach my $minfo (&get_all_module_infos($_[0])) {
7283         next if (!&check_os_support($minfo));
7284         if ($risk) {
7285                 # Check module risk level
7286                 next if ($risk ne 'high' && $minfo->{'risk'} &&
7287                          $minfo->{'risk'} !~ /$risk/);
7288                 }
7289         else {
7290                 # Check user's ACL
7291                 next if (!$acl{$base_remote_user,$minfo->{'dir'}} &&
7292                          !$acl{$base_remote_user,"*"});
7293                 }
7294         next if (&is_readonly_mode() && !$minfo->{'readonly'});
7295         push(@rv, $minfo);
7296         }
7297
7298 # Check usermod restrictions
7299 my @usermods = &list_usermods();
7300 @rv = sort { $a->{'desc'} cmp $b->{'desc'} }
7301             &available_usermods(\@rv, \@usermods);
7302
7303 # Check RBAC restrictions
7304 my @rbacrv;
7305 foreach my $m (@rv) {
7306         if (&supports_rbac($m->{'dir'}) &&
7307             &use_rbac_module_acl(undef, $m->{'dir'})) {
7308                 local $rbacs = &get_rbac_module_acl($remote_user,
7309                                                     $m->{'dir'});
7310                 if ($rbacs) {
7311                         # RBAC allows
7312                         push(@rbacrv, $m);
7313                         }
7314                 }
7315         else {
7316                 # Module or system doesn't support RBAC
7317                 push(@rbacrv, $m) if (!$gconfig{'rbacdeny_'.$base_remote_user});
7318                 }
7319         }
7320
7321 # Check theme vetos
7322 my @themerv;
7323 if (defined(&theme_foreign_available)) {
7324         foreach my $m (@rbacrv) {
7325                 if (&theme_foreign_available($m->{'dir'})) {
7326                         push(@themerv, $m);
7327                         }
7328                 }
7329         }
7330 else {
7331         @themerv = @rbacrv;
7332         }
7333
7334 # Check licence module vetos
7335 my @licrv;
7336 if ($main::licence_module) {
7337         foreach my $m (@themerv) {
7338                 if (&foreign_call($main::licence_module,
7339                                   "check_module_licence", $m->{'dir'})) {       
7340                         push(@licrv, $m);
7341                         }
7342                 }
7343         }
7344 else {  
7345         @licrv = @themerv;
7346         }
7347
7348 return @licrv;
7349 }
7350
7351 =head2 get_visible_module_infos(nocache)
7352
7353 Like get_available_module_infos, but excludes hidden modules from the list.
7354 Each element of the returned array is a hash reference in the same format as
7355 returned by get_module_info.
7356
7357 =cut
7358 sub get_visible_module_infos
7359 {
7360 my ($nocache) = @_;
7361 my $pn = &get_product_name();
7362 return grep { !$_->{'hidden'} &&
7363               !$_->{$pn.'_hidden'} } &get_available_module_infos($nocache);
7364 }
7365
7366 =head2 get_visible_modules_categories(nocache)
7367
7368 Returns a list of Webmin module categories, each of which is a hash ref
7369 with 'code', 'desc' and 'modules' keys. The modules value is an array ref
7370 of modules in the category, in the format returned by get_module_info.
7371 Un-used modules are automatically assigned to the 'unused' category, and
7372 those with no category are put into 'others'.
7373
7374 =cut
7375 sub get_visible_modules_categories
7376 {
7377 my ($nocache) = @_;
7378 my @mods = &get_visible_module_infos($nocache);
7379 my @unmods;
7380 if (&get_product_name() eq 'webmin') {
7381         @unmods = grep { $_->{'installed'} eq '0' } @mods;
7382         @mods = grep { $_->{'installed'} ne '0' } @mods;
7383         }
7384 my %cats = &list_categories(\@mods);
7385 my @rv;
7386 foreach my $c (keys %cats) {
7387         my $cat = { 'code' => $c || 'other',
7388                     'desc' => $cats{$c} };
7389         $cat->{'modules'} = [ grep { $_->{'category'} eq $c } @mods ];
7390         push(@rv, $cat);
7391         }
7392 @rv = sort { ($b->{'code'} eq "others" ? "" : $b->{'code'}) cmp
7393              ($a->{'code'} eq "others" ? "" : $a->{'code'}) } @rv;
7394 if (@unmods) {
7395         # Add un-installed modules in magic category
7396         my $cat = { 'code' => 'unused',
7397                     'desc' => $text{'main_unused'},
7398                     'unused' => 1,
7399                     'modules' => \@unmods };
7400         push(@rv, $cat);
7401         }
7402 return @rv;
7403 }
7404
7405 =head2 is_under_directory(directory, file)
7406
7407 Returns 1 if the given file is under the specified directory, 0 if not.
7408 Symlinks are taken into account in the file to find it's 'real' location.
7409
7410 =cut
7411 sub is_under_directory
7412 {
7413 my ($dir, $file) = @_;
7414 return 1 if ($dir eq "/");
7415 return 0 if ($file =~ /\.\./);
7416 my $ld = &resolve_links($dir);
7417 if ($ld ne $dir) {
7418         return &is_under_directory($ld, $file);
7419         }
7420 my $lp = &resolve_links($file);
7421 if ($lp ne $file) {
7422         return &is_under_directory($dir, $lp);
7423         }
7424 return 0 if (length($file) < length($dir));
7425 return 1 if ($dir eq $file);
7426 $dir =~ s/\/*$/\//;
7427 return substr($file, 0, length($dir)) eq $dir;
7428 }
7429
7430 =head2 parse_http_url(url, [basehost, baseport, basepage, basessl])
7431
7432 Given an absolute URL, returns the host, port, page and ssl flag components.
7433 Relative URLs can also be parsed, if the base information is provided.
7434
7435 =cut
7436 sub parse_http_url
7437 {
7438 if ($_[0] =~ /^(http|https):\/\/\[([^\]]+)\](:(\d+))?(\/\S*)?$/ ||
7439     $_[0] =~ /^(http|https):\/\/([^:\/]+)(:(\d+))?(\/\S*)?$/) {
7440         # An absolute URL
7441         my $ssl = $1 eq 'https';
7442         return ($2, $3 ? $4 : $ssl ? 443 : 80, $5 || "/", $ssl);
7443         }
7444 elsif (!$_[1]) {
7445         # Could not parse
7446         return undef;
7447         }
7448 elsif ($_[0] =~ /^\/\S*$/) {
7449         # A relative to the server URL
7450         return ($_[1], $_[2], $_[0], $_[4]);
7451         }
7452 else {
7453         # A relative to the directory URL
7454         my $page = $_[3];
7455         $page =~ s/[^\/]+$//;
7456         return ($_[1], $_[2], $page.$_[0], $_[4]);
7457         }
7458 }
7459
7460 =head2 check_clicks_function
7461
7462 Returns HTML for a JavaScript function called check_clicks that returns
7463 true when first called, but false subsequently. Useful on onClick for
7464 critical buttons. Deprecated, as this method of preventing duplicate actions
7465 is un-reliable.
7466
7467 =cut
7468 sub check_clicks_function
7469 {
7470 return <<EOF;
7471 <script>
7472 clicks = 0;
7473 function check_clicks(form)
7474 {
7475 clicks++;
7476 if (clicks == 1)
7477         return true;
7478 else {
7479         if (form != null) {
7480                 for(i=0; i<form.length; i++)
7481                         form.elements[i].disabled = true;
7482                 }
7483         return false;
7484         }
7485 }
7486 </script>
7487 EOF
7488 }
7489
7490 =head2 load_entities_map
7491
7492 Returns a hash ref containing mappings between HTML entities (like ouml) and
7493 ascii values (like 246). Mainly for internal use.
7494
7495 =cut
7496 sub load_entities_map
7497 {
7498 if (!%entities_map_cache) {
7499         local $_;
7500         open(EMAP, "$root_directory/entities_map.txt");
7501         while(<EMAP>) {
7502                 if (/^(\d+)\s+(\S+)/) {
7503                         $entities_map_cache{$2} = $1;
7504                         }
7505                 }
7506         close(EMAP);
7507         }
7508 return \%entities_map_cache;
7509 }
7510
7511 =head2 entities_to_ascii(string)
7512
7513 Given a string containing HTML entities like &ouml; and &#55;, replace them
7514 with their ASCII equivalents.
7515
7516 =cut
7517 sub entities_to_ascii
7518 {
7519 my ($str) = @_;
7520 my $emap = &load_entities_map();
7521 $str =~ s/&([a-z]+);/chr($emap->{$1})/ge;
7522 $str =~ s/&#(\d+);/chr($1)/ge;
7523 return $str;
7524 }
7525
7526 =head2 get_product_name
7527
7528 Returns either 'webmin' or 'usermin', depending on which program the current
7529 module is in. Useful for modules that can be installed into either.
7530
7531 =cut
7532 sub get_product_name
7533 {
7534 return $gconfig{'product'} if (defined($gconfig{'product'}));
7535 return defined($gconfig{'userconfig'}) ? 'usermin' : 'webmin';
7536 }
7537
7538 =head2 get_charset
7539
7540 Returns the character set for the current language, such as iso-8859-1.
7541
7542 =cut
7543 sub get_charset
7544 {
7545 my $charset = defined($gconfig{'charset'}) ? $gconfig{'charset'} :
7546                  $current_lang_info->{'charset'} ?
7547                  $current_lang_info->{'charset'} : $default_charset;
7548 return $charset;
7549 }
7550
7551 =head2 get_display_hostname
7552
7553 Returns the system's hostname for UI display purposes. This may be different
7554 from the actual hostname if you administrator has configured it so in the
7555 Webmin Configuration module.
7556
7557 =cut
7558 sub get_display_hostname
7559 {
7560 if ($gconfig{'hostnamemode'} == 0) {
7561         return &get_system_hostname();
7562         }
7563 elsif ($gconfig{'hostnamemode'} == 3) {
7564         return $gconfig{'hostnamedisplay'};
7565         }
7566 else {
7567         my $h = $ENV{'HTTP_HOST'};
7568         $h =~ s/:\d+//g;
7569         if ($gconfig{'hostnamemode'} == 2) {
7570                 $h =~ s/^(www|ftp|mail)\.//i;
7571                 }
7572         return $h;
7573         }
7574 }
7575
7576 =head2 save_module_config([&config], [modulename])
7577
7578 Saves the configuration for some module. The config parameter is an optional
7579 hash reference of names and values to save, which defaults to the global
7580 %config hash. The modulename parameter is the module to update the config
7581 file, which defaults to the current module.
7582
7583 =cut
7584 sub save_module_config
7585 {
7586 my $c = $_[0] || { &get_module_variable('%config') };
7587 my $m = defined($_[1]) ? $_[1] : &get_module_name();
7588 &write_file("$config_directory/$m/config", $c);
7589 }
7590
7591 =head2 save_user_module_config([&config], [modulename])
7592
7593 Saves the user's Usermin preferences for some module. The config parameter is
7594 an optional hash reference of names and values to save, which defaults to the
7595 global %userconfig hash. The modulename parameter is the module to update the
7596 config file, which defaults to the current module.
7597
7598 =cut
7599 sub save_user_module_config
7600 {
7601 my $c = $_[0] || { &get_module_variable('%userconfig') };
7602 my $m = $_[1] || &get_module_name();
7603 my $ucd = $user_config_directory;
7604 if (!$ucd) {
7605         my @uinfo = @remote_user_info ? @remote_user_info
7606                                       : getpwnam($remote_user);
7607         return if (!@uinfo || !$uinfo[7]);
7608         $ucd = "$uinfo[7]/$gconfig{'userconfig'}";
7609         }
7610 &write_file("$ucd/$m/config", $c);
7611 }
7612
7613 =head2 nice_size(bytes, [min])
7614
7615 Converts a number of bytes into a number followed by a suffix like GB, MB
7616 or kB. Rounding is to two decimal digits. The optional min parameter sets the
7617 smallest units to use - so you could pass 1024*1024 to never show bytes or kB.
7618
7619 =cut
7620 sub nice_size
7621 {
7622 my ($units, $uname);
7623 if (abs($_[0]) > 1024*1024*1024*1024 || $_[1] >= 1024*1024*1024*1024) {
7624         $units = 1024*1024*1024*1024;
7625         $uname = "TB";
7626         }
7627 elsif (abs($_[0]) > 1024*1024*1024 || $_[1] >= 1024*1024*1024) {
7628         $units = 1024*1024*1024;
7629         $uname = "GB";
7630         }
7631 elsif (abs($_[0]) > 1024*1024 || $_[1] >= 1024*1024) {
7632         $units = 1024*1024;
7633         $uname = "MB";
7634         }
7635 elsif (abs($_[0]) > 1024 || $_[1] >= 1024) {
7636         $units = 1024;
7637         $uname = "kB";
7638         }
7639 else {
7640         $units = 1;
7641         $uname = "bytes";
7642         }
7643 my $sz = sprintf("%.2f", ($_[0]*1.0 / $units));
7644 $sz =~ s/\.00$//;
7645 return $sz." ".$uname;
7646 }
7647
7648 =head2 get_perl_path
7649
7650 Returns the path to Perl currently in use, such as /usr/bin/perl.
7651
7652 =cut
7653 sub get_perl_path
7654 {
7655 if (open(PERL, "$config_directory/perl-path")) {
7656         my $rv;
7657         chop($rv = <PERL>);
7658         close(PERL);
7659         return $rv;
7660         }
7661 return $^X if (-x $^X);
7662 return &has_command("perl");
7663 }
7664
7665 =head2 get_goto_module([&mods])
7666
7667 Returns the details of a module that the current user should be re-directed
7668 to after logging in, or undef if none. Useful for themes.
7669
7670 =cut
7671 sub get_goto_module
7672 {
7673 my @mods = $_[0] ? @{$_[0]} : &get_visible_module_infos();
7674 if ($gconfig{'gotomodule'}) {
7675         my ($goto) = grep { $_->{'dir'} eq $gconfig{'gotomodule'} } @mods;
7676         return $goto if ($goto);
7677         }
7678 if (@mods == 1 && $gconfig{'gotoone'}) {
7679         return $mods[0];
7680         }
7681 return undef;
7682 }
7683
7684 =head2 select_all_link(field, form, [text])
7685
7686 Returns HTML for a 'Select all' link that uses Javascript to select
7687 multiple checkboxes with the same name. The parameters are :
7688
7689 =item field - Name of the checkbox inputs.
7690
7691 =item form - Index of the form on the page.
7692
7693 =item text - Message for the link, defaulting to 'Select all'.
7694
7695 =cut
7696 sub select_all_link
7697 {
7698 return &theme_select_all_link(@_) if (defined(&theme_select_all_link));
7699 my ($field, $form, $text) = @_;
7700 $form = int($form);
7701 $text ||= $text{'ui_selall'};
7702 return "<a class='select_all' href='#' onClick='var ff = document.forms[$form].$field; ff.checked = true; for(i=0; i<ff.length; i++) { if (!ff[i].disabled) { ff[i].checked = true; } } return false'>$text</a>";
7703 }
7704
7705 =head2 select_invert_link(field, form, text)
7706
7707 Returns HTML for an 'Invert selection' link that uses Javascript to invert the
7708 selection on multiple checkboxes with the same name. The parameters are :
7709
7710 =item field - Name of the checkbox inputs.
7711
7712 =item form - Index of the form on the page.
7713
7714 =item text - Message for the link, defaulting to 'Invert selection'.
7715
7716 =cut
7717 sub select_invert_link
7718 {
7719 return &theme_select_invert_link(@_) if (defined(&theme_select_invert_link));
7720 my ($field, $form, $text) = @_;
7721 $form = int($form);
7722 $text ||= $text{'ui_selinv'};
7723 return "<a class='select_invert' href='#' onClick='var ff = document.forms[$form].$field; ff.checked = !ff.checked; for(i=0; i<ff.length; i++) { if (!ff[i].disabled) { ff[i].checked = !ff[i].checked; } } return false'>$text</a>";
7724 }
7725
7726 =head2 select_rows_link(field, form, text, &rows)
7727
7728 Returns HTML for a link that uses Javascript to select rows with particular
7729 values for their checkboxes. The parameters are :
7730
7731 =item field - Name of the checkbox inputs.
7732
7733 =item form - Index of the form on the page.
7734
7735 =item text - Message for the link, de
7736
7737 =item rows - Reference to an array of 1 or 0 values, indicating which rows to check.
7738
7739 =cut
7740 sub select_rows_link
7741 {
7742 return &theme_select_rows_link(@_) if (defined(&theme_select_rows_link));
7743 my ($field, $form, $text, $rows) = @_;
7744 $form = int($form);
7745 my $js = "var sel = { ".join(",", map { "\"".&quote_escape($_)."\":1" } @$rows)." }; ";
7746 $js .= "for(var i=0; i<document.forms[$form].${field}.length; i++) { var r = document.forms[$form].${field}[i]; r.checked = sel[r.value]; } ";
7747 $js .= "return false;";
7748 return "<a href='#' onClick='$js'>$text</a>";
7749 }
7750
7751 =head2 check_pid_file(file)
7752
7753 Given a pid file, returns the PID it contains if the process is running.
7754
7755 =cut
7756 sub check_pid_file
7757 {
7758 open(PIDFILE, $_[0]) || return undef;
7759 my $pid = <PIDFILE>;
7760 close(PIDFILE);
7761 $pid =~ /^\s*(\d+)/ || return undef;
7762 kill(0, $1) || return undef;
7763 return $1;
7764 }
7765
7766 =head2 get_mod_lib
7767
7768 Return the local os-specific library name to this module. For internal use only.
7769
7770 =cut
7771 sub get_mod_lib
7772 {
7773 my $mn = &get_module_name();
7774 my $md = &module_root_directory($mn);
7775 if (-r "$md/$mn-$gconfig{'os_type'}-$gconfig{'os_version'}-lib.pl") {
7776         return "$mn-$gconfig{'os_type'}-$gconfig{'os_version'}-lib.pl";
7777         }
7778 elsif (-r "$md/$mn-$gconfig{'os_type'}-lib.pl") {
7779         return "$mn-$gconfig{'os_type'}-lib.pl";
7780         }
7781 elsif (-r "$md/$mn-generic-lib.pl") {
7782         return "$mn-generic-lib.pl";
7783         }
7784 else {
7785         return "";
7786         }
7787 }
7788
7789 =head2 module_root_directory(module)
7790
7791 Given a module name, returns its root directory. On a typical Webmin install,
7792 all modules are under the same directory - but it is theoretically possible to
7793 have more than one.
7794
7795 =cut
7796 sub module_root_directory
7797 {
7798 my $d = ref($_[0]) ? $_[0]->{'dir'} : $_[0];
7799 if (@root_directories > 1) {
7800         foreach my $r (@root_directories) {
7801                 if (-d "$r/$d") {
7802                         return "$r/$d";
7803                         }
7804                 }
7805         }
7806 return "$root_directories[0]/$d";
7807 }
7808
7809 =head2 list_mime_types
7810
7811 Returns a list of all known MIME types and their extensions, as a list of hash
7812 references with keys :
7813
7814 =item type - The MIME type, like text/plain.
7815
7816 =item exts - A list of extensions, like .doc and .avi.
7817
7818 =item desc - A human-readable description for the MIME type.
7819
7820 =cut
7821 sub list_mime_types
7822 {
7823 if (!@list_mime_types_cache) {
7824         local $_;
7825         open(MIME, "$root_directory/mime.types");
7826         while(<MIME>) {
7827                 my $cmt;
7828                 s/\r|\n//g;
7829                 if (s/#\s*(.*)$//g) {
7830                         $cmt = $1;
7831                         }
7832                 my ($type, @exts) = split(/\s+/);
7833                 if ($type) {
7834                         push(@list_mime_types_cache, { 'type' => $type,
7835                                                        'exts' => \@exts,
7836                                                        'desc' => $cmt });
7837                         }
7838                 }
7839         close(MIME);
7840         }
7841 return @list_mime_types_cache;
7842 }
7843
7844 =head2 guess_mime_type(filename, [default])
7845
7846 Given a file name like xxx.gif or foo.html, returns a guessed MIME type.
7847 The optional default parameter sets a default type of use if none is found,
7848 which defaults to application/octet-stream.
7849
7850 =cut
7851 sub guess_mime_type
7852 {
7853 if ($_[0] =~ /\.([A-Za-z0-9\-]+)$/) {
7854         my $ext = $1;
7855         foreach my $t (&list_mime_types()) {
7856                 foreach my $e (@{$t->{'exts'}}) {
7857                         return $t->{'type'} if (lc($e) eq lc($ext));
7858                         }
7859                 }
7860         }
7861 return @_ > 1 ? $_[1] : "application/octet-stream";
7862 }
7863
7864 =head2 open_tempfile([handle], file, [no-error], [no-tempfile], [safe?])
7865
7866 Opens a file handle for writing to a temporary file, which will only be
7867 renamed over the real file when the handle is closed. This allows critical
7868 files like /etc/shadow to be updated safely, even if writing fails part way
7869 through due to lack of disk space. The parameters are :
7870
7871 =item handle - File handle to open, as you would use in Perl's open function.
7872
7873 =item file - Full path to the file to write, prefixed by > or >> to indicate over-writing or appending. In append mode, no temp file is used.
7874
7875 =item no-error - By default, this function will call error if the open fails. Setting this parameter to 1 causes it to return 0 on failure, and set $! with the error code.
7876
7877 =item no-tempfile - If set to 1, writing will be direct to the file instead of using a temporary file.
7878
7879 =item safe - Indicates to users in read-only mode that this write is safe and non-destructive.
7880
7881 =cut
7882 sub open_tempfile
7883 {
7884 if (@_ == 1) {
7885         # Just getting a temp file
7886         if (!defined($main::open_tempfiles{$_[0]})) {
7887                 $_[0] =~ /^(.*)\/(.*)$/ || return $_[0];
7888                 my $dir = $1 || "/";
7889                 my $tmp = "$dir/$2.webmintmp.$$";
7890                 $main::open_tempfiles{$_[0]} = $tmp;
7891                 push(@main::temporary_files, $tmp);
7892                 }
7893         return $main::open_tempfiles{$_[0]};
7894         }
7895 else {
7896         # Actually opening
7897         my ($fh, $file, $noerror, $notemp, $safe) = @_;
7898         $fh = &callers_package($fh);
7899
7900         my %gaccess = &get_module_acl(undef, "");
7901         my $db = $gconfig{'debug_what_write'};
7902         if ($file =~ /\r|\n|\0/) {
7903                 if ($noerror) { return 0; }
7904                 else { &error("Filename contains invalid characters"); }
7905                 }
7906         if (&is_readonly_mode() && $file =~ />/ && !$safe) {
7907                 # Read-only mode .. veto all writes
7908                 print STDERR "vetoing write to $file\n";
7909                 return open($fh, ">$null_file");
7910                 }
7911         elsif ($file =~ /^(>|>>|)nul$/i) {
7912                 # Write to Windows null device
7913                 &webmin_debug_log($1 eq ">" ? "WRITE" :
7914                           $1 eq ">>" ? "APPEND" : "READ", "nul") if ($db);
7915                 }
7916         elsif ($file =~ /^(>|>>)(\/dev\/.*)/ || lc($file) eq "nul") {
7917                 # Writes to /dev/null or TTYs don't need to be handled
7918                 &webmin_debug_log($1 eq ">" ? "WRITE" : "APPEND", $2) if ($db);
7919                 return open($fh, $file);
7920                 }
7921         elsif ($file =~ /^>\s*(([a-zA-Z]:)?\/.*)$/ && !$notemp) {
7922                 &webmin_debug_log("WRITE", $1) if ($db);
7923                 # Over-writing a file, via a temp file
7924                 $file = $1;
7925                 $file = &translate_filename($file);
7926                 while(-l $file) {
7927                         # Open the link target instead
7928                         $file = &resolve_links($file);
7929                         }
7930                 if (-d $file) {
7931                         # Cannot open a directory!
7932                         if ($noerror) { return 0; }
7933                         else { &error("Cannot write to directory $file"); }
7934                         }
7935                 my $tmp = &open_tempfile($file);
7936                 my $ex = open($fh, ">$tmp");
7937                 if (!$ex && $! =~ /permission/i) {
7938                         # Could not open temp file .. try opening actual file
7939                         # instead directly
7940                         $ex = open($fh, ">$file");
7941                         delete($main::open_tempfiles{$file});
7942                         }
7943                 else {
7944                         $main::open_temphandles{$fh} = $file;
7945                         }
7946                 binmode($fh);
7947                 if (!$ex && !$noerror) {
7948                         &error(&text("efileopen", $file, $!));
7949                         }
7950                 return $ex;
7951                 }
7952         elsif ($file =~ /^>\s*(([a-zA-Z]:)?\/.*)$/ && $notemp) {
7953                 # Just writing direct to a file
7954                 &webmin_debug_log("WRITE", $1) if ($db);
7955                 $file = $1;
7956                 $file = &translate_filename($file);
7957                 my @old_attributes = &get_clear_file_attributes($file);
7958                 my $ex = open($fh, ">$file");
7959                 &reset_file_attributes($file, \@old_attributes);
7960                 $main::open_temphandles{$fh} = $file;
7961                 if (!$ex && !$noerror) {
7962                         &error(&text("efileopen", $file, $!));
7963                         }
7964                 binmode($fh);
7965                 return $ex;
7966                 }
7967         elsif ($file =~ /^>>\s*(([a-zA-Z]:)?\/.*)$/) {
7968                 # Appending to a file .. nothing special to do
7969                 &webmin_debug_log("APPEND", $1) if ($db);
7970                 $file = $1;
7971                 $file = &translate_filename($file);
7972                 my @old_attributes = &get_clear_file_attributes($file);
7973                 my $ex = open($fh, ">>$file");
7974                 &reset_file_attributes($file, \@old_attributes);
7975                 $main::open_temphandles{$fh} = $file;
7976                 if (!$ex && !$noerror) {
7977                         &error(&text("efileopen", $file, $!));
7978                         }
7979                 binmode($fh);
7980                 return $ex;
7981                 }
7982         elsif ($file =~ /^([a-zA-Z]:)?\//) {
7983                 # Read mode .. nothing to do here
7984                 &webmin_debug_log("READ", $file) if ($db);
7985                 $file = &translate_filename($file);
7986                 return open($fh, $file);
7987                 }
7988         elsif ($file eq ">" || $file eq ">>") {
7989                 my ($package, $filename, $line) = caller;
7990                 if ($noerror) { return 0; }
7991                 else { &error("Missing file to open at ${package}::${filename} line $line"); }
7992                 }
7993         else {
7994                 my ($package, $filename, $line) = caller;
7995                 &error("Unsupported file or mode $file at ${package}::${filename} line $line");
7996                 }
7997         }
7998 }
7999
8000 =head2 close_tempfile(file|handle)
8001
8002 Copies a temp file to the actual file, assuming that all writes were
8003 successful. The handle must have been one passed to open_tempfile.
8004
8005 =cut
8006 sub close_tempfile
8007 {
8008 my $file;
8009 my $fh = &callers_package($_[0]);
8010
8011 if (defined($file = $main::open_temphandles{$fh})) {
8012         # Closing a handle
8013         close($fh) || &error(&text("efileclose", $file, $!));
8014         delete($main::open_temphandles{$fh});
8015         return &close_tempfile($file);
8016         }
8017 elsif (defined($main::open_tempfiles{$_[0]})) {
8018         # Closing a file
8019         &webmin_debug_log("CLOSE", $_[0]) if ($gconfig{'debug_what_write'});
8020         my @st = stat($_[0]);
8021         if (&is_selinux_enabled() && &has_command("chcon")) {
8022                 # Set original security context
8023                 system("chcon --reference=".quotemeta($_[0]).
8024                        " ".quotemeta($main::open_tempfiles{$_[0]}).
8025                        " >/dev/null 2>&1");
8026                 }
8027         my @old_attributes = &get_clear_file_attributes($_[0]);
8028         rename($main::open_tempfiles{$_[0]}, $_[0]) || &error("Failed to replace $_[0] with $main::open_tempfiles{$_[0]} : $!");
8029         if (@st) {
8030                 # Set original permissions and ownership
8031                 chmod($st[2], $_[0]);
8032                 chown($st[4], $st[5], $_[0]);
8033                 }
8034         &reset_file_attributes($_[0], \@old_attributes);
8035         delete($main::open_tempfiles{$_[0]});
8036         @main::temporary_files = grep { $_ ne $main::open_tempfiles{$_[0]} } @main::temporary_files;
8037         if ($main::open_templocks{$_[0]}) {
8038                 &unlock_file($_[0]);
8039                 delete($main::open_templocks{$_[0]});
8040                 }
8041         return 1;
8042         }
8043 else {
8044         # Must be closing a handle not associated with a file
8045         close($_[0]);
8046         return 1;
8047         }
8048 }
8049
8050 =head2 print_tempfile(handle, text, ...)
8051
8052 Like the normal print function, but calls &error on failure. Useful when
8053 combined with open_tempfile, to ensure that a criticial file is never
8054 only partially written.
8055
8056 =cut
8057 sub print_tempfile
8058 {
8059 my ($fh, @args) = @_;
8060 $fh = &callers_package($fh);
8061 (print $fh @args) || &error(&text("efilewrite",
8062                             $main::open_temphandles{$fh} || $fh, $!));
8063 }
8064
8065 =head2 is_selinux_enabled
8066
8067 Returns 1 if SElinux is supported on this system and enabled, 0 if not.
8068
8069 =cut
8070 sub is_selinux_enabled
8071 {
8072 if (!defined($main::selinux_enabled_cache)) {
8073         my %seconfig;
8074         if ($gconfig{'os_type'} !~ /-linux$/) {
8075                 # Not on linux, so no way
8076                 $main::selinux_enabled_cache = 0;
8077                 }
8078         elsif (&read_env_file("/etc/selinux/config", \%seconfig)) {
8079                 # Use global config file
8080                 $main::selinux_enabled_cache =
8081                         $seconfig{'SELINUX'} eq 'disabled' ||
8082                         !$seconfig{'SELINUX'} ? 0 : 1;
8083                 }
8084         else {
8085                 # Use selinuxenabled command
8086                 #$selinux_enabled_cache =
8087                 #       system("selinuxenabled >/dev/null 2>&1") ? 0 : 1;
8088                 $main::selinux_enabled_cache = 0;
8089                 }
8090         }
8091 return $main::selinux_enabled_cache;
8092 }
8093
8094 =head2 get_clear_file_attributes(file)
8095
8096 Finds file attributes that may prevent writing, clears them and returns them
8097 as a list. May call error. Mainly for internal use by open_tempfile and
8098 close_tempfile.
8099
8100 =cut
8101 sub get_clear_file_attributes
8102 {
8103 my ($file) = @_;
8104 my @old_attributes;
8105 if ($gconfig{'chattr'}) {
8106         # Get original immutable bit
8107         my $out = &backquote_command(
8108                 "lsattr ".quotemeta($file)." 2>/dev/null");
8109         if (!$?) {
8110                 $out =~ s/\s\S+\n//;
8111                 @old_attributes = grep { $_ ne '-' } split(//, $out);
8112                 }
8113         if (&indexof("i", @old_attributes) >= 0) {
8114                 my $err = &backquote_logged(
8115                         "chattr -i ".quotemeta($file)." 2>&1");
8116                 if ($?) {
8117                         &error("Failed to remove immutable bit on ".
8118                                "$file : $err");
8119                         }
8120                 }
8121         }
8122 return @old_attributes;
8123 }
8124
8125 =head2 reset_file_attributes(file, &attributes)
8126
8127 Put back cleared attributes on some file. May call error. Mainly for internal
8128 use by close_tempfile.
8129
8130 =cut
8131 sub reset_file_attributes
8132 {
8133 my ($file, $old_attributes) = @_;
8134 if (&indexof("i", @$old_attributes) >= 0) {
8135         my $err = &backquote_logged(
8136                 "chattr +i ".quotemeta($file)." 2>&1");
8137         if ($?) {
8138                 &error("Failed to restore immutable bit on ".
8139                        "$file : $err");
8140                 }
8141         }
8142 }
8143
8144 =head2 cleanup_tempnames
8145
8146 Remove all temporary files generated using transname. Typically only called
8147 internally when a Webmin script exits.
8148
8149 =cut
8150 sub cleanup_tempnames
8151 {
8152 foreach my $t (@main::temporary_files) {
8153         &unlink_file($t);
8154         }
8155 @main::temporary_files = ( );
8156 }
8157
8158 =head2 open_lock_tempfile([handle], file, [no-error])
8159
8160 Returns a temporary file for writing to some actual file, and also locks it.
8161 Effectively the same as calling lock_file and open_tempfile on the same file,
8162 but calls the unlock for you automatically when it is closed.
8163
8164 =cut
8165 sub open_lock_tempfile
8166 {
8167 my ($fh, $file, $noerror, $notemp, $safe) = @_;
8168 $fh = &callers_package($fh);
8169 my $lockfile = $file;
8170 $lockfile =~ s/^[^\/]*//;
8171 if ($lockfile =~ /^\//) {
8172         $main::open_templocks{$lockfile} = &lock_file($lockfile);
8173         }
8174 return &open_tempfile($fh, $file, $noerror, $notemp, $safe);
8175 }
8176
8177 sub END
8178 {
8179 $main::end_exit_status ||= $?;
8180 if ($$ == $main::initial_process_id) {
8181         # Exiting from initial process
8182         &cleanup_tempnames();
8183         if ($gconfig{'debug_what_start'} && $main::debug_log_start_time &&
8184             $main::debug_log_start_module eq &get_module_name()) {
8185                 my $len = time() - $main::debug_log_start_time;
8186                 &webmin_debug_log("STOP", "runtime=$len");
8187                 $main::debug_log_start_time = 0;
8188                 }
8189         if (!$ENV{'SCRIPT_NAME'} &&
8190             $main::initial_module_name eq &get_module_name()) {
8191                 # In a command-line script - call the real exit, so that the
8192                 # exit status gets properly propogated. In some cases this
8193                 # was not happening.
8194                 exit($main::end_exit_status);
8195                 }
8196         }
8197 }
8198
8199 =head2 month_to_number(month)
8200
8201 Converts a month name like feb to a number like 1.
8202
8203 =cut
8204 sub month_to_number
8205 {
8206 return $month_to_number_map{lc(substr($_[0], 0, 3))};
8207 }
8208
8209 =head2 number_to_month(number)
8210
8211 Converts a number like 1 to a month name like Feb.
8212
8213 =cut
8214 sub number_to_month
8215 {
8216 return ucfirst($number_to_month_map{$_[0]});
8217 }
8218
8219 =head2 get_rbac_module_acl(user, module)
8220
8221 Returns a hash reference of RBAC overrides ACLs for some user and module.
8222 May return undef if none exist (indicating access denied), or the string *
8223 if full access is granted.
8224
8225 =cut
8226 sub get_rbac_module_acl
8227 {
8228 my ($user, $mod) = @_;
8229 eval "use Authen::SolarisRBAC";
8230 return undef if ($@);
8231 my %rv;
8232 my $foundany = 0;
8233 if (Authen::SolarisRBAC::chkauth("webmin.$mod.admin", $user)) {
8234         # Automagic webmin.modulename.admin authorization exists .. allow access
8235         $foundany = 1;
8236         if (!Authen::SolarisRBAC::chkauth("webmin.$mod.config", $user)) {
8237                 %rv = ( 'noconfig' => 1 );
8238                 }
8239         else {
8240                 %rv = ( );
8241                 }
8242         }
8243 local $_;
8244 open(RBAC, &module_root_directory($mod)."/rbac-mapping");
8245 while(<RBAC>) {
8246         s/\r|\n//g;
8247         s/#.*$//;
8248         my ($auths, $acls) = split(/\s+/, $_);
8249         my @auths = split(/,/, $auths);
8250         next if (!$auths);
8251         my ($merge) = ($acls =~ s/^\+//);
8252         my $gotall = 1;
8253         if ($auths eq "*") {
8254                 # These ACLs apply to all RBAC users.
8255                 # Only if there is some that match a specific authorization
8256                 # later will they be used though.
8257                 }
8258         else {
8259                 # Check each of the RBAC authorizations
8260                 foreach my $a (@auths) {
8261                         if (!Authen::SolarisRBAC::chkauth($a, $user)) {
8262                                 $gotall = 0;
8263                                 last;
8264                                 }
8265                         }
8266                 $foundany++ if ($gotall);
8267                 }
8268         if ($gotall) {
8269                 # Found an RBAC authorization - return the ACLs
8270                 return "*" if ($acls eq "*");
8271                 my %acl = map { split(/=/, $_, 2) } split(/,/, $acls);
8272                 if ($merge) {
8273                         # Just add to current set
8274                         foreach my $a (keys %acl) {
8275                                 $rv{$a} = $acl{$a};
8276                                 }
8277                         }
8278                 else {
8279                         # Found final ACLs
8280                         return \%acl;
8281                         }
8282                 }
8283         }
8284 close(RBAC);
8285 return !$foundany ? undef : %rv ? \%rv : undef;
8286 }
8287
8288 =head2 supports_rbac([module])
8289
8290 Returns 1 if RBAC client support is available, such as on Solaris.
8291
8292 =cut
8293 sub supports_rbac
8294 {
8295 return 0 if ($gconfig{'os_type'} ne 'solaris');
8296 eval "use Authen::SolarisRBAC";
8297 return 0 if ($@);
8298 if ($_[0]) {
8299         #return 0 if (!-r &module_root_directory($_[0])."/rbac-mapping");
8300         }
8301 return 1;
8302 }
8303
8304 =head2 supports_ipv6()
8305
8306 Returns 1 if outgoing IPv6 connections can be made
8307
8308 =cut
8309 sub supports_ipv6
8310 {
8311 return $ipv6_module_error ? 0 : 1;
8312 }
8313
8314 =head2 use_rbac_module_acl(user, module)
8315
8316 Returns 1 if some user should use RBAC to get permissions for a module
8317
8318 =cut
8319 sub use_rbac_module_acl
8320 {
8321 my $u = defined($_[0]) ? $_[0] : $base_remote_user;
8322 my $m = defined($_[1]) ? $_[1] : &get_module_name();
8323 return 1 if ($gconfig{'rbacdeny_'.$u});         # RBAC forced for user
8324 my %access = &get_module_acl($u, $m, 1);
8325 return $access{'rbac'} ? 1 : 0;
8326 }
8327
8328 =head2 execute_command(command, stdin, stdout, stderr, translate-files?, safe?)
8329
8330 Runs some command, possibly feeding it input and capturing output to the
8331 give files or scalar references. The parameters are :
8332
8333 =item command - Full command to run, possibly including shell meta-characters.
8334
8335 =item stdin - File to read input from, or a scalar ref containing input, or undef if no input should be given.
8336
8337 =item stdout - File to write output to, or a scalar ref into which output should be placed, or undef if the output is to be discarded.
8338
8339 =item stderr - File to write error output to, or a scalar ref into which error output should be placed, or undef if the error output is to be discarded.
8340
8341 =item translate-files - Set to 1 to apply filename translation to any filenames. Usually has no effect.
8342
8343 =item safe - Set to 1 if this command is safe and does not modify the state of the system.
8344
8345 =cut
8346 sub execute_command
8347 {
8348 my ($cmd, $stdin, $stdout, $stderr, $trans, $safe) = @_;
8349 if (&is_readonly_mode() && !$safe) {
8350         print STDERR "Vetoing command $_[0]\n";
8351         $? = 0;
8352         return 0;
8353         }
8354 $cmd = &translate_command($cmd);
8355
8356 # Use ` operator where possible
8357 &webmin_debug_log('CMD', "cmd=$cmd") if ($gconfig{'debug_what_cmd'});
8358 if (!$stdin && ref($stdout) && !$stderr) {
8359         $cmd = "($cmd)" if ($gconfig{'os_type'} ne 'windows');
8360         $$stdout = `$cmd 2>$null_file`;
8361         return $?;
8362         }
8363 elsif (!$stdin && ref($stdout) && $stdout eq $stderr) {
8364         $cmd = "($cmd)" if ($gconfig{'os_type'} ne 'windows');
8365         $$stdout = `$cmd 2>&1`;
8366         return $?;
8367         }
8368 elsif (!$stdin && !$stdout && !$stderr) {
8369         $cmd = "($cmd)" if ($gconfig{'os_type'} ne 'windows');
8370         return system("$cmd >$null_file 2>$null_file <$null_file");
8371         }
8372
8373 # Setup pipes
8374 $| = 1;         # needed on some systems to flush before forking
8375 pipe(EXECSTDINr, EXECSTDINw);
8376 pipe(EXECSTDOUTr, EXECSTDOUTw);
8377 pipe(EXECSTDERRr, EXECSTDERRw);
8378 my $pid;
8379 if (!($pid = fork())) {
8380         untie(*STDIN);
8381         untie(*STDOUT);
8382         untie(*STDERR);
8383         open(STDIN, "<&EXECSTDINr");
8384         open(STDOUT, ">&EXECSTDOUTw");
8385         if (ref($stderr) && $stderr eq $stdout) {
8386                 open(STDERR, ">&EXECSTDOUTw");
8387                 }
8388         else {
8389                 open(STDERR, ">&EXECSTDERRw");
8390                 }
8391         $| = 1;
8392         close(EXECSTDINw);
8393         close(EXECSTDOUTr);
8394         close(EXECSTDERRr);
8395
8396         my $fullcmd = "($cmd)";
8397         if ($stdin && !ref($stdin)) {
8398                 $fullcmd .= " <$stdin";
8399                 }
8400         if ($stdout && !ref($stdout)) {
8401                 $fullcmd .= " >$stdout";
8402                 }
8403         if ($stderr && !ref($stderr)) {
8404                 if ($stderr eq $stdout) {
8405                         $fullcmd .= " 2>&1";
8406                         }
8407                 else {
8408                         $fullcmd .= " 2>$stderr";
8409                         }
8410                 }
8411         if ($gconfig{'os_type'} eq 'windows') {
8412                 exec($fullcmd);
8413                 }
8414         else {
8415                 exec("/bin/sh", "-c", $fullcmd);
8416                 }
8417         print "Exec failed : $!\n";
8418         exit(1);
8419         }
8420 close(EXECSTDINr);
8421 close(EXECSTDOUTw);
8422 close(EXECSTDERRw);
8423
8424 # Feed input and capture output
8425 local $_;
8426 if ($stdin && ref($stdin)) {
8427         print EXECSTDINw $$stdin;
8428         close(EXECSTDINw);
8429         }
8430 if ($stdout && ref($stdout)) {
8431         $$stdout = undef;
8432         while(<EXECSTDOUTr>) {
8433                 $$stdout .= $_;
8434                 }
8435         close(EXECSTDOUTr);
8436         }
8437 if ($stderr && ref($stderr) && $stderr ne $stdout) {
8438         $$stderr = undef;
8439         while(<EXECSTDERRr>) {
8440                 $$stderr .= $_;
8441                 }
8442         close(EXECSTDERRr);
8443         }
8444
8445 # Get exit status
8446 waitpid($pid, 0);
8447 return $?;
8448 }
8449
8450 =head2 open_readfile(handle, file)
8451
8452 Opens some file for reading. Returns 1 on success, 0 on failure. Pretty much
8453 exactly the same as Perl's open function.
8454
8455 =cut
8456 sub open_readfile
8457 {
8458 my ($fh, $file) = @_;
8459 $fh = &callers_package($fh);
8460 my $realfile = &translate_filename($file);
8461 &webmin_debug_log('READ', $file) if ($gconfig{'debug_what_read'});
8462 return open($fh, "<".$realfile);
8463 }
8464
8465 =head2 open_execute_command(handle, command, output?, safe?)
8466
8467 Runs some command, with the specified file handle set to either write to it if
8468 in-or-out is set to 0, or read to it if output is set to 1. The safe flag
8469 indicates if the command modifies the state of the system or not.
8470
8471 =cut
8472 sub open_execute_command
8473 {
8474 my ($fh, $cmd, $mode, $safe) = @_;
8475 $fh = &callers_package($fh);
8476 my $realcmd = &translate_command($cmd);
8477 if (&is_readonly_mode() && !$safe) {
8478         # Don't actually run it
8479         print STDERR "vetoing command $cmd\n";
8480         $? = 0;
8481         if ($mode == 0) {
8482                 return open($fh, ">$null_file");
8483                 }
8484         else {
8485                 return open($fh, $null_file);
8486                 }
8487         }
8488 # Really run it
8489 &webmin_debug_log('CMD', "cmd=$realcmd mode=$mode")
8490         if ($gconfig{'debug_what_cmd'});
8491 if ($mode == 0) {
8492         return open($fh, "| $cmd");
8493         }
8494 elsif ($mode == 1) {
8495         return open($fh, "$cmd 2>$null_file |");
8496         }
8497 elsif ($mode == 2) {
8498         return open($fh, "$cmd 2>&1 |");
8499         }
8500 }
8501
8502 =head2 translate_filename(filename)
8503
8504 Applies all relevant registered translation functions to a filename. Mostly
8505 for internal use, and typically does nothing.
8506
8507 =cut
8508 sub translate_filename
8509 {
8510 my ($realfile) = @_;
8511 my @funcs = grep { $_->[0] eq &get_module_name() ||
8512                    !defined($_->[0]) } @main::filename_callbacks;
8513 foreach my $f (@funcs) {
8514         my $func = $f->[1];
8515         $realfile = &$func($realfile, @{$f->[2]});
8516         }
8517 return $realfile;
8518 }
8519
8520 =head2 translate_command(filename)
8521
8522 Applies all relevant registered translation functions to a command. Mostly
8523 for internal use, and typically does nothing.
8524
8525 =cut
8526 sub translate_command
8527 {
8528 my ($realcmd) = @_;
8529 my @funcs = grep { $_->[0] eq &get_module_name() ||
8530                    !defined($_->[0]) } @main::command_callbacks;
8531 foreach my $f (@funcs) {
8532         my $func = $f->[1];
8533         $realcmd = &$func($realcmd, @{$f->[2]});
8534         }
8535 return $realcmd;
8536 }
8537
8538 =head2 register_filename_callback(module|undef, &function, &args)
8539
8540 Registers some function to be called when the specified module (or all
8541 modules) tries to open a file for reading and writing. The function must
8542 return the actual file to open. This allows you to override which files
8543 other code actually operates on, via the translate_filename function.
8544
8545 =cut
8546 sub register_filename_callback
8547 {
8548 my ($mod, $func, $args) = @_;
8549 push(@main::filename_callbacks, [ $mod, $func, $args ]);
8550 }
8551
8552 =head2 register_command_callback(module|undef, &function, &args)
8553
8554 Registers some function to be called when the specified module (or all
8555 modules) tries to execute a command. The function must return the actual
8556 command to run. This allows you to override which commands other other code
8557 actually runs, via the translate_command function.
8558
8559 =cut
8560 sub register_command_callback
8561 {
8562 my ($mod, $func, $args) = @_;
8563 push(@main::command_callbacks, [ $mod, $func, $args ]);
8564 }
8565
8566 =head2 capture_function_output(&function, arg, ...)
8567
8568 Captures output that some function prints to STDOUT, and returns it. Useful
8569 for functions outside your control that print data when you really want to
8570 manipulate it before output.
8571
8572 =cut
8573 sub capture_function_output
8574 {
8575 my ($func, @args) = @_;
8576 socketpair(SOCKET2, SOCKET1, AF_UNIX, SOCK_STREAM, PF_UNSPEC);
8577 my $old = select(SOCKET1);
8578 my @rv = &$func(@args);
8579 select($old);
8580 close(SOCKET1);
8581 my $out;
8582 local $_;
8583 while(<SOCKET2>) {
8584         $out .= $_;
8585         }
8586 close(SOCKET2);
8587 return wantarray ? ($out, \@rv) : $out;
8588 }
8589
8590 =head2 capture_function_output_tempfile(&function, arg, ...)
8591
8592 Behaves the same as capture_function_output, but uses a temporary file
8593 to avoid buffer full problems.
8594
8595 =cut
8596 sub capture_function_output_tempfile
8597 {
8598 my ($func, @args) = @_;
8599 my $temp = &transname();
8600 open(BUFFER, ">$temp");
8601 my $old = select(BUFFER);
8602 my @rv = &$func(@args);
8603 select($old);
8604 close(BUFFER);
8605 my $out = &read_file_contents($temp);
8606 &unlink_file($temp);
8607 return wantarray ? ($out, \@rv) : $out;
8608 }
8609
8610 =head2 modules_chooser_button(field, multiple, [form])
8611
8612 Returns HTML for a button for selecting one or many Webmin modules.
8613 field - Name of the HTML field to place the module names into.
8614 multiple - Set to 1 if multiple modules can be selected.
8615 form - Index of the form on the page.
8616
8617 =cut
8618 sub modules_chooser_button
8619 {
8620 return &theme_modules_chooser_button(@_)
8621         if (defined(&theme_modules_chooser_button));
8622 my $form = defined($_[2]) ? $_[2] : 0;
8623 my $w = $_[1] ? 700 : 500;
8624 my $h = 200;
8625 if ($_[1] && $gconfig{'db_sizemodules'}) {
8626         ($w, $h) = split(/x/, $gconfig{'db_sizemodules'});
8627         }
8628 elsif (!$_[1] && $gconfig{'db_sizemodule'}) {
8629         ($w, $h) = split(/x/, $gconfig{'db_sizemodule'});
8630         }
8631 return "<input type=button onClick='ifield = document.forms[$form].$_[0]; chooser = window.open(\"$gconfig{'webprefix'}/module_chooser.cgi?multi=$_[1]&module=\"+escape(ifield.value), \"chooser\", \"toolbar=no,menubar=no,scrollbars=yes,width=$w,height=$h\"); chooser.ifield = ifield; window.ifield = ifield' value=\"...\">\n";
8632 }
8633
8634 =head2 substitute_template(text, &hash)
8635
8636 Given some text and a hash reference, for each ocurrance of $FOO or ${FOO} in
8637 the text replaces it with the value of the hash key foo. Also supports blocks
8638 like ${IF-FOO} ... ${ENDIF-FOO}, whose contents are only included if foo is 
8639 non-zero, and ${IF-FOO} ... ${ELSE-FOO} ... ${ENDIF-FOO}.
8640
8641 =cut
8642 sub substitute_template
8643 {
8644 # Add some extra fixed parameters to the hash
8645 my %hash = %{$_[1]};
8646 $hash{'hostname'} = &get_system_hostname();
8647 $hash{'webmin_config'} = $config_directory;
8648 $hash{'webmin_etc'} = $config_directory;
8649 $hash{'module_config'} = &get_module_variable('$module_config_directory');
8650 $hash{'webmin_var'} = $var_directory;
8651
8652 # Add time-based parameters, for use in DNS
8653 $hash{'current_time'} = time();
8654 my @tm = localtime($hash{'current_time'});
8655 $hash{'current_year'} = $tm[5]+1900;
8656 $hash{'current_month'} = sprintf("%2.2d", $tm[4]+1);
8657 $hash{'current_day'} = sprintf("%2.2d", $tm[3]);
8658 $hash{'current_hour'} = sprintf("%2.2d", $tm[2]);
8659 $hash{'current_minute'} = sprintf("%2.2d", $tm[1]);
8660 $hash{'current_second'} = sprintf("%2.2d", $tm[0]);
8661
8662 # Actually do the substition
8663 my $rv = $_[0];
8664 foreach my $s (keys %hash) {
8665         next if ($s eq '');     # Prevent just $ from being subbed
8666         my $us = uc($s);
8667         my $sv = $hash{$s};
8668         $rv =~ s/\$\{\Q$us\E\}/$sv/g;
8669         $rv =~ s/\$\Q$us\E/$sv/g;
8670         if ($sv) {
8671                 # Replace ${IF}..${ELSE}..${ENDIF} block with first value,
8672                 # and ${IF}..${ENDIF} with value
8673                 $rv =~ s/\$\{IF-\Q$us\E\}(\n?)([\000-\377]*?)\$\{ELSE-\Q$us\E\}(\n?)([\000-\377]*?)\$\{ENDIF-\Q$us\E\}(\n?)/$2/g;
8674                 $rv =~ s/\$\{IF-\Q$us\E\}(\n?)([\000-\377]*?)\$\{ENDIF-\Q$us\E\}(\n?)/$2/g;
8675
8676                 # Replace $IF..$ELSE..$ENDIF block with first value,
8677                 # and $IF..$ENDIF with value
8678                 $rv =~ s/\$IF-\Q$us\E(\n?)([\000-\377]*?)\$ELSE-\Q$us\E(\n?)([\000-\377]*?)\$ENDIF-\Q$us\E(\n?)/$2/g;
8679                 $rv =~ s/\$IF-\Q$us\E(\n?)([\000-\377]*?)\$ENDIF-\Q$us\E(\n?)/$2/g;
8680
8681                 # Replace ${IFEQ}..${ENDIFEQ} block with first value if
8682                 # matching, nothing if not
8683                 $rv =~ s/\$\{IFEQ-\Q$us\E-\Q$sv\E\}(\n?)([\000-\377]*?)\$\{ENDIFEQ-\Q$us\E-\Q$sv\E\}(\n?)/$2/g;
8684                 $rv =~ s/\$\{IFEQ-\Q$us\E-[^\}]+}(\n?)([\000-\377]*?)\$\{ENDIFEQ-\Q$us\E-[^\}]+\}(\n?)//g;
8685
8686                 # Replace $IFEQ..$ENDIFEQ block with first value if
8687                 # matching, nothing if not
8688                 $rv =~ s/\$IFEQ-\Q$us\E-\Q$sv\E(\n?)([\000-\377]*?)\$ENDIFEQ-\Q$us\E-\Q$sv\E(\n?)/$2/g;
8689                 $rv =~ s/\$IFEQ-\Q$us\E-\S+(\n?)([\000-\377]*?)\$ENDIFEQ-\Q$us\E-\S+(\n?)//g;
8690                 }
8691         else {
8692                 # Replace ${IF}..${ELSE}..${ENDIF} block with second value,
8693                 # and ${IF}..${ENDIF} with nothing
8694                 $rv =~ s/\$\{IF-\Q$us\E\}(\n?)([\000-\377]*?)\$\{ELSE-\Q$us\E\}(\n?)([\000-\377]*?)\$\{ENDIF-\Q$us\E\}(\n?)/$4/g;
8695                 $rv =~ s/\$\{IF-\Q$us\E\}(\n?)([\000-\377]*?)\$\{ENDIF-\Q$us\E\}(\n?)//g;
8696
8697                 # Replace $IF..$ELSE..$ENDIF block with second value,
8698                 # and $IF..$ENDIF with nothing
8699                 $rv =~ s/\$IF-\Q$us\E(\n?)([\000-\377]*?)\$ELSE-\Q$us\E(\n?)([\000-\377]*?)\$ENDIF-\Q$us\E(\n?)/$4/g;
8700                 $rv =~ s/\$IF-\Q$us\E(\n?)([\000-\377]*?)\$ENDIF-\Q$us\E(\n?)//g;
8701
8702                 # Replace ${IFEQ}..${ENDIFEQ} block with nothing
8703                 $rv =~ s/\$\{IFEQ-\Q$us\E-[^\}]+}(\n?)([\000-\377]*?)\$\{ENDIFEQ-\Q$us\E-[^\}]+\}(\n?)//g;
8704                 $rv =~ s/\$IFEQ-\Q$us\E-\S+(\n?)([\000-\377]*?)\$ENDIFEQ-\Q$us\E-\S+(\n?)//g;
8705                 }
8706         }
8707
8708 # Now assume any $IF blocks whose variables are not present in the hash
8709 # evaluate to false.
8710 # $IF...$ELSE x $ENDIF => x
8711 $rv =~ s/\$\{IF\-([A-Z]+)\}.*?\$\{ELSE\-\1\}(.*?)\$\{ENDIF\-\1\}/$2/gs;
8712 # $IF...x...$ENDIF => (nothing)
8713 $rv =~ s/\$\{IF\-([A-Z]+)\}.*?\$\{ENDIF\-\1\}//gs;
8714 # ${var} => (nothing)
8715 $rv =~ s/\$\{[A-Z]+\}//g;
8716
8717 return $rv;
8718 }
8719
8720 =head2 running_in_zone
8721
8722 Returns 1 if the current Webmin instance is running in a Solaris zone. Used to
8723 disable module and features that are not appropriate, like those that modify
8724 mounted filesystems.
8725
8726 =cut
8727 sub running_in_zone
8728 {
8729 return 0 if ($gconfig{'os_type'} ne 'solaris' ||
8730              $gconfig{'os_version'} < 10);
8731 my $zn = `zonename 2>$null_file`;
8732 chop($zn);
8733 return $zn && $zn ne "global";
8734 }
8735
8736 =head2 running_in_vserver
8737
8738 Returns 1 if the current Webmin instance is running in a Linux VServer.
8739 Used to disable modules and features that are not appropriate.
8740
8741 =cut
8742 sub running_in_vserver
8743 {
8744 return 0 if ($gconfig{'os_type'} !~ /^\*-linux$/);
8745 my $vserver;
8746 local $_;
8747 open(MTAB, "/etc/mtab");
8748 while(<MTAB>) {
8749         my ($dev, $mp) = split(/\s+/, $_);
8750         if ($mp eq "/" && $dev =~ /^\/dev\/hdv/) {
8751                 $vserver = 1;
8752                 last;
8753                 }
8754         }
8755 close(MTAB);
8756 return $vserver;
8757 }
8758
8759 =head2 running_in_xen
8760
8761 Returns 1 if Webmin is running inside a Xen instance, by looking
8762 at /proc/xen/capabilities.
8763
8764 =cut
8765 sub running_in_xen
8766 {
8767 return 0 if (!-r "/proc/xen/capabilities");
8768 my $cap = &read_file_contents("/proc/xen/capabilities");
8769 return $cap =~ /control_d/ ? 0 : 1;
8770 }
8771
8772 =head2 running_in_openvz
8773
8774 Returns 1 if Webmin is running inside an OpenVZ container, by looking
8775 at /proc/vz/veinfo for a non-zero line.
8776
8777 =cut
8778 sub running_in_openvz
8779 {
8780 return 0 if (!-r "/proc/vz/veinfo");
8781 my $lref = &read_file_lines("/proc/vz/veinfo", 1);
8782 return 0 if (!$lref || !@$lref);
8783 foreach my $l (@$lref) {
8784         $l =~ s/^\s+//;
8785         my @ll = split(/\s+/, $l);
8786         return 0 if ($ll[0] eq '0');
8787         }
8788 return 1;
8789 }
8790
8791 =head2 list_categories(&modules, [include-empty])
8792
8793 Returns a hash mapping category codes to names, including any custom-defined
8794 categories. The modules parameter must be an array ref of module hash objects,
8795 as returned by get_all_module_infos.
8796
8797 =cut
8798 sub list_categories
8799 {
8800 my ($mods, $empty) = @_;
8801 my (%cats, %catnames);
8802 &read_file("$config_directory/webmin.catnames", \%catnames);
8803 foreach my $o (@lang_order_list) {
8804         &read_file("$config_directory/webmin.catnames.$o", \%catnames);
8805         }
8806 if ($empty) {
8807         %cats = %catnames;
8808         }
8809 foreach my $m (@$mods) {
8810         my $c = $m->{'category'};
8811         next if ($cats{$c});
8812         if (defined($catnames{$c})) {
8813                 $cats{$c} = $catnames{$c};
8814                 }
8815         elsif ($text{"category_$c"}) {
8816                 $cats{$c} = $text{"category_$c"};
8817                 }
8818         else {
8819                 # try to get category name from module ..
8820                 my %mtext = &load_language($m->{'dir'});
8821                 if ($mtext{"category_$c"}) {
8822                         $cats{$c} = $mtext{"category_$c"};
8823                         }
8824                 else {
8825                         $c = $m->{'category'} = "";
8826                         $cats{$c} = $text{"category_$c"};
8827                         }
8828                 }
8829         }
8830 return %cats;
8831 }
8832
8833 =head2 is_readonly_mode
8834
8835 Returns 1 if the current user is in read-only mode, and thus all writes
8836 to files and command execution should fail.
8837
8838 =cut
8839 sub is_readonly_mode
8840 {
8841 if (!defined($main::readonly_mode_cache)) {
8842         my %gaccess = &get_module_acl(undef, "");
8843         $main::readonly_mode_cache = $gaccess{'readonly'} ? 1 : 0;
8844         }
8845 return $main::readonly_mode_cache;
8846 }
8847
8848 =head2 command_as_user(user, with-env?, command, ...)
8849
8850 Returns a command to execute some command as the given user, using the
8851 su statement. If on Linux, the /bin/sh shell is forced in case the user
8852 does not have a valid shell. If with-env is set to 1, the -s flag is added
8853 to the su command to read the user's .profile or .bashrc file.
8854
8855 =cut
8856 sub command_as_user
8857 {
8858 my ($user, $env, @args) = @_;
8859 my @uinfo = getpwnam($user);
8860 if ($uinfo[8] ne "/bin/sh" && $uinfo[8] !~ /\/bash$/) {
8861         # User shell doesn't appear to be valid
8862         if ($gconfig{'os_type'} =~ /-linux$/) {
8863                 # Use -s /bin/sh to force it
8864                 $shellarg = " -s /bin/sh";
8865                 }
8866         elsif ($gconfig{'os_type'} eq 'freebsd' ||
8867                $gconfig{'os_type'} eq 'solaris' &&
8868                 $gconfig{'os_version'} >= 11 ||
8869                $gconfig{'os_type'} eq 'macos') {
8870                 # Use -m and force /bin/sh
8871                 @args = ( "/bin/sh", "-c", quotemeta(join(" ", @args)) );
8872                 $shellarg = " -m";
8873                 }
8874         }
8875 my $rv = "su".($env ? " -" : "").$shellarg.
8876          " ".quotemeta($user)." -c ".quotemeta(join(" ", @args));
8877 return $rv;
8878 }
8879
8880 =head2 list_osdn_mirrors(project, file)
8881
8882 This function is now deprecated in favor of letting sourceforge just
8883 redirect to the best mirror, and now just returns their primary download URL.
8884
8885 =cut
8886 sub list_osdn_mirrors
8887 {
8888 my ($project, $file) = @_;
8889 return ( { 'url' => "http://downloads.sourceforge.net/$project/$file",
8890            'default' => 0,
8891            'mirror' => 'downloads' } );
8892 }
8893
8894 =head2 convert_osdn_url(url)
8895
8896 Given a URL like http://osdn.dl.sourceforge.net/sourceforge/project/file.zip
8897 or http://prdownloads.sourceforge.net/project/file.zip , convert it
8898 to a real URL on the sourceforge download redirector.
8899
8900 =cut
8901 sub convert_osdn_url
8902 {
8903 my ($url) = @_;
8904 if ($url =~ /^http:\/\/[^\.]+.dl.sourceforge.net\/sourceforge\/([^\/]+)\/(.*)$/ ||
8905     $url =~ /^http:\/\/prdownloads.sourceforge.net\/([^\/]+)\/(.*)$/) {
8906         # Always use the Sourceforge mail download URL, which does
8907         # a location-based redirect for us
8908         my ($project, $file) = ($1, $2);
8909         $url = "http://prdownloads.sourceforge.net/sourceforge/".
8910                "$project/$file";
8911         return wantarray ? ( $url, 0 ) : $url;
8912         }
8913 else {
8914         # Some other source .. don't change
8915         return wantarray ? ( $url, 2 ) : $url;
8916         }
8917 }
8918
8919 =head2 get_current_dir
8920
8921 Returns the directory the current process is running in.
8922
8923 =cut
8924 sub get_current_dir
8925 {
8926 my $out;
8927 if ($gconfig{'os_type'} eq 'windows') {
8928         # Use cd command
8929         $out = `cd`;
8930         }
8931 else {
8932         # Use pwd command
8933         $out = `pwd`;
8934         $out =~ s/\\/\//g;
8935         }
8936 $out =~ s/\r|\n//g;
8937 return $out;
8938 }
8939
8940 =head2 supports_users
8941
8942 Returns 1 if the current OS supports Unix user concepts and functions like
8943 su , getpw* and so on. This will be true on Linux and other Unixes, but false
8944 on Windows.
8945
8946 =cut
8947 sub supports_users
8948 {
8949 return $gconfig{'os_type'} ne 'windows';
8950 }
8951
8952 =head2 supports_symlinks
8953
8954 Returns 1 if the current OS supports symbolic and hard links. This will not
8955 be the case on Windows.
8956
8957 =cut
8958 sub supports_symlinks
8959 {
8960 return $gconfig{'os_type'} ne 'windows';
8961 }
8962
8963 =head2 quote_path(path)
8964
8965 Returns a path with safe quoting for the current operating system.
8966
8967 =cut
8968 sub quote_path
8969 {
8970 my ($path) = @_;
8971 if ($gconfig{'os_type'} eq 'windows' || $path =~ /^[a-z]:/i) {
8972         # Windows only supports "" style quoting
8973         return "\"$path\"";
8974         }
8975 else {
8976         return quotemeta($path);
8977         }
8978 }
8979
8980 =head2 get_windows_root
8981
8982 Returns the base windows system directory, like c:/windows.
8983
8984 =cut
8985 sub get_windows_root
8986 {
8987 if ($ENV{'SystemRoot'}) {
8988         my $rv = $ENV{'SystemRoot'};
8989         $rv =~ s/\\/\//g;
8990         return $rv;
8991         }
8992 else {
8993         return -d "c:/windows" ? "c:/windows" : "c:/winnt";
8994         }
8995 }
8996
8997 =head2 read_file_contents(file)
8998
8999 Given a filename, returns its complete contents as a string. Effectively
9000 the same as the Perl construct `cat file`.
9001
9002 =cut
9003 sub read_file_contents
9004 {
9005 &open_readfile(FILE, $_[0]) || return undef;
9006 local $/ = undef;
9007 my $rv = <FILE>;
9008 close(FILE);
9009 return $rv;
9010 }
9011
9012 =head2 unix_crypt(password, salt)
9013
9014 Performs Unix encryption on a password, using the built-in crypt function or
9015 the Crypt::UnixCrypt module if the former does not work. The salt parameter
9016 must be either an already-hashed password, or a two-character alpha-numeric
9017 string.
9018
9019 =cut
9020 sub unix_crypt
9021 {
9022 my ($pass, $salt) = @_;
9023 return "" if ($salt !~ /^[a-zA-Z0-9\.\/]{2}/);   # same as real crypt
9024 my $rv = eval "crypt(\$pass, \$salt)";
9025 my $err = $@;
9026 return $rv if ($rv && !$@);
9027 eval "use Crypt::UnixCrypt";
9028 if (!$@) {
9029         return Crypt::UnixCrypt::crypt($pass, $salt);
9030         }
9031 else {
9032         &error("Failed to encrypt password : $err");
9033         }
9034 }
9035
9036 =head2 split_quoted_string(string)
9037
9038 Given a string like I<foo "bar baz" quux>, returns the array :
9039 foo, bar baz, quux
9040
9041 =cut
9042 sub split_quoted_string
9043 {
9044 my ($str) = @_;
9045 my @rv;
9046 while($str =~ /^"([^"]*)"\s*([\000-\377]*)$/ ||
9047       $str =~ /^'([^']*)'\s*([\000-\377]*)$/ ||
9048       $str =~ /^(\S+)\s*([\000-\377]*)$/) {
9049         push(@rv, $1);
9050         $str = $2;
9051         }
9052 return @rv;
9053 }
9054
9055 =head2 write_to_http_cache(url, file|&data)
9056
9057 Updates the Webmin cache with the contents of the given file, possibly also
9058 clearing out old data. Mainly for internal use by http_download.
9059
9060 =cut
9061 sub write_to_http_cache
9062 {
9063 my ($url, $file) = @_;
9064 return 0 if (!$gconfig{'cache_size'});
9065
9066 # Don't cache downloads that look dynamic
9067 if ($url =~ /cgi-bin/ || $url =~ /\?/) {
9068         return 0;
9069         }
9070
9071 # Check if the current module should do caching
9072 if ($gconfig{'cache_mods'} =~ /^\!(.*)$/) {
9073         # Caching all except some modules
9074         my @mods = split(/\s+/, $1);
9075         return 0 if (&indexof(&get_module_name(), @mods) != -1);
9076         }
9077 elsif ($gconfig{'cache_mods'}) {
9078         # Only caching some modules
9079         my @mods = split(/\s+/, $gconfig{'cache_mods'});
9080         return 0 if (&indexof(&get_module_name(), @mods) == -1);
9081         }
9082
9083 # Work out the size
9084 my $size;
9085 if (ref($file)) {
9086         $size = length($$file);
9087         }
9088 else {
9089         my @st = stat($file);
9090         $size = $st[7];
9091         }
9092
9093 if ($size > $gconfig{'cache_size'}) {
9094         # Bigger than the whole cache - so don't save it
9095         return 0;
9096         }
9097 my $cfile = $url;
9098 $cfile =~ s/\//_/g;
9099 $cfile = "$main::http_cache_directory/$cfile";
9100
9101 # See how much we have cached currently, clearing old files
9102 my $total = 0;
9103 mkdir($main::http_cache_directory, 0700) if (!-d $main::http_cache_directory);
9104 opendir(CACHEDIR, $main::http_cache_directory);
9105 foreach my $f (readdir(CACHEDIR)) {
9106         next if ($f eq "." || $f eq "..");
9107         my $path = "$main::http_cache_directory/$f";
9108         my @st = stat($path);
9109         if ($gconfig{'cache_days'} &&
9110             time()-$st[9] > $gconfig{'cache_days'}*24*60*60) {
9111                 # This file is too old .. trash it
9112                 unlink($path);
9113                 }
9114         else {
9115                 $total += $st[7];
9116                 push(@cached, [ $path, $st[7], $st[9] ]);
9117                 }
9118         }
9119 closedir(CACHEDIR);
9120 @cached = sort { $a->[2] <=> $b->[2] } @cached;
9121 while($total+$size > $gconfig{'cache_size'} && @cached) {
9122         # Cache is too big .. delete some files until the new one will fit
9123         unlink($cached[0]->[0]);
9124         $total -= $cached[0]->[1];
9125         shift(@cached);
9126         }
9127
9128 # Finally, write out the new file
9129 if (ref($file)) {
9130         &open_tempfile(CACHEFILE, ">$cfile");
9131         &print_tempfile(CACHEFILE, $$file);
9132         &close_tempfile(CACHEFILE);
9133         }
9134 else {
9135         my ($ok, $err) = &copy_source_dest($file, $cfile);
9136         }
9137
9138 return 1;
9139 }
9140
9141 =head2 check_in_http_cache(url)
9142
9143 If some URL is in the cache and valid, return the filename for it. Mainly
9144 for internal use by http_download.
9145
9146 =cut
9147 sub check_in_http_cache
9148 {
9149 my ($url) = @_;
9150 return undef if (!$gconfig{'cache_size'});
9151
9152 # Check if the current module should do caching
9153 if ($gconfig{'cache_mods'} =~ /^\!(.*)$/) {
9154         # Caching all except some modules
9155         my @mods = split(/\s+/, $1);
9156         return 0 if (&indexof(&get_module_name(), @mods) != -1);
9157         }
9158 elsif ($gconfig{'cache_mods'}) {
9159         # Only caching some modules
9160         my @mods = split(/\s+/, $gconfig{'cache_mods'});
9161         return 0 if (&indexof(&get_module_name(), @mods) == -1);
9162         }
9163
9164 my $cfile = $url;
9165 $cfile =~ s/\//_/g;
9166 $cfile = "$main::http_cache_directory/$cfile";
9167 my @st = stat($cfile);
9168 return undef if (!@st || !$st[7]);
9169 if ($gconfig{'cache_days'} && time()-$st[9] > $gconfig{'cache_days'}*24*60*60) {
9170         # Too old!
9171         unlink($cfile);
9172         return undef;
9173         }
9174 open(TOUCH, ">>$cfile");        # Update the file time, to keep it in the cache
9175 close(TOUCH);
9176 return $cfile;
9177 }
9178
9179 =head2 supports_javascript
9180
9181 Returns 1 if the current browser is assumed to support javascript.
9182
9183 =cut
9184 sub supports_javascript
9185 {
9186 if (defined(&theme_supports_javascript)) {
9187         return &theme_supports_javascript();
9188         }
9189 return $ENV{'MOBILE_DEVICE'} ? 0 : 1;
9190 }
9191
9192 =head2 get_module_name
9193
9194 Returns the name of the Webmin module that called this function. For internal
9195 use only by other API functions.
9196
9197 =cut
9198 sub get_module_name
9199 {
9200 return &get_module_variable('$module_name');
9201 }
9202
9203 =head2 get_module_variable(name, [ref])
9204
9205 Returns the value of some variable which is set in the caller's context, if
9206 using the new WebminCore package. For internal use only.
9207
9208 =cut
9209 sub get_module_variable
9210 {
9211 my ($v, $wantref) = @_;
9212 my $slash = $wantref ? "\\" : "";
9213 my $thispkg = &web_libs_package();
9214 if ($thispkg eq 'WebminCore') {
9215         my ($vt, $vn) = split('', $v, 2);
9216         my $callpkg;
9217         for(my $i=0; ($callpkg) = caller($i); $i++) {
9218                 last if ($callpkg ne $thispkg);
9219                 }
9220         return eval "${slash}${vt}${callpkg}::${vn}";
9221         }
9222 return eval "${slash}${v}";
9223 }
9224
9225 =head2 clear_time_locale()
9226
9227 Temporarily force the locale to C, until reset_time_locale is called. This is
9228 useful if your code is going to call C<strftime> from the POSIX package, and
9229 you want to ensure that the output is in a consistent format.
9230
9231 =cut
9232 sub clear_time_locale
9233 {
9234 if ($main::clear_time_locale_count == 0) {
9235         eval {
9236                 use POSIX;
9237                 $main::clear_time_locale_old = POSIX::setlocale(POSIX::LC_TIME);
9238                 POSIX::setlocale(POSIX::LC_TIME, "C");
9239                 };
9240         }
9241 $main::clear_time_locale_count++;
9242 }
9243
9244 =head2 reset_time_locale()
9245
9246 Revert the locale to whatever it was before clear_time_locale was called
9247
9248 =cut
9249 sub reset_time_locale
9250 {
9251 if ($main::clear_time_locale_count == 1) {
9252         eval {
9253                 POSIX::setlocale(POSIX::LC_TIME, $main::clear_time_locale_old);
9254                 $main::clear_time_locale_old = undef;
9255                 };
9256         }
9257 $main::clear_time_locale_count--;
9258 }
9259
9260 =head2 callers_package(filehandle)
9261
9262 Convert a non-module filehandle like FOO to one qualified with the 
9263 caller's caller's package, like fsdump::FOO. For internal use only.
9264
9265 =cut
9266 sub callers_package
9267 {
9268 my ($fh) = @_;
9269 my $callpkg = (caller(1))[0];
9270 my $thispkg = &web_libs_package();
9271 if (!ref($fh) && $fh !~ /::/ &&
9272     $callpkg ne $thispkg && $thispkg eq 'WebminCore') {
9273         $fh = $callpkg."::".$fh;
9274         }
9275 return $fh;
9276 }
9277
9278 =head2 web_libs_package()
9279
9280 Returns the package this code is in. We can't always trust __PACKAGE__. For
9281 internal use only.
9282
9283 =cut
9284 sub web_libs_package
9285 {
9286 if ($called_from_webmin_core) {
9287         return "WebminCore";
9288         }
9289 return __PACKAGE__;
9290 }
9291
9292 =head2 get_userdb_string
9293
9294 Returns the URL-style string for connecting to the users and groups database
9295
9296 =cut
9297 sub get_userdb_string
9298 {
9299 return undef if ($main::no_miniserv_userdb);
9300 my %miniserv;
9301 &get_miniserv_config(\%miniserv);
9302 return $miniserv{'userdb'};
9303 }
9304
9305 =head2 connect_userdb(string)
9306
9307 Returns a handle for talking to a user database - may be a DBI or LDAP handle.
9308 On failure returns an error message string. In an array context, returns the
9309 protocol type too.
9310
9311 =cut
9312 sub connect_userdb
9313 {
9314 my ($str) = @_;
9315 my ($proto, $user, $pass, $host, $prefix, $args) = &split_userdb_string($str);
9316 if ($proto eq "mysql") {
9317         # Connect to MySQL with DBI
9318         my $drh = eval "use DBI; DBI->install_driver('mysql');";
9319         $drh || return $text{'sql_emysqldriver'};
9320         my ($host, $port) = split(/:/, $host);
9321         my $cstr = "database=$prefix;host=$host";
9322         $cstr .= ";port=$port" if ($port);
9323         my $dbh = $drh->connect($cstr, $user, $pass, { });
9324         $dbh || return &text('sql_emysqlconnect', $drh->errstr);
9325         return wantarray ? ($dbh, $proto, $prefix, $args) : $dbh;
9326         }
9327 elsif ($proto eq "postgresql") {
9328         # Connect to PostgreSQL with DBI
9329         my $drh = eval "use DBI; DBI->install_driver('Pg');";
9330         $drh || return $text{'sql_epostgresqldriver'};
9331         my ($host, $port) = split(/:/, $host);
9332         my $cstr = "dbname=$prefix;host=$host";
9333         $cstr .= ";port=$port" if ($port);
9334         my $dbh = $drh->connect($cstr, $user, $pass);
9335         $dbh || return &text('sql_epostgresqlconnect', $drh->errstr);
9336         return wantarray ? ($dbh, $proto, $prefix, $args) : $dbh;
9337         }
9338 elsif ($proto eq "ldap") {
9339         # Connect with perl LDAP module
9340         eval "use Net::LDAP";
9341         $@ && return $text{'sql_eldapdriver'};
9342         my ($host, $port) = split(/:/, $host);
9343         my $scheme = $args->{'scheme'} || 'ldap';
9344         if (!$port) {
9345                 $port = $scheme eq 'ldaps' ? 636 : 389;
9346                 }
9347         my $ldap = Net::LDAP->new($host,
9348                                   port => $port,
9349                                   'scheme' => $scheme);
9350         $ldap || return &text('sql_eldapconnect', $host);
9351         my $mesg;
9352         if ($args->{'tls'}) {
9353                 # Switch to TLS mode
9354                 eval { $mesg = $ldap->start_tls(); };
9355                 if ($@ || !$mesg || $mesg->code) {
9356                         return &text('sql_eldaptls',
9357                             $@ ? $@ : $mesg ? $mesg->error : "Unknown error");
9358                         }
9359                 }
9360         # Login to the server
9361         if ($pass) {
9362                 $mesg = $ldap->bind(dn => $user, password => $pass);
9363                 }
9364         else {
9365                 $mesg = $ldap->bind(dn => $user, anonymous => 1);
9366                 }
9367         if (!$mesg || $mesg->code) {
9368                 return &text('sql_eldaplogin', $user,
9369                              $mesg ? $mesg->error : "Unknown error");
9370                 }
9371         return wantarray ? ($ldap, $proto, $prefix, $args) : $ldap;
9372         }
9373 else {
9374         return "Unknown protocol $proto";
9375         }
9376 }
9377
9378 =head2 disconnect_userdb(string, &handle)
9379
9380 Closes a handle opened by connect_userdb
9381
9382 =cut
9383 sub disconnect_userdb
9384 {
9385 my ($str, $h) = @_;
9386 if ($str =~ /^(mysql|postgresql):/) {
9387         # DBI disconnnect
9388         if (!$h->{'AutoCommit'}) {
9389                 $h->commit();
9390                 }
9391         $h->disconnect();
9392         }
9393 elsif ($str =~ /^ldap:/) {
9394         # LDAP disconnect
9395         $h->unbind();
9396         $h->disconnect();
9397         }
9398 }
9399
9400 =head2 split_userdb_string(string)
9401
9402 Converts a string like mysql://user:pass@host/db into separate parts
9403
9404 =cut
9405 sub split_userdb_string
9406 {
9407 my ($str) = @_;
9408 if ($str =~ /^([a-z]+):\/\/([^:]*):([^\@]*)\@([a-z0-9\.\-\_]+)\/([^\?]+)(\?(.*))?$/) {
9409         my ($proto, $user, $pass, $host, $prefix, $argstr) =
9410                 ($1, $2, $3, $4, $5, $7);
9411         my %args = map { split(/=/, $_, 2) } split(/\&/, $argstr);
9412         return ($proto, $user, $pass, $host, $prefix, \%args);
9413         }
9414 return ( );
9415 }
9416
9417 $done_web_lib_funcs = 1;
9418
9419 1;