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