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