1 =head1 web-lib-funcs.pl
3 Common functions for Webmin CGI scripts. This file gets in-directly included
4 by all scripts that use web-lib.pl.
9 ui_print_header(undef, 'My Module', '');
10 print 'This is Webmin version ',get_webmin_version(),'<p>\n';
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);
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);
30 =head2 read_file(file, &hash, [&order], [lowercase], [split-char])
32 Fill the given hash reference with name=value pairs from a file. The required
35 =item file - The file to head, which must be text with each line like name=value
37 =item hash - The hash reference to add values read from the file to.
39 =item order - If given, an array reference to add names to in the order they were read
41 =item lowercase - If set to 1, names are converted to lower case
43 =item split-char - If set, names and values are split on this character instead of =
49 my $split = defined($_[4]) ? $_[4] : "=";
50 my $realfile = &translate_filename($_[0]);
51 &open_readfile(ARFILE, $_[0]) || return 0;
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);
60 $_[1]->{$_[3] ? lc($n) : $n} = $v;
61 push(@{$_[2]}, $n) if ($_[2]);
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]};
72 =head2 read_file_cached(file, &hash, [&order], [lowercase], [split-char])
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.
81 my $realfile = &translate_filename($_[0]);
82 if (defined($main::read_file_cache{$realfile})) {
84 %{$_[1]} = ( %{$_[1]}, %{$main::read_file_cache{$realfile}} );
87 elsif ($main::read_file_missing{$realfile}) {
88 # Doesn't exist, so don't re-try read
92 # Actually read the file
94 if (&read_file($_[0], \%d, $_[2], $_[3], $_[4])) {
95 %{$main::read_file_cache{$realfile}} = %d;
96 %{$_[1]} = ( %{$_[1]}, %d );
100 # Flag as non-existant
101 $main::read_file_missing{$realfile} = 1;
107 =head2 write_file(file, &hash, [join-char])
109 Write out the contents of a hash as name=value lines. The parameters are :
111 =item file - Full path to write to
113 =item hash - A hash reference containing names and values to output
115 =item join-char - If given, names and values are separated by this instead of =
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, $!));
131 foreach $k (keys %{$_[1]}) {
132 if (!exists($old{$k})) {
133 (print ARFILE $k,$join,$_[1]->{$k},"\n") ||
134 &error(&text("efilewrite", $realfile, $!));
137 &close_tempfile(ARFILE);
138 if (defined($main::read_file_cache{$realfile})) {
139 %{$main::read_file_cache{$realfile}} = %{$_[1]};
141 if (defined($main::read_file_missing{$realfile})) {
142 $main::read_file_missing{$realfile} = 0;
146 =head2 html_escape(string)
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.
158 $tmp =~ s/\"/"/g;
159 $tmp =~ s/\'/'/g;
164 =head2 quote_escape(string, [only-quote])
166 Converts ' and " characters in a string into HTML entities, and returns it.
167 Useful for outputing HTML tag values.
172 my ($tmp, $only) = @_;
173 if ($tmp !~ /\&[a-zA-Z]+;/ && $tmp !~ /\&#/) {
174 # convert &, unless it is part of &#nnn; or &foo;
175 $tmp =~ s/&([^#])/&$1/g;
177 $tmp =~ s/&$/&/g;
178 $tmp =~ s/\"/"/g if ($only eq '' || $only eq '"');
179 $tmp =~ s/\'/'/g if ($only eq '' || $only eq "'");
183 =head2 tempname([filename])
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.
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($<) :
203 if ($gconfig{'os_type'} eq 'windows' || $tmp_dir =~ /^[a-z]:/i) {
204 # On Windows system, just create temp dir if missing
206 mkdir($tmp_dir, 0755) ||
207 &error("Failed to create temp directory $tmp_dir : $!");
211 # On Unix systems, need to make sure temp dir is valid
213 while($tries++ < 10) {
214 my @st = lstat($tmp_dir);
215 last if ($st[4] == $< && (-d _) && ($st[2] & 0777) == 0755);
217 unlink($tmp_dir) || rmdir($tmp_dir) ||
218 system("/bin/rm -rf ".quotemeta($tmp_dir));
220 mkdir($tmp_dir, 0755) || next;
221 chown($<, $(, $tmp_dir);
222 chmod(0755, $tmp_dir);
225 my @st = lstat($tmp_dir);
226 &error("Failed to create temp directory $tmp_dir : uid=$st[4] mode=$st[2]");
230 if (defined($_[0]) && $_[0] !~ /\.\./) {
231 $rv = "$tmp_dir/$_[0]";
234 $main::tempfilecount++;
236 $rv = $tmp_dir."/".int(rand(1000000))."_".
237 $main::tempfilecount."_".$scriptname;
242 =head2 transname([filename])
244 Behaves exactly like tempname, but records the temp file for deletion when the
245 current Webmin script process exits.
250 my $rv = &tempname(@_);
251 push(@main::temporary_files, $rv);
255 =head2 trunc(string, maxlen)
257 Truncates a string to the shortest whole word less than or equal to the
258 given width. Useful for word wrapping.
263 if (length($_[0]) <= $_[1]) {
266 my $str = substr($_[0],0,$_[1]);
275 =head2 indexof(string, value, ...)
277 Returns the index of some value in an array of values, or -1 if it was not
283 for(my $i=1; $i <= $#_; $i++) {
284 if ($_[$i] eq $_[0]) { return $i - 1; }
289 =head2 indexoflc(string, value, ...)
291 Like indexof, but does a case-insensitive match
296 my $str = lc(shift(@_));
297 my @arr = map { lc($_) } @_;
298 return &indexof($str, @arr);
301 =head2 sysprint(handle, [string]+)
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.
309 my $fh = &callers_package($_[0]);
310 my $str = join('', @_[1..$#_]);
311 syswrite $fh, $str, length($str);
314 =head2 check_ipaddress(ip)
316 Check if some IPv4 address is properly formatted, returning 1 if so or 0 if not.
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;
328 =head2 check_ip6address(ip)
330 Check if some IPv6 address is properly formatted, and returns 1 if so.
335 my @blocks = split(/:/, $_[0]);
336 return 0 if (@blocks == 0 || @blocks > 8);
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].
342 my $where = index($blocks[$ib],"/");
345 my $b = substr($blocks[$ib],0,$where);
346 $m = substr($blocks[$ib],$where+1,length($blocks[$ib])-($where+1));
350 # The netmask must take its value in [0;128]
351 return 0 if ($m <0 || $m >128);
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 "::".
357 foreach $b (@blocks) {
358 return 0 if ($b ne "" && $b !~ /^[0-9a-f]{1,4}$/i);
359 $empty++ if ($b eq "");
361 return 0 if ($empty > 1 && !($_[0] =~ /^::/ && $empty == 2));
367 =head2 generate_icon(image, title, link, [href], [width], [height], [before-title], [after-title])
369 Prints HTML for an icon image. The parameters are :
371 =item image - URL for the image, like images/foo.gif
373 =item title - Text to appear under the icon
375 =item link - Optional destination for the icon's link
377 =item href - Other HTML attributes to be added to the <a href> for the link
379 =item width - Optional width of the icon
381 =item height - Optional height of the icon
383 =item before-title - HTML to appear before the title link, but which is not actually in the link
385 =item after-title - HTML to appear after the title link, but which is not actually in the link
390 &load_theme_library();
391 if (defined(&theme_generate_icon)) {
392 &theme_generate_icon(@_);
395 my $w = !defined($_[4]) ? "width=48" : $_[4] ? "width=$_[4]" : "";
396 my $h = !defined($_[5]) ? "height=48" : $_[5] ? "height=$_[5]" : "";
397 if ($tconfig{'noicons'}) {
399 print "$_[6]<a href=\"$_[2]\" $_[3]>$_[1]</a>$_[7]\n";
402 print "$_[6]$_[1]$_[7]\n";
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";
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";
420 Converts a string to a form ok for putting in a URL, using % escaping.
426 $rv =~ s/([^A-Za-z0-9])/sprintf("%%%2.2X", ord($1))/ge;
430 =head2 un_urlize(string)
432 Converts a URL-encoded string to it's original contents - the reverse of the
440 $rv =~ s/%(..)/pack("c",hex($1))/ge;
444 =head2 include(filename)
446 Read and output the contents of the given file.
452 open(INCLUDE, &translate_filename($_[0])) || return 0;
460 =head2 copydata(in-handle, out-handle)
462 Read from one file handle and write to another, until there is no more to read.
468 $in = &callers_package($in);
469 $out = &callers_package($out);
471 while(read($in, $buf, 1024) > 0) {
472 (print $out $buf) || return 0;
477 =head2 ReadParseMime([maximum], [&cbfunc, &cbargs])
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 :
482 =item maximum - If the number of bytes of input exceeds this number, stop reading and call error.
484 =item cbfunc - A function reference to call after reading each block of data.
486 =item cbargs - Additional parameters to the callback function.
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) {
498 &$cbfunc(0, $ENV{'CONTENT_LENGTH'}, $file, @$cbargs) if ($cbfunc);
500 <STDIN>; # skip first boundary
503 # Read section headers
507 $got += length($line);
508 &$cbfunc($got, $ENV{'CONTENT_LENGTH'}, @$cbargs) if ($cbfunc);
509 if ($max && $got > $max) {
514 if ($line =~ /^(\S+):\s*(.*)$/) {
515 $header{$lastheader = lc($1)} = $2;
517 elsif ($line =~ /^\s+(.*)$/) {
518 $header{$lastheader} .= $line;
522 # Parse out filename and type
523 if ($header{'content-disposition'} =~ /^form-data(.*)/) {
525 while ($rest =~ /([a-zA-Z]*)=\"([^\"]*)\"(.*)/) {
530 $foo = $name . "_$1";
537 &error($text{'readparse_cdheader'});
539 if ($header{'content-type'} =~ /^([^\s;]+)/) {
540 $foo = $name . "_content_type";
543 $file = $in{$name."_filename"};
546 $in{$name} .= "\0" if (defined($in{$name}));
549 $got += length($line);
550 &$cbfunc($got, $ENV{'CONTENT_LENGTH'}, $file, @$cbargs)
552 if ($max && $got > $max) {
553 #print STDERR "over limit of $max\n";
558 &$cbfunc(-1, $ENV{'CONTENT_LENGTH'}, $file, @$cbargs)
563 $ptline =~ s/[^a-zA-Z0-9\-]/\./g;
564 if (index($line, $boundary) != -1) { last; }
567 chop($in{$name}); chop($in{$name});
568 if (index($line,"$boundary--") != -1) { last; }
570 &$cbfunc(-1, $ENV{'CONTENT_LENGTH'}, $file, @$cbargs) if ($cbfunc);
573 =head2 ReadParse([&hash], [method], [noplus])
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
579 =item method - For use of this HTTP method, such as GET
581 =item noplus - Don't convert + in parameters to spaces.
586 my $a = $_[0] || \%in;
588 my $meth = $_[1] ? $_[1] : $ENV{'REQUEST_METHOD'};
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 : $!");
595 if ($ENV{'QUERY_STRING'}) {
596 if ($in) { $in .= "&".$ENV{'QUERY_STRING'}; }
597 else { $in = $ENV{'QUERY_STRING'}; }
599 @in = split(/\&/, $in);
600 foreach my $i (@in) {
601 my ($k, $v) = split(/=/, $i, 2);
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;
612 =head2 read_fully(fh, &buffer, length)
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.
621 my ($fh, $buf, $len) = @_;
622 $fh = &callers_package($fh);
625 my $r = read(STDIN, $$buf, $len-$got, $got);
632 =head2 read_parse_mime_callback(size, totalsize, upload-id)
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
639 sub read_parse_mime_callback
641 my ($size, $totalsize, $filename, $id) = @_;
642 return if ($gconfig{'no_upload_tracker'});
645 # Create the upload tracking directory - if running as non-root, this has to
646 # be under the user's home
649 my @uinfo = @remote_user_info ? @remote_user_info : getpwuid($<);
650 $vardir = "$uinfo[7]/.tmp";
653 $vardir = $ENV{'WEBMIN_VAR'};
656 &make_dir($vardir, 0755);
659 # Remove any upload.* files more than 1 hour old
660 if (!$main::read_parse_mime_callback_flushed) {
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");
671 $main::read_parse_mime_callback_flushed++;
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}) {
681 $main::read_parse_mime_callback_pc{$upfile} = $pc;
685 &open_tempfile(UPFILE, ">$upfile");
686 print UPFILE $size,"\n";
687 print UPFILE $totalsize,"\n";
688 print UPFILE $filename,"\n";
689 &close_tempfile(UPFILE);
692 =head2 read_parse_mime_javascript(upload-id, [&fields])
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.
698 sub read_parse_mime_javascript
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\");";
704 my $if = join(" || ", map { "typeof($_) != \"undefined\" && $_.value != \"\"" } @$fields);
705 return "onSubmit='if ($if) { $opener }'";
708 return "onSubmit='$opener'";
712 =head2 PrintHeader(charset)
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.
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";
727 if (defined($_[0])) {
728 print "Content-type: text/html; Charset=$_[0]\n\n";
731 print "Content-type: text/html\n\n";
735 =head2 header(title, image, [help], [config], [nomodule], [nowebmin], [rightside], [head-stuff], [body-stuff], [below])
737 Outputs a Webmin HTML page header with a title, including HTTP headers. The
740 =item title - The text to show at the top of the page
742 =item image - An image to show instead of the title text. This is typically left blank.
744 =item help - If set, this is the name of a help page that will be linked to in the title.
746 =item config - If set to 1, the title will contain a link to the module's config page.
748 =item nomodule - If set to 1, there will be no link in the title section to the module's index.
750 =item nowebmin - If set to 1, there will be no link in the title section to the Webmin index.
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.
754 =item head-stuff - HTML to be included in the <head> section of the page.
756 =item body-stuff - HTML attributes to be include in the <body> tag.
758 =item below - HTML to be displayed below the title. Typically this is used for application or server version information.
763 return if ($main::done_webmin_header++);
765 my $charset = defined($main::force_charset) ? $main::force_charset
767 &PrintHeader($charset);
768 &load_theme_library();
769 if (defined(&theme_header)) {
770 $module_name = &get_module_name();
774 print "<!doctype html public \"-//W3C//DTD HTML 3.2 Final//EN\">\n";
777 if (defined(&theme_prehead)) {
781 print "<meta http-equiv=\"Content-Type\" ",
782 "content=\"text/html; Charset="."e_escape($charset)."\">\n";
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);
790 print "$tconfig{'headhtml'}\n" if ($tconfig{'headhtml'});
791 if ($tconfig{'headinclude'}) {
792 print &read_file_contents(
793 "$theme_root_directory/$tconfig{'headinclude'}");
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'}"
804 my $dir = $current_lang_info->{'dir'} ? "dir=\"$current_lang_info->{'dir'}\""
806 print "<body bgcolor=#$bgcolor link=#$link vlink=#$link text=#$text ",
807 "$bgimage $tconfig{'inbody'} $dir $_[8]>\n";
808 if (defined(&theme_prebody)) {
811 my $hostname = &get_display_hostname();
812 my $version = &get_webmin_version();
813 my $prebody = $tconfig{'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;
821 if ($tconfig{'prebodyinclude'}) {
823 open(INC, "$theme_root_directory/$tconfig{'prebodyinclude'}");
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";
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";
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 &&
850 print "<a href='$gconfig{'webprefix'}/session_login.cgi?logout=1'>",
851 "$text{'main_logout'}</a><br>";
853 elsif ($gconfig{'gotoone'} && @avail == 1 && !$nolo) {
854 print "<a href=$gconfig{'webprefix'}/switch_user.cgi>",
855 "$text{'main_switch'}</a><br>";
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";
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";
869 if (ref($_[2]) eq "ARRAY" && !$ENV{'ANONYMOUS_USER'} &&
870 !$tconfig{'nohelp'}) {
871 print &hlink($text{'header_help'}, $_[2]->[0], $_[2]->[1]),
874 elsif (defined($_[2]) && !$ENV{'ANONYMOUS_USER'} &&
875 !$tconfig{'nohelp'}) {
876 print &hlink($text{'header_help'}, $_[2]),"<br>\n";
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";
890 # Title is a single image
891 print "<td id='headln2c' align=center width=70%>",
892 "<img alt=\"$_[0]\" src=\"$_[1]\"></td>\n";
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]);
904 print "<td id='headln2r' width=15% valign=top align=right>";
906 print "</td></tr></table>\n";
907 print $tconfig{'postheader'};
911 =head2 get_html_title(title)
913 Returns the full string to appear in the HTML <title> block.
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;
928 elsif ($gconfig{'sysinfo'} == 4 && $remote_user) {
929 $title = sprintf "%s on %s (%s %s)\n",
931 $os_type, $os_version;
936 if ($gconfig{'showlogin'} && $remote_user) {
937 $title = $remote_user.($title ? " : ".$title : "");
939 if ($gconfig{'showhost'}) {
940 $title = $host.($title ? " : ".$title : "");
945 =head2 get_html_framed_title
947 Returns the title text for a framed theme main page.
950 sub get_html_framed_title
953 my $os_type = $gconfig{'real_os_type'} || $gconfig{'os_type'};
954 my $os_version = $gconfig{'real_os_version'} || $gconfig{'os_version'};
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;
963 # Title like 'Webmin x.yy on hostname (Linux 6)'
964 if ($os_version eq "*") {
968 $ostr = "$os_type $os_version";
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 : "");
978 if ($gconfig{'showhost'}) {
979 $title = $host.($title ? " : ".$title : "");
985 =head2 get_html_status_line(text-only)
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.
991 sub get_html_status_line
994 if (($gconfig{'sysinfo'} != 0 || !$remote_user) && !$textonly) {
995 # Disabled in this mode
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"
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"));
1013 $line =~ s/\r|\n//g;
1014 return "<script language=JavaScript type=text/javascript>\n".
1015 "defaultStatus=\""."e_escape($line)."\";\n".
1020 =head2 popup_header([title], [head-stuff], [body-stuff], [no-body])
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
1026 =item title - Title text for the popup window.
1028 =item head-stuff - HTML to appear in the <head> section.
1030 =item body-stuff - HTML attributes to be include in the <body> tag.
1032 =item no-body - If set to 1, don't generate a body tag
1037 return if ($main::done_webmin_header++);
1039 my $charset = defined($main::force_charset) ? $main::force_charset
1041 &PrintHeader($charset);
1042 &load_theme_library();
1043 if (defined(&theme_popup_header)) {
1044 &theme_popup_header(@_);
1047 print "<!doctype html public \"-//W3C//DTD HTML 3.2 Final//EN\">\n";
1050 if (defined(&theme_popup_prehead)) {
1051 &theme_popup_prehead(@_);
1053 print "<title>$_[0]</title>\n";
1055 print "$tconfig{'headhtml'}\n" if ($tconfig{'headhtml'});
1056 if ($tconfig{'headinclude'}) {
1057 print &read_file_contents(
1058 "$theme_root_directory/$tconfig{'headinclude'}");
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'}"
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(@_);
1078 =head2 footer([page, name]+, [noendbody])
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 :
1084 footer('/', 'Webmin index', '', 'Module menu');
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;
1097 for(my $i=0; $i+1<@_; $i+=2) {
1099 if ($url ne '/' || !$tconfig{'noindex'}) {
1101 $url = "/?cat=$this_module_info{'category'}";
1103 elsif ($url eq '' && &get_module_name()) {
1104 $url = "/".&get_module_name()."/".
1105 $this_module_info{'index_link'};
1107 elsif ($url =~ /^\?/ && &get_module_name()) {
1108 $url = "/".&get_module_name()."/$url";
1110 $url = "$gconfig{'webprefix'}$url" if ($url =~ /^\//);
1112 print "<a href=\"$url\"><img alt=\"<-\" align=middle border=0 src=$gconfig{'webprefix'}/images/left.gif></a>\n";
1117 print " <a href=\"$url\">",&text('main_return', $_[$i+1]),"</a>\n";
1122 my $postbody = $tconfig{'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";
1136 if ($tconfig{'postbodyinclude'}) {
1138 open(INC, "$theme_root_directory/$tconfig{'postbodyinclude'}");
1144 if (defined(&theme_postbody)) {
1145 &theme_postbody(@_);
1147 print "</body></html>\n";
1151 =head2 popup_footer([no-body])
1153 Outputs html for a footer for a popup window, started by popup_header.
1158 &load_theme_library();
1159 if (defined(&theme_popup_footer)) {
1160 &theme_popup_footer(@_);
1169 =head2 load_theme_library
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.
1175 sub load_theme_library
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'};
1186 =head2 redirect(url)
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.
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'};
1200 if ($_[0] =~ /^(http|https|ftp|gopher):/) {
1201 # Absolute URL (like http://...)
1204 elsif ($_[0] =~ /^\//) {
1205 # Absolute path (like /foo/bar.cgi)
1206 $url = "$prot://$ENV{'SERVER_NAME'}$port$wp$_[0]";
1208 elsif ($ENV{'SCRIPT_NAME'} =~ /^(.*)\/[^\/]*$/) {
1209 # Relative URL (like foo.cgi)
1210 $url = "$prot://$ENV{'SERVER_NAME'}$port$wp$1/$_[0]";
1213 $url = "$prot://$ENV{'SERVER_NAME'}$port/$wp$_[0]";
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);
1222 print "Location: $url\n\n";
1226 =head2 kill_byname(name, signal)
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
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); }
1243 =head2 kill_byname_logged(name, signal)
1245 Like kill_byname, but also logs the killing.
1248 sub kill_byname_logged
1250 my @pids = &find_byname($_[0]);
1251 return scalar(@pids) if (&is_readonly_mode());
1252 if (@pids) { &kill_logged($_[1], @pids); return scalar(@pids); }
1256 =head2 find_byname(name)
1258 Finds processes searching for the given name in their command lines, and
1259 returns a list of matching PIDs.
1264 if ($gconfig{'os_type'} =~ /-linux$/ && -r "/proc/$$/cmdline") {
1265 # Linux with /proc filesystem .. use cmdline files, as this is
1266 # faster than forking
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]/) {
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();
1287 foreach my $p (@procs) {
1288 if ($p->{'args'} =~ /$_[0]/) {
1289 push(@pids, $p->{'pid'});
1292 @pids = grep { $_ != $$ } @pids;
1297 # Fall back to running a command
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;
1307 =head2 error([message]+)
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.
1317 my $msg = join("", @_);
1318 $msg =~ s/<[^>]*>//g;
1319 if (!$main::error_must_die) {
1320 print STDERR "Error: ",$msg,"\n";
1322 &load_theme_library();
1323 if ($main::error_must_die) {
1324 if ($gconfig{'error_stack'}) {
1325 print STDERR "Error: ",$msg,"\n";
1326 for(my $i=0; my @stack = caller($i); $i++) {
1327 print STDERR "File: $stack[1] Line: $stack[2] ",
1328 "Function: $stack[3]\n";
1333 elsif (!$ENV{'REQUEST_METHOD'}) {
1334 # Show text-only error
1335 print STDERR "$text{'error'}\n";
1336 print STDERR "-----\n";
1337 print STDERR ($main::whatfailed ? "$main::whatfailed : " : ""),
1339 print STDERR "-----\n";
1340 if ($gconfig{'error_stack'}) {
1342 print STDERR $text{'error_stack'},"\n";
1343 for(my $i=0; my @stack = caller($i); $i++) {
1344 print STDERR &text('error_stackline',
1345 $stack[1], $stack[2], $stack[3]),"\n";
1350 elsif (defined(&theme_error)) {
1354 &header($text{'error'}, "");
1356 print "<h3>",($main::whatfailed ? "$main::whatfailed : " : ""),
1358 if ($gconfig{'error_stack'}) {
1360 print "<h3>$text{'error_stack'}</h3>\n";
1362 print "<tr> <td><b>$text{'error_file'}</b></td> ",
1363 "<td><b>$text{'error_line'}</b></td> ",
1364 "<td><b>$text{'error_sub'}</b></td> </tr>\n";
1365 for($i=0; my @stack = caller($i); $i++) {
1367 print "<td>$stack[1]</td>\n";
1368 print "<td>$stack[2]</td>\n";
1369 print "<td>$stack[3]</td>\n";
1375 if ($ENV{'HTTP_REFERER'} && $main::completed_referers_check) {
1376 &footer($ENV{'HTTP_REFERER'}, $text{'error_previous'});
1382 &unlock_all_files();
1383 &cleanup_tempnames();
1387 =head2 popup_error([message]+)
1389 This function is almost identical to error, but displays the message with HTML
1390 headers suitable for a popup window.
1395 &load_theme_library();
1396 if ($main::error_must_die) {
1399 elsif (defined(&theme_popup_error)) {
1400 &theme_popup_error(@_);
1403 &popup_header($text{'error'}, "");
1404 print "<h3>",($main::whatfailed ? "$main::whatfailed : " : ""),@_,"</h3>\n";
1407 &unlock_all_files();
1408 &cleanup_tempnames();
1412 =head2 error_setup(message)
1414 Registers a message to be prepended to all error messages displayed by the
1420 $main::whatfailed = $_[0];
1423 =head2 wait_for(handle, regexp, regexp, ...)
1425 Reads from the input stream until one of the regexps matches, and returns the
1426 index of the matching regexp, or -1 if input ended before any matched. This is
1427 very useful for parsing the output of interactive programs, and can be used with
1428 a two-way pipe to feed input to a program in response to output matched by
1431 If the matching regexp contains bracketed sub-expressions, their values will
1432 be placed in the global array @matches, indexed starting from 1. You cannot
1433 use the Perl variables $1, $2 and so on to capture matches.
1437 $rv = wait_for($loginfh, "username:");
1439 error("Didn't get username prompt");
1441 print $loginfh "joe\n";
1442 $rv = wait_for($loginfh, "password:");
1444 error("Didn't get password prompt");
1446 print $loginfh "smeg\n";
1451 my ($c, $i, $sw, $rv, $ha);
1452 undef($wait_for_input);
1453 if ($wait_for_debug) {
1454 print STDERR "wait_for(",join(",", @_),")\n";
1456 $ha = &callers_package($_[0]);
1457 if ($wait_for_debug) {
1458 print STDERR "File handle=$ha fd=",fileno($ha),"\n";
1463 " if ((\$c = getc(\$ha)) eq \"\") { return -1; }\n".
1464 " \$wait_for_input .= \$c;\n";
1465 if ($wait_for_debug) {
1466 $codes .= "print STDERR \$wait_for_input,\"\\n\";";
1468 for($i=1; $i<@_; $i++) {
1469 $sw = $i>1 ? "elsif" : "if";
1470 $codes .= " $sw (\$wait_for_input =~ /$_[$i]/i) { \$hit = $i-1; }\n";
1473 " if (defined(\$hit)) {\n".
1474 " \@matches = (-1, \$1, \$2, \$3, \$4, \$5, \$6, \$7, \$8, \$9);\n".
1480 &error("wait_for error : $@\n");
1485 =head2 fast_wait_for(handle, string, string, ...)
1487 This function behaves very similar to wait_for (documented above), but instead
1488 of taking regular expressions as parameters, it takes strings. As soon as the
1489 input contains one of them, it will return the index of the matching string.
1490 If the input ends before any match, it returns -1.
1495 my ($inp, $maxlen, $ha, $i, $c, $inpl);
1496 for($i=1; $i<@_; $i++) {
1497 $maxlen = length($_[$i]) > $maxlen ? length($_[$i]) : $maxlen;
1501 if (($c = getc($ha)) eq "") {
1502 &error("fast_wait_for read error : $!");
1505 if (length($inp) > $maxlen) {
1506 $inp = substr($inp, length($inp)-$maxlen);
1508 $inpl = length($inp);
1509 for($i=1; $i<@_; $i++) {
1510 if ($_[$i] eq substr($inp, $inpl-length($_[$i]))) {
1517 =head2 has_command(command)
1519 Returns the full path to the executable if some command is in the path, or
1520 undef if not found. If the given command is already an absolute path and
1521 exists, then the same path will be returned.
1526 if (!$_[0]) { return undef; }
1527 if (exists($main::has_command_cache{$_[0]})) {
1528 return $main::has_command_cache{$_[0]};
1531 my $slash = $gconfig{'os_type'} eq 'windows' ? '\\' : '/';
1532 if ($_[0] =~ /^\// || $_[0] =~ /^[a-z]:[\\\/]/i) {
1533 # Absolute path given - just use it
1534 my $t = &translate_filename($_[0]);
1535 $rv = (-x $t && !-d _) ? $_[0] : undef;
1538 # Check each directory in the path
1540 foreach my $d (split($path_separator, $ENV{'PATH'})) {
1541 next if ($donedir{$d}++);
1542 $d =~ s/$slash$// if ($d ne $slash);
1543 my $t = &translate_filename("$d/$_[0]");
1544 if (-x $t && !-d _) {
1545 $rv = $d.$slash.$_[0];
1548 if ($gconfig{'os_type'} eq 'windows') {
1549 foreach my $sfx (".exe", ".com", ".bat") {
1550 my $t = &translate_filename("$d/$_[0]").$sfx;
1551 if (-r $t && !-d _) {
1552 $rv = $d.$slash.$_[0].$sfx;
1559 $main::has_command_cache{$_[0]} = $rv;
1563 =head2 make_date(seconds, [date-only], [fmt])
1565 Converts a Unix date/time in seconds to a human-readable form, by default
1566 formatted like dd/mmm/yyyy hh:mm:ss. Parameters are :
1568 =item seconds - Unix time is seconds to convert.
1570 =item date-only - If set to 1, exclude the time from the returned string.
1572 =item fmt - Optional, one of dd/mon/yyyy, dd/mm/yyyy, mm/dd/yyyy or yyyy/mm/dd
1577 my ($secs, $only, $fmt) = @_;
1578 my @tm = localtime($secs);
1581 $fmt = $gconfig{'dateformat'} || 'dd/mon/yyyy';
1583 if ($fmt eq 'dd/mon/yyyy') {
1584 $date = sprintf "%2.2d/%s/%4.4d",
1585 $tm[3], $text{"smonth_".($tm[4]+1)}, $tm[5]+1900;
1587 elsif ($fmt eq 'dd/mm/yyyy') {
1588 $date = sprintf "%2.2d/%2.2d/%4.4d", $tm[3], $tm[4]+1, $tm[5]+1900;
1590 elsif ($fmt eq 'mm/dd/yyyy') {
1591 $date = sprintf "%2.2d/%2.2d/%4.4d", $tm[4]+1, $tm[3], $tm[5]+1900;
1593 elsif ($fmt eq 'yyyy/mm/dd') {
1594 $date = sprintf "%4.4d/%2.2d/%2.2d", $tm[5]+1900, $tm[4]+1, $tm[3];
1596 elsif ($fmt eq 'd. mon yyyy') {
1597 $date = sprintf "%d. %s %4.4d",
1598 $tm[3], $text{"smonth_".($tm[4]+1)}, $tm[5]+1900;
1600 elsif ($fmt eq 'dd.mm.yyyy') {
1601 $date = sprintf "%2.2d.%2.2d.%4.4d", $tm[3], $tm[4]+1, $tm[5]+1900;
1603 elsif ($fmt eq 'yyyy-mm-dd') {
1604 $date = sprintf "%4.4d-%2.2d-%2.2d", $tm[5]+1900, $tm[4]+1, $tm[3];
1607 $date .= sprintf " %2.2d:%2.2d", $tm[2], $tm[1];
1612 =head2 file_chooser_button(input, type, [form], [chroot], [addmode])
1614 Return HTML for a button that pops up a file chooser when clicked, and places
1615 the selected filename into another HTML field. The parameters are :
1617 =item input - Name of the form field to store the filename in.
1619 =item type - 0 for file or directory chooser, or 1 for directory only.
1621 =item form - Index of the form containing the button.
1623 =item chroot - If set to 1, the chooser will be limited to this directory.
1625 =item addmode - If set to 1, the selected filename will be appended to the text box instead of replacing it's contents.
1628 sub file_chooser_button
1630 return &theme_file_chooser_button(@_)
1631 if (defined(&theme_file_chooser_button));
1632 my $form = defined($_[2]) ? $_[2] : 0;
1633 my $chroot = defined($_[3]) ? $_[3] : "/";
1634 my $add = int($_[4]);
1635 my ($w, $h) = (400, 300);
1636 if ($gconfig{'db_sizefile'}) {
1637 ($w, $h) = split(/x/, $gconfig{'db_sizefile'});
1639 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 =head2 popup_window_button(url, width, height, scrollbars?, &field-mappings)
1644 Returns HTML for a button that will popup a chooser window of some kind. The
1647 =item url - Base URL of the popup window's contents
1649 =item width - Width of the window in pixels
1651 =item height - Height in pixels
1653 =item scrollbars - Set to 1 if the window should have scrollbars
1655 The field-mappings parameter is an array ref of array refs containing
1657 =item - Attribute to assign field to in the popup window
1659 =item - Form field name
1661 =item - CGI parameter to URL for value, if any
1664 sub popup_window_button
1666 return &theme_popup_window_button(@_) if (defined(&theme_popup_window_button));
1667 my ($url, $w, $h, $scroll, $fields) = @_;
1668 my $scrollyn = $scroll ? "yes" : "no";
1669 my $rv = "<input type=button onClick='";
1670 foreach my $m (@$fields) {
1671 $rv .= "$m->[0] = form.$m->[1]; ";
1673 my $sep = $url =~ /\?/ ? "&" : "?";
1674 $rv .= "chooser = window.open(\"$url\"";
1675 foreach my $m (@$fields) {
1677 $rv .= "+\"$sep$m->[2]=\"+escape($m->[0].value)";
1681 $rv .= ", \"chooser\", \"toolbar=no,menubar=no,scrollbars=$scrollyn,resizable=yes,width=$w,height=$h\"); ";
1682 foreach my $m (@$fields) {
1683 $rv .= "chooser.$m->[0] = $m->[0]; ";
1684 $rv .= "window.$m->[0] = $m->[0]; ";
1686 $rv .= "' value=\"...\">";
1690 =head2 read_acl(&user-module-hash, &user-list-hash)
1692 Reads the Webmin acl file into the given hash references. The first is indexed
1693 by a combined key of username,module , with the value being set to 1 when
1694 the user has access to that module. The second is indexed by username, with
1695 the value being an array ref of allowed modules.
1697 This function is deprecated in favour of foreign_available, which performs a
1698 more comprehensive check of module availability.
1703 if (!%main::acl_hash_cache) {
1705 open(ACL, &acl_filename());
1707 if (/^([^:]+):\s*(.*)/) {
1709 my @mods = split(/\s+/, $2);
1710 foreach my $m (@mods) {
1711 $main::acl_hash_cache{$user,$m}++;
1713 $main::acl_array_cache{$user} = \@mods;
1718 if ($_[0]) { %{$_[0]} = %main::acl_hash_cache; }
1719 if ($_[1]) { %{$_[1]} = %main::acl_array_cache; }
1724 Returns the file containing the webmin ACL, which is usually
1725 /etc/webmin/webmin.acl.
1730 return "$config_directory/webmin.acl";
1735 Does nothing, but kept around for compatability.
1742 =head2 get_miniserv_config(&hash)
1744 Reads the Webmin webserver's (miniserv.pl) configuration file, usually located
1745 at /etc/webmin/miniserv.conf, and stores its names and values in the given
1749 sub get_miniserv_config
1751 return &read_file_cached(
1752 $ENV{'MINISERV_CONFIG'} || "$config_directory/miniserv.conf", $_[0]);
1755 =head2 put_miniserv_config(&hash)
1757 Writes out the Webmin webserver configuration file from the contents of
1758 the given hash ref. This should be initially populated by get_miniserv_config,
1761 get_miniserv_config(\%miniserv);
1762 $miniserv{'port'} = 10005;
1763 put_miniserv_config(\%miniserv);
1767 sub put_miniserv_config
1769 &write_file($ENV{'MINISERV_CONFIG'} || "$config_directory/miniserv.conf",
1773 =head2 restart_miniserv([nowait])
1775 Kill the old miniserv process and re-start it, then optionally waits for
1776 it to restart. This will apply all configuration settings.
1779 sub restart_miniserv
1782 return undef if (&is_readonly_mode());
1784 &get_miniserv_config(\%miniserv) || return;
1787 if ($gconfig{'os_type'} ne 'windows') {
1788 # On Unix systems, we can restart with a signal
1789 my ($pid, $addr, $i);
1790 $miniserv{'inetd'} && return;
1791 my @oldst = stat($miniserv{'pidfile'});
1792 $pid = $ENV{'MINISERV_PID'};
1794 open(PID, $miniserv{'pidfile'}) ||
1795 &error("Failed to open PID file $miniserv{'pidfile'}");
1798 $pid || &error("Invalid PID file $miniserv{'pidfile'}");
1801 # Just signal miniserv to restart
1802 &kill_logged('HUP', $pid) || &error("Incorrect Webmin PID $pid");
1804 # Wait till new PID is written, indicating a restart
1805 for($i=0; $i<60; $i++) {
1807 my @newst = stat($miniserv{'pidfile'});
1808 last if ($newst[9] != $oldst[9]);
1810 $i < 60 || &error("Webmin server did not write new PID file");
1812 ## Totally kill the process and re-run it
1813 #$SIG{'TERM'} = 'IGNORE';
1814 #&kill_logged('TERM', $pid);
1815 #&system_logged("$config_directory/start >/dev/null 2>&1 </dev/null");
1818 # On Windows, we need to use the flag file
1819 open(TOUCH, ">$miniserv{'restartflag'}");
1824 # wait for miniserv to come back up
1825 $addr = inet_aton($miniserv{'bind'} ? $miniserv{'bind'} : "127.0.0.1");
1827 for($i=0; $i<20; $i++) {
1829 socket(STEST, PF_INET, SOCK_STREAM, getprotobyname("tcp"));
1830 my $rv = connect(STEST,
1831 pack_sockaddr_in($miniserv{'port'}, $addr));
1833 last if ($rv && ++$ok >= 2);
1835 $i < 20 || &error("Failed to restart Webmin server!");
1839 =head2 reload_miniserv
1841 Sends a USR1 signal to the miniserv process, telling it to read-read it's
1842 configuration files. Not all changes will be applied though, such as the
1843 IP addresses and ports to accept connections on.
1848 return undef if (&is_readonly_mode());
1850 &get_miniserv_config(\%miniserv) || return;
1852 if ($gconfig{'os_type'} ne 'windows') {
1853 # Send a USR1 signal to re-read the config
1854 my ($pid, $addr, $i);
1855 $miniserv{'inetd'} && return;
1856 $pid = $ENV{'MINISERV_PID'};
1858 open(PID, $miniserv{'pidfile'}) ||
1859 &error("Failed to open PID file $miniserv{'pidfile'}");
1862 $pid || &error("Invalid PID file $miniserv{'pidfile'}");
1864 &kill_logged('USR1', $pid) || &error("Incorrect Webmin PID $pid");
1866 # Make sure this didn't kill Webmin!
1868 if (!kill(0, $pid)) {
1869 print STDERR "USR1 signal killed Webmin - restarting\n";
1870 &system_logged("$config_directory/start >/dev/null 2>&1 </dev/null");
1874 # On Windows, we need to use the flag file
1875 open(TOUCH, ">$miniserv{'reloadflag'}");
1880 =head2 check_os_support(&minfo, [os-type, os-version], [api-only])
1882 Returns 1 if some module is supported on the current operating system, or the
1883 OS supplies as parameters. The parameters are :
1885 =item minfo - A hash ref of module information, as returned by get_module_info
1887 =item os-type - The Webmin OS code to use instead of the system's real OS, such as redhat-linux
1889 =item os-version - The Webmin OS version to use, such as 13.0
1891 =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.
1894 sub check_os_support
1896 my $oss = $_[0]->{'os_support'};
1897 if ($_[3] && $oss && $_[0]->{'api_os_support'}) {
1898 # May provide usable API
1899 $oss .= " ".$_[0]->{'api_os_support'};
1901 if ($_[0]->{'nozone'} && &running_in_zone()) {
1902 # Not supported in a Solaris Zone
1905 if ($_[0]->{'novserver'} && &running_in_vserver()) {
1906 # Not supported in a Linux Vserver
1909 if ($_[0]->{'noopenvz'} && &running_in_openvz()) {
1910 # Not supported in an OpenVZ container
1913 return 1 if (!$oss || $oss eq '*');
1914 my $osver = $_[2] || $gconfig{'os_version'};
1915 my $ostype = $_[1] || $gconfig{'os_type'};
1918 my ($os, $ver, $codes);
1919 my ($neg) = ($oss =~ s/^!//); # starts with !
1920 $anyneg++ if ($neg);
1921 if ($oss =~ /^([^\/\s]+)\/([^\{\s]+)\{([^\}]*)\}\s*(.*)$/) {
1923 $os = $1; $ver = $2; $codes = $3; $oss = $4;
1925 elsif ($oss =~ /^([^\/\s]+)\/([^\/\s]+)\s*(.*)$/) {
1927 $os = $1; $ver = $2; $oss = $3;
1929 elsif ($oss =~ /^([^\{\s]+)\{([^\}]*)\}\s*(.*)$/) {
1931 $os = $1; $codes = $2; $oss = $3;
1933 elsif ($oss =~ /^\{([^\}]*)\}\s*(.*)$/) {
1935 $codes = $1; $oss = $2;
1937 elsif ($oss =~ /^(\S+)\s*(.*)$/) {
1939 $os = $1; $oss = $2;
1942 next if ($os && !($os eq $ostype ||
1943 $ostype =~ /^(\S+)-(\S+)$/ && $os eq "*-$2"));
1944 if ($ver =~ /^([0-9\.]+)\-([0-9\.]+)$/) {
1945 next if ($osver < $1 || $osver > $2);
1947 elsif ($ver =~ /^([0-9\.]+)\-\*$/) {
1948 next if ($osver < $1);
1950 elsif ($ver =~ /^\*\-([0-9\.]+)$/) {
1951 next if ($osver > $1);
1954 next if ($ver ne $osver);
1956 next if ($codes && !eval $codes);
1962 =head2 http_download(host, port, page, destfile, [&error], [&callback], [sslmode], [user, pass], [timeout], [osdn-convert], [no-cache], [&headers])
1964 Downloads data from a HTTP url to a local file or string. The parameters are :
1966 =item host - The hostname part of the URL, such as www.google.com
1968 =item port - The HTTP port number, such as 80
1970 =item page - The filename part of the URL, like /index.html
1972 =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.
1974 =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.
1976 =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.
1978 =item sslmode - If set to 1, an HTTPS connection is used instead of HTTP.
1980 =item user - If set, HTTP authentication is done with this username.
1982 =item pass - The HTTP password to use with the username above.
1984 =item timeout - A timeout in seconds to wait for the TCP connection to be established before failing.
1986 =item osdn-convert - If set to 1, URL for downloads from sourceforge are converted to use an appropriate mirror site.
1988 =item no-cache - If set to 1, Webmin's internal caching for this URL is disabled.
1990 =item headers - If set to a hash ref of additional HTTP headers, they will be added to the request.
1995 my ($host, $port, $page, $dest, $error, $cbfunc, $ssl, $user, $pass,
1996 $timeout, $osdn, $nocache, $headers) = @_;
1997 if ($gconfig{'debug_what_net'}) {
1998 &webmin_debug_log('HTTP', "host=$host port=$port page=$page ssl=$ssl".
1999 ($user ? " user=$user pass=$pass" : "").
2000 (ref($dest) ? "" : " dest=$dest"));
2003 # Convert OSDN URL first
2004 my $prot = $ssl ? "https://" : "http://";
2005 my $portstr = $ssl && $port == 443 ||
2006 !$ssl && $port == 80 ? "" : ":$port";
2007 ($host, $port, $page, $ssl) = &parse_http_url(
2008 &convert_osdn_url($prot.$host.$portstr.$page));
2011 # Check if we already have cached the URL
2012 my $url = ($ssl ? "https://" : "http://").$host.":".$port.$page;
2013 my $cfile = &check_in_http_cache($url);
2014 if ($cfile && !$nocache) {
2015 # Yes! Copy to dest file or variable
2016 &$cbfunc(6, $url) if ($cbfunc);
2018 &open_readfile(CACHEFILE, $cfile);
2020 $$dest = <CACHEFILE>;
2024 ©_source_dest($cfile, $dest);
2031 push(@headers, [ "Host", $host ]);
2032 push(@headers, [ "User-agent", "Webmin" ]);
2033 push(@headers, [ "Accept-language", "en" ]);
2035 my $auth = &encode_base64("$user:$pass");
2036 $auth =~ tr/\r\n//d;
2037 push(@headers, [ "Authorization", "Basic $auth" ]);
2039 foreach my $hname (keys %$headers) {
2040 push(@headers, [ $hname, $headers->{$hname} ]);
2043 # Actually download it
2044 $main::download_timed_out = undef;
2045 local $SIG{ALRM} = \&download_timeout;
2046 alarm($timeout || 60);
2047 my $h = &make_http_connection($host, $port, $ssl, "GET", $page, \@headers);
2049 $h = $main::download_timed_out if ($main::download_timed_out);
2051 if ($error) { $$error = $h; return; }
2052 else { &error($h); }
2054 &complete_http_download($h, $dest, $error, $cbfunc, $osdn, $host, $port,
2055 $headers, $ssl, $nocache);
2056 if ((!$error || !$$error) && !$nocache) {
2057 &write_to_http_cache($url, $dest);
2061 =head2 complete_http_download(handle, destfile, [&error], [&callback], [osdn], [oldhost], [oldport], [&send-headers], [old-ssl], [no-cache])
2063 Do a HTTP download, after the headers have been sent. For internal use only,
2064 typically called by http_download.
2067 sub complete_http_download
2069 local ($line, %header, @headers, $s); # Kept local so that callback funcs
2075 ($line = &read_http_connection($_[0])) =~ tr/\r\n//d;
2076 if ($line !~ /^HTTP\/1\..\s+(200|30[0-9])(\s+|$)/) {
2078 if ($_[2]) { ${$_[2]} = $line; return; }
2079 else { &error("Download failed : $line"); }
2082 &$cbfunc(1, $rcode >= 300 && $rcode < 400 ? 1 : 0)
2085 $line = &read_http_connection($_[0]);
2086 $line =~ tr/\r\n//d;
2087 $line =~ /^(\S+):\s+(.*)$/ || last;
2088 $header{lc($1)} = $2;
2089 push(@headers, [ lc($1), $2 ]);
2092 if ($main::download_timed_out) {
2093 if ($_[2]) { ${$_[2]} = $main::download_timed_out; return 0; }
2094 else { &error($main::download_timed_out); }
2096 &$cbfunc(2, $header{'content-length'}) if ($cbfunc);
2097 if ($rcode >= 300 && $rcode < 400) {
2098 # follow the redirect
2099 &$cbfunc(5, $header{'location'}) if ($cbfunc);
2100 my ($host, $port, $page, $ssl);
2101 if ($header{'location'} =~ /^(http|https):\/\/([^:]+):(\d+)(\/.*)?$/) {
2102 $ssl = $1 eq 'https' ? 1 : 0;
2103 $host = $2; $port = $3; $page = $4 || "/";
2105 elsif ($header{'location'} =~ /^(http|https):\/\/([^:\/]+)(\/.*)?$/) {
2106 $ssl = $1 eq 'https' ? 1 : 0;
2107 $host = $2; $port = 80; $page = $3 || "/";
2109 elsif ($header{'location'} =~ /^\// && $_[5]) {
2110 # Relative to same server
2114 $page = $header{'location'};
2116 elsif ($header{'location'}) {
2117 # Assume relative to same dir .. not handled
2118 if ($_[2]) { ${$_[2]} = "Invalid Location header $header{'location'}"; return; }
2119 else { &error("Invalid Location header $header{'location'}"); }
2122 if ($_[2]) { ${$_[2]} = "Missing Location header"; return; }
2123 else { &error("Missing Location header"); }
2126 ($page, $params) = split(/\?/, $page);
2128 $page .= "?".$params if (defined($params));
2129 &http_download($host, $port, $page, $_[1], $_[2], $cbfunc, $ssl,
2130 undef, undef, undef, $_[4], $_[9], $_[7]);
2135 # Append to a variable
2136 while(defined($buf = &read_http_connection($_[0], 1024))) {
2138 &$cbfunc(3, length(${$_[1]})) if ($cbfunc);
2144 if (!&open_tempfile(PFILE, ">$_[1]", 1)) {
2145 if ($_[2]) { ${$_[2]} = "Failed to write to $_[1] : $!"; return; }
2146 else { &error("Failed to write to $_[1] : $!"); }
2148 binmode(PFILE); # For windows
2149 while(defined($buf = &read_http_connection($_[0], 1024))) {
2150 &print_tempfile(PFILE, $buf);
2151 $got += length($buf);
2152 &$cbfunc(3, $got) if ($cbfunc);
2154 &close_tempfile(PFILE);
2155 if ($header{'content-length'} &&
2156 $got != $header{'content-length'}) {
2157 if ($_[2]) { ${$_[2]} = "Download incomplete"; return; }
2158 else { &error("Download incomplete"); }
2161 &$cbfunc(4) if ($cbfunc);
2163 &close_http_connection($_[0]);
2167 =head2 ftp_download(host, file, destfile, [&error], [&callback], [user, pass], [port])
2169 Download data from an FTP site to a local file. The parameters are :
2171 =item host - FTP server hostname
2173 =item file - File on the FTP server to download
2175 =item destfile - File on the Webmin system to download data to
2177 =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.
2179 =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.
2181 =item user - Username to login to the FTP server as. If missing, Webmin will login as anonymous.
2183 =item pass - Password for the username above.
2185 =item port - FTP server port number, which defaults to 21 if not set.
2190 my ($host, $file, $dest, $error, $cbfunc, $user, $pass, $port) = @_;
2192 if ($gconfig{'debug_what_net'}) {
2193 &webmin_debug_log('FTP', "host=$host port=$port file=$file".
2194 ($user ? " user=$user pass=$pass" : "").
2195 (ref($dest) ? "" : " dest=$dest"));
2199 if (&is_readonly_mode()) {
2200 if ($_[3]) { ${$_[3]} = "FTP connections not allowed in readonly mode";
2202 else { &error("FTP connections not allowed in readonly mode"); }
2205 # Check if we already have cached the URL
2206 my $url = "ftp://".$host.$file;
2207 my $cfile = &check_in_http_cache($url);
2209 # Yes! Copy to dest file or variable
2210 &$cbfunc(6, $url) if ($cbfunc);
2212 &open_readfile(CACHEFILE, $cfile);
2214 $$dest = <CACHEFILE>;
2218 ©_source_dest($cfile, $dest);
2223 # Actually download it
2224 $main::download_timed_out = undef;
2225 local $SIG{ALRM} = \&download_timeout;
2228 if ($gconfig{'ftp_proxy'} =~ /^http:\/\/(\S+):(\d+)/ && !&no_proxy($_[0])) {
2229 # download through http-style proxy
2231 if (&open_socket($1, $2, "SOCK", \$error)) {
2233 if ($main::download_timed_out) {
2235 if ($_[3]) { ${$_[3]} = $main::download_timed_out; return 0; }
2236 else { &error($main::download_timed_out); }
2238 my $esc = $_[1]; $esc =~ s/ /%20/g;
2239 my $up = "$_[5]:$_[6]\@" if ($_[5]);
2240 my $portstr = $port == 21 ? "" : ":$port";
2241 print SOCK "GET ftp://$up$_[0]$portstr$esc HTTP/1.0\r\n";
2242 print SOCK "User-agent: Webmin\r\n";
2243 if ($gconfig{'proxy_user'}) {
2244 my $auth = &encode_base64(
2245 "$gconfig{'proxy_user'}:$gconfig{'proxy_pass'}");
2246 $auth =~ tr/\r\n//d;
2247 print SOCK "Proxy-Authorization: Basic $auth\r\n";
2250 &complete_http_download({ 'fh' => "SOCK" }, $_[2], $_[3], $_[4]);
2253 elsif (!$gconfig{'proxy_fallback'}) {
2255 if ($error) { $$error = $main::download_timed_out; return 0; }
2256 else { &error($main::download_timed_out); }
2261 # connect to host and login with real FTP protocol
2262 &open_socket($_[0], $port, "SOCK", $_[3]) || return 0;
2264 if ($main::download_timed_out) {
2265 if ($_[3]) { ${$_[3]} = $main::download_timed_out; return 0; }
2266 else { &error($main::download_timed_out); }
2268 &ftp_command("", 2, $_[3]) || return 0;
2270 # Login as supplied user
2271 my @urv = &ftp_command("USER $_[5]", [ 2, 3 ], $_[3]);
2273 if (int($urv[1]/100) == 3) {
2274 &ftp_command("PASS $_[6]", 2, $_[3]) || return 0;
2278 # Login as anonymous
2279 my @urv = &ftp_command("USER anonymous", [ 2, 3 ], $_[3]);
2281 if (int($urv[1]/100) == 3) {
2282 &ftp_command("PASS root\@".&get_system_hostname(), 2,
2286 &$cbfunc(1, 0) if ($cbfunc);
2289 # get the file size and tell the callback
2290 &ftp_command("TYPE I", 2, $_[3]) || return 0;
2291 my $size = &ftp_command("SIZE $_[1]", 2, $_[3]);
2292 defined($size) || return 0;
2294 &$cbfunc(2, int($size));
2298 my $pasv = &ftp_command("PASV", 2, $_[3]);
2299 defined($pasv) || return 0;
2300 $pasv =~ /\(([0-9,]+)\)/;
2301 @n = split(/,/ , $1);
2302 &open_socket("$n[0].$n[1].$n[2].$n[3]",
2303 $n[4]*256 + $n[5], "CON", $_[3]) || return 0;
2304 &ftp_command("RETR $_[1]", 1, $_[3]) || return 0;
2308 &open_tempfile(PFILE, ">$_[2]", 1);
2309 while(read(CON, $buf, 1024) > 0) {
2310 &print_tempfile(PFILE, $buf);
2311 $got += length($buf);
2312 &$cbfunc(3, $got) if ($cbfunc);
2314 &close_tempfile(PFILE);
2316 if ($got != $size) {
2317 if ($_[3]) { ${$_[3]} = "Download incomplete"; return 0; }
2318 else { &error("Download incomplete"); }
2320 &$cbfunc(4) if ($cbfunc);
2322 &ftp_command("", 2, $_[3]) || return 0;
2326 &ftp_command("QUIT", 2, $_[3]) || return 0;
2330 &write_to_http_cache($url, $dest);
2334 =head2 ftp_upload(host, file, srcfile, [&error], [&callback], [user, pass], [port])
2336 Upload data from a local file to an FTP site. The parameters are :
2338 =item host - FTP server hostname
2340 =item file - File on the FTP server to write to
2342 =item srcfile - File on the Webmin system to upload data from
2344 =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.
2346 =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.
2348 =item user - Username to login to the FTP server as. If missing, Webmin will login as anonymous.
2350 =item pass - Password for the username above.
2352 =item port - FTP server port number, which defaults to 21 if not set.
2359 if (&is_readonly_mode()) {
2360 if ($_[3]) { ${$_[3]} = "FTP connections not allowed in readonly mode";
2362 else { &error("FTP connections not allowed in readonly mode"); }
2365 $main::download_timed_out = undef;
2366 local $SIG{ALRM} = \&download_timeout;
2369 # connect to host and login
2370 &open_socket($_[0], $_[7] || 21, "SOCK", $_[3]) || return 0;
2372 if ($main::download_timed_out) {
2373 if ($_[3]) { ${$_[3]} = $main::download_timed_out; return 0; }
2374 else { &error($main::download_timed_out); }
2376 &ftp_command("", 2, $_[3]) || return 0;
2378 # Login as supplied user
2379 my @urv = &ftp_command("USER $_[5]", [ 2, 3 ], $_[3]);
2381 if (int($urv[1]/100) == 3) {
2382 &ftp_command("PASS $_[6]", 2, $_[3]) || return 0;
2386 # Login as anonymous
2387 my @urv = &ftp_command("USER anonymous", [ 2, 3 ], $_[3]);
2389 if (int($urv[1]/100) == 3) {
2390 &ftp_command("PASS root\@".&get_system_hostname(), 2,
2394 &$cbfunc(1, 0) if ($cbfunc);
2396 &ftp_command("TYPE I", 2, $_[3]) || return 0;
2398 # get the file size and tell the callback
2399 my @st = stat($_[2]);
2401 &$cbfunc(2, $st[7]);
2405 my $pasv = &ftp_command("PASV", 2, $_[3]);
2406 defined($pasv) || return 0;
2407 $pasv =~ /\(([0-9,]+)\)/;
2408 @n = split(/,/ , $1);
2409 &open_socket("$n[0].$n[1].$n[2].$n[3]", $n[4]*256 + $n[5], "CON", $_[3]) || return 0;
2410 &ftp_command("STOR $_[1]", 1, $_[3]) || return 0;
2415 while(read(PFILE, $buf, 1024) > 0) {
2417 $got += length($buf);
2418 &$cbfunc(3, $got) if ($cbfunc);
2422 if ($got != $st[7]) {
2423 if ($_[3]) { ${$_[3]} = "Upload incomplete"; return 0; }
2424 else { &error("Upload incomplete"); }
2426 &$cbfunc(4) if ($cbfunc);
2429 &ftp_command("", 2, $_[3]) || return 0;
2430 &ftp_command("QUIT", 2, $_[3]) || return 0;
2436 =head2 no_proxy(host)
2438 Checks if some host is on the no proxy list. For internal use by the
2439 http_download and ftp_download functions.
2444 my $ip = &to_ipaddress($_[0]);
2445 foreach my $n (split(/\s+/, $gconfig{'noproxy'})) {
2446 return 1 if ($_[0] =~ /\Q$n\E/ ||
2452 =head2 open_socket(host, port, handle, [&error])
2454 Open a TCP connection to some host and port, using a file handle. The
2457 =item host - Hostname or IP address to connect to.
2459 =item port - TCP port number.
2461 =item handle - A file handle name to use for the connection.
2463 =item error - A string reference to write any error message into. If not set, the error function is called on failure.
2468 my ($host, $port, $fh, $err) = @_;
2469 $fh = &callers_package($fh);
2471 if ($gconfig{'debug_what_net'}) {
2472 &webmin_debug_log('TCP', "host=$host port=$port");
2474 if (!socket($fh, PF_INET, SOCK_STREAM, getprotobyname("tcp"))) {
2475 if ($err) { $$err = "Failed to create socket : $!"; return 0; }
2476 else { &error("Failed to create socket : $!"); }
2479 if (!($addr = inet_aton($host))) {
2480 if ($err) { $$err = "Failed to lookup IP address for $host"; return 0; }
2481 else { &error("Failed to lookup IP address for $host"); }
2483 if ($gconfig{'bind_proxy'}) {
2484 if (!bind($fh,pack_sockaddr_in(0, inet_aton($gconfig{'bind_proxy'})))) {
2485 if ($err) { $$err = "Failed to bind to source address : $!"; return 0; }
2486 else { &error("Failed to bind to source address : $!"); }
2489 if (!connect($fh, pack_sockaddr_in($port, $addr))) {
2490 if ($err) { $$err = "Failed to connect to $host:$port : $!"; return 0; }
2491 else { &error("Failed to connect to $host:$port : $!"); }
2493 my $old = select($fh); $| =1; select($old);
2497 =head2 download_timeout
2499 Called when a download times out. For internal use only.
2502 sub download_timeout
2504 $main::download_timed_out = "Download timed out";
2507 =head2 ftp_command(command, expected, [&error], [filehandle])
2509 Send an FTP command, and die if the reply is not what was expected. Mainly
2510 for internal use by the ftp_download and ftp_upload functions.
2515 my ($cmd, $expect, $err, $fh) = @_;
2517 $fh = &callers_package($fh);
2520 my $what = $cmd ne "" ? "<i>$cmd</i>" : "initial connection";
2522 print $fh "$cmd\r\n";
2525 if (!($line = <$fh>)) {
2527 if ($err) { $$err = "Failed to read reply to $what"; return undef; }
2528 else { &error("Failed to read reply to $what"); }
2530 $line =~ /^(...)(.)(.*)$/;
2533 foreach my $c (@$expect) {
2534 $found++ if (int($1/100) == $c);
2538 $found++ if (int($1/100) == $_[1]);
2542 if ($err) { $$err = "$what failed : $3"; return undef; }
2543 else { &error("$what failed : $3"); }
2548 # Need to skip extra stuff..
2550 if (!($line = <$fh>)) {
2552 if ($$err) { $$err = "Failed to read reply to $what";
2554 else { &error("Failed to read reply to $what"); }
2556 $line =~ /^(....)(.*)$/; $reply .= $2;
2557 if ($1 eq "$rcode ") { last; }
2561 return wantarray ? ($reply, $rcode) : $reply;
2564 =head2 to_ipaddress(hostname)
2566 Converts a hostname to an a.b.c.d format IP address, or returns undef if
2567 it cannot be resolved.
2572 if (&check_ipaddress($_[0])) {
2576 my $hn = gethostbyname($_[0]);
2577 return undef if (!$hn);
2578 local @ip = unpack("CCCC", $hn);
2579 return join("." , @ip);
2583 =head2 icons_table(&links, &titles, &icons, [columns], [href], [width], [height], &befores, &afters)
2585 Renders a 4-column table of icons. The useful parameters are :
2587 =item links - An array ref of link destination URLs for the icons.
2589 =item titles - An array ref of titles to appear under the icons.
2591 =item icons - An array ref of URLs for icon images.
2593 =item columns - Number of columns to layout the icons with. Defaults to 4.
2598 &load_theme_library();
2599 if (defined(&theme_icons_table)) {
2600 &theme_icons_table(@_);
2604 my $cols = $_[3] ? $_[3] : 4;
2605 my $per = int(100.0 / $cols);
2606 print "<table class='icons_table' width=100% cellpadding=5>\n";
2607 for(my $i=0; $i<@{$_[0]}; $i++) {
2608 if ($i%$cols == 0) { print "<tr>\n"; }
2609 print "<td width=$per% align=center valign=top>\n";
2610 &generate_icon($_[2]->[$i], $_[1]->[$i], $_[0]->[$i],
2611 ref($_[4]) ? $_[4]->[$i] : $_[4], $_[5], $_[6],
2612 $_[7]->[$i], $_[8]->[$i]);
2614 if ($i%$cols == $cols-1) { print "</tr>\n"; }
2616 while($i++%$cols) { print "<td width=$per%></td>\n"; $need_tr++; }
2617 print "</tr>\n" if ($need_tr);
2621 =head2 replace_file_line(file, line, [newline]*)
2623 Replaces one line in some file with 0 or more new lines. The parameters are :
2625 =item file - Full path to some file, like /etc/hosts.
2627 =item line - Line number to replace, starting from 0.
2629 =item newline - Zero or more lines to put into the file at the given line number. These must be newline-terminated strings.
2632 sub replace_file_line
2635 my $realfile = &translate_filename($_[0]);
2636 open(FILE, $realfile);
2639 if (@_ > 2) { splice(@lines, $_[1], 1, @_[2..$#_]); }
2640 else { splice(@lines, $_[1], 1); }
2641 &open_tempfile(FILE, ">$realfile");
2642 &print_tempfile(FILE, @lines);
2643 &close_tempfile(FILE);
2646 =head2 read_file_lines(file, [readonly])
2648 Returns a reference to an array containing the lines from some file. This
2649 array can be modified, and will be written out when flush_file_lines()
2650 is called. The parameters are :
2652 =item file - Full path to the file to read.
2654 =item readonly - Should be set 1 if the caller is only going to read the lines, and never write it out.
2658 $lref = read_file_lines("/etc/hosts");
2659 push(@$lref, "127.0.0.1 localhost");
2660 flush_file_lines("/etc/hosts");
2666 my ($package, $filename, $line) = caller;
2667 print STDERR "Missing file to read at ${package}::${filename} line $line\n";
2669 my $realfile = &translate_filename($_[0]);
2670 if (!$main::file_cache{$realfile}) {
2673 &webmin_debug_log('READ', $_[0]) if ($gconfig{'debug_what_read'});
2674 open(READFILE, $realfile);
2677 $eol = /\r\n$/ ? "\r\n" : "\n";
2683 $main::file_cache{$realfile} = \@lines;
2684 $main::file_cache_noflush{$realfile} = $_[1];
2685 $main::file_cache_eol{$realfile} = $eol || "\n";
2688 # Make read-write if currently readonly
2690 $main::file_cache_noflush{$realfile} = 0;
2693 return $main::file_cache{$realfile};
2696 =head2 flush_file_lines([file], [eol])
2698 Write out to a file previously read by read_file_lines to disk (except
2699 for those marked readonly). The parameters are :
2701 =item file - The file to flush out.
2703 =item eof - End-of-line character for each line. Defaults to \n.
2706 sub flush_file_lines
2710 local $trans = &translate_filename($_[0]);
2711 $main::file_cache{$trans} ||
2712 &error("flush_file_lines called on non-loaded file $trans");
2713 push(@files, $trans);
2716 @files = ( keys %main::file_cache );
2718 foreach my $f (@files) {
2719 my $eol = $_[1] || $main::file_cache_eol{$f} || "\n";
2720 if (!$main::file_cache_noflush{$f}) {
2721 no warnings; # XXX Bareword file handles should go away
2722 &open_tempfile(FLUSHFILE, ">$f");
2723 foreach my $line (@{$main::file_cache{$f}}) {
2724 (print FLUSHFILE $line,$eol) ||
2725 &error(&text("efilewrite", $f, $!));
2727 &close_tempfile(FLUSHFILE);
2729 delete($main::file_cache{$f});
2730 delete($main::file_cache_noflush{$f});
2734 =head2 unflush_file_lines(file)
2736 Clear the internal cache of some given file, previously read by read_file_lines.
2739 sub unflush_file_lines
2741 my $realfile = &translate_filename($_[0]);
2742 delete($main::file_cache{$realfile});
2743 delete($main::file_cache_noflush{$realfile});
2746 =head2 unix_user_input(fieldname, user, [form])
2748 Returns HTML for an input to select a Unix user. By default this is a text
2749 box with a user popup button next to it.
2754 if (defined(&theme_unix_user_input)) {
2755 return &theme_unix_user_input(@_);
2757 return "<input name=$_[0] size=13 value=\"$_[1]\"> ".
2758 &user_chooser_button($_[0], 0, $_[2] || 0)."\n";
2761 =head2 unix_group_input(fieldname, user, [form])
2763 Returns HTML for an input to select a Unix group. By default this is a text
2764 box with a group popup button next to it.
2767 sub unix_group_input
2769 if (defined(&theme_unix_group_input)) {
2770 return &theme_unix_group_input(@_);
2772 return "<input name=$_[0] size=13 value=\"$_[1]\"> ".
2773 &group_chooser_button($_[0], 0, $_[2] || 0)."\n";
2776 =head2 hlink(text, page, [module], [width], [height])
2778 Returns HTML for a link that when clicked on pops up a window for a Webmin
2779 help page. The parameters are :
2781 =item text - Text for the link.
2783 =item page - Help page code, such as 'intro'.
2785 =item module - Module the help page is in. Defaults to the current module.
2787 =item width - Width of the help popup window. Defaults to 600 pixels.
2789 =item height - Height of the help popup window. Defaults to 400 pixels.
2791 The actual help pages are in each module's help sub-directory, in files with
2797 if (defined(&theme_hlink)) {
2798 return &theme_hlink(@_);
2800 my $mod = $_[2] ? $_[2] : &get_module_name();
2801 my $width = $_[3] || $tconfig{'help_width'} || $gconfig{'help_width'} || 600;
2802 my $height = $_[4] || $tconfig{'help_height'} || $gconfig{'help_height'} || 400;
2803 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>";
2806 =head2 user_chooser_button(field, multiple, [form])
2808 Returns HTML for a javascript button for choosing a Unix user or users.
2809 The parameters are :
2811 =item field - Name of the HTML field to place the username into.
2813 =item multiple - Set to 1 if multiple users can be selected.
2815 =item form - Index of the form on the page.
2818 sub user_chooser_button
2820 return undef if (!&supports_users());
2821 return &theme_user_chooser_button(@_)
2822 if (defined(&theme_user_chooser_button));
2823 my $form = defined($_[2]) ? $_[2] : 0;
2824 my $w = $_[1] ? 500 : 300;
2826 if ($_[1] && $gconfig{'db_sizeusers'}) {
2827 ($w, $h) = split(/x/, $gconfig{'db_sizeusers'});
2829 elsif (!$_[1] && $gconfig{'db_sizeuser'}) {
2830 ($w, $h) = split(/x/, $gconfig{'db_sizeuser'});
2832 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";
2835 =head2 group_chooser_button(field, multiple, [form])
2837 Returns HTML for a javascript button for choosing a Unix group or groups
2838 The parameters are :
2840 =item field - Name of the HTML field to place the group name into.
2842 =item multiple - Set to 1 if multiple groups can be selected.
2844 =item form - Index of the form on the page.
2847 sub group_chooser_button
2849 return undef if (!&supports_users());
2850 return &theme_group_chooser_button(@_)
2851 if (defined(&theme_group_chooser_button));
2852 my $form = defined($_[2]) ? $_[2] : 0;
2853 my $w = $_[1] ? 500 : 300;
2855 if ($_[1] && $gconfig{'db_sizeusers'}) {
2856 ($w, $h) = split(/x/, $gconfig{'db_sizeusers'});
2858 elsif (!$_[1] && $gconfig{'db_sizeuser'}) {
2859 ($w, $h) = split(/x/, $gconfig{'db_sizeuser'});
2861 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";
2864 =head2 foreign_check(module, [api-only])
2866 Checks if some other module exists and is supported on this OS. The parameters
2869 =item module - Name of the module to check.
2871 =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.
2876 my ($mod, $api) = @_;
2878 my $mdir = &module_root_directory($mod);
2879 &read_file_cached("$mdir/module.info", \%minfo) || return 0;
2880 return &check_os_support(\%minfo, undef, undef, $api);
2883 =head2 foreign_exists(module)
2885 Checks if some other module exists. The module parameter is the short module
2891 my $mdir = &module_root_directory($_[0]);
2892 return -r "$mdir/module.info";
2895 =head2 foreign_available(module)
2897 Returns 1 if some module is installed, and acessible to the current user. The
2898 module parameter is the module directory name.
2901 sub foreign_available
2903 return 0 if (!&foreign_check($_[0]) &&
2904 !$gconfig{'available_even_if_no_support'});
2905 my %foreign_module_info = &get_module_info($_[0]);
2907 # Check list of allowed modules
2909 &read_acl(\%acl, undef);
2910 return 0 if (!$acl{$base_remote_user,$_[0]} &&
2911 !$acl{$base_remote_user,'*'});
2913 # Check for usermod restrictions
2914 my @usermods = &list_usermods();
2915 return 0 if (!&available_usermods( [ \%foreign_module_info ], \@usermods));
2917 if (&get_product_name() eq "webmin") {
2918 # Check if the user has any RBAC privileges in this module
2919 if (&supports_rbac($_[0]) &&
2920 &use_rbac_module_acl(undef, $_[0])) {
2921 # RBAC is enabled for this user and module - check if he
2923 my $rbacs = &get_rbac_module_acl($remote_user, $_[0]);
2924 return 0 if (!$rbacs);
2926 elsif ($gconfig{'rbacdeny_'.$base_remote_user}) {
2927 # If denying access to modules not specifically allowed by
2928 # RBAC, then prevent access
2933 # Check readonly support
2934 if (&is_readonly_mode()) {
2935 return 0 if (!$foreign_module_info{'readonly'});
2938 # Check if theme vetos
2939 if (defined(&theme_foreign_available)) {
2940 return 0 if (!&theme_foreign_available($_[0]));
2943 # Check if licence module vetos
2944 if ($main::licence_module) {
2945 return 0 if (!&foreign_call($main::licence_module,
2946 "check_module_licence", $_[0]));
2952 =head2 foreign_require(module, [file], [package])
2954 Brings in functions from another module, and places them in the Perl namespace
2955 with the same name as the module. The parameters are :
2957 =item module - The source module's directory name, like sendmail.
2959 =item file - The API file in that module, like sendmail-lib.pl. If missing, all API files are loaded.
2961 =item package - Perl package to place the module's functions and global variables in.
2963 If the original module name contains dashes, they will be replaced with _ in
2969 my ($mod, $file, $pkg) = @_;
2970 $pkg ||= $mod || "global";
2971 $pkg =~ s/[^A-Za-z0-9]/_/g;
2974 push(@files, $file);
2978 my %minfo = &get_module_info($mod);
2979 if ($minfo{'library'}) {
2980 @files = split(/\s+/, $minfo{'library'});
2983 @files = ( $mod."-lib.pl" );
2986 @files = grep { !$main::done_foreign_require{$pkg,$_} } @files;
2987 return 1 if (!@files);
2988 foreach my $f (@files) {
2989 $main::done_foreign_require{$pkg,$f}++;
2992 my $mdir = &module_root_directory($mod);
2993 @INC = &unique($mdir, @INC);
2994 -d $mdir || &error("Module $mod does not exist");
2995 if (!&get_module_name() && $mod) {
2998 my $old_fmn = $ENV{'FOREIGN_MODULE_NAME'};
2999 my $old_frd = $ENV{'FOREIGN_ROOT_DIRECTORY'};
3000 my $code = "package $pkg; ".
3001 "\$ENV{'FOREIGN_MODULE_NAME'} = '$mod'; ".
3002 "\$ENV{'FOREIGN_ROOT_DIRECTORY'} = '$root_directory'; ";
3003 foreach my $f (@files) {
3004 $code .= "do '$mdir/$f' || die \$@; ";
3007 if (defined($old_fmn)) {
3008 $ENV{'FOREIGN_MODULE_NAME'} = $old_fmn;
3011 delete($ENV{'FOREIGN_MODULE_NAME'});
3013 if (defined($old_frd)) {
3014 $ENV{'FOREIGN_ROOT_DIRECTORY'} = $old_frd;
3017 delete($ENV{'FOREIGN_ROOT_DIRECTORY'});
3020 if ($@) { &error("Require $mod/$files[0] failed : <pre>$@</pre>"); }
3024 =head2 foreign_call(module, function, [arg]*)
3026 Call a function in another module. The module parameter is the target module
3027 directory name, function is the perl sub to call, and the remaining parameters
3028 are the arguments. However, unless you need to call a function whose name
3029 is dynamic, it is better to use Perl's cross-module function call syntax
3030 like module::function(args).
3035 my $pkg = $_[0] || "global";
3036 $pkg =~ s/[^A-Za-z0-9]/_/g;
3037 my @args = @_[2 .. @_-1];
3038 $main::foreign_args = \@args;
3039 my @rv = eval <<EOF;
3041 &$_[1](\@{\$main::foreign_args});
3043 if ($@) { &error("$_[0]::$_[1] failed : $@"); }
3044 return wantarray ? @rv : $rv[0];
3047 =head2 foreign_config(module, [user-config])
3049 Get the configuration from another module, and return it as a hash. If the
3050 user-config parameter is set to 1, returns the Usermin user-level preferences
3051 for the current user instead.
3056 my ($mod, $uc) = @_;
3059 &read_file_cached("$root_directory/$mod/defaultuconfig", \%fconfig);
3060 &read_file_cached("$config_directory/$mod/uconfig", \%fconfig);
3061 &read_file_cached("$user_config_directory/$mod/config", \%fconfig);
3064 &read_file_cached("$config_directory/$mod/config", \%fconfig);
3069 =head2 foreign_installed(module, mode)
3071 Checks if the server for some module is installed, and possibly also checks
3072 if the module has been configured by Webmin.
3073 For mode 1, returns 2 if the server is installed and configured for use by
3074 Webmin, 1 if installed but not configured, or 0 otherwise.
3075 For mode 0, returns 1 if installed, 0 if not.
3076 If the module does not provide an install_check.pl script, assumes that
3077 the server is installed.
3080 sub foreign_installed
3082 my ($mod, $configured) = @_;
3083 if (defined($main::foreign_installed_cache{$mod,$configured})) {
3085 return $main::foreign_installed_cache{$mod,$configured};
3089 if (!&foreign_check($mod)) {
3094 my $mdir = &module_root_directory($mod);
3095 if (!-r "$mdir/install_check.pl") {
3096 # Not known, assume OK
3097 $rv = $configured ? 2 : 1;
3100 # Call function to check
3101 &foreign_require($mod, "install_check.pl");
3102 $rv = &foreign_call($mod, "is_installed", $configured);
3105 $main::foreign_installed_cache{$mod,$configured} = $rv;
3110 =head2 foreign_defined(module, function)
3112 Returns 1 if some function is defined in another module. In general, it is
3113 simpler to use the syntax &defined(module::function) instead.
3119 $pkg =~ s/[^A-Za-z0-9]/_/g;
3120 my $func = "${pkg}::$_[1]";
3121 return defined(&$func);
3124 =head2 get_system_hostname([short])
3126 Returns the hostname of this system. If the short parameter is set to 1,
3127 then the domain name is not prepended - otherwise, Webmin will attempt to get
3128 the fully qualified hostname, like foo.example.com.
3131 sub get_system_hostname
3134 if (!$main::get_system_hostname[$m]) {
3135 if ($gconfig{'os_type'} ne 'windows') {
3136 # Try some common Linux hostname files first
3138 if ($gconfig{'os_type'} eq 'redhat-linux') {
3140 &read_env_file("/etc/sysconfig/network", \%nc);
3141 if ($nc{'HOSTNAME'}) {
3142 $fromfile = $nc{'HOSTNAME'};
3145 elsif ($gconfig{'os_type'} eq 'debian-linux') {
3146 my $hn = &read_file_contents("/etc/hostname");
3152 elsif ($gconfig{'os_type'} eq 'open-linux') {
3153 my $hn = &read_file_contents("/etc/HOSTNAME");
3159 elsif ($gconfig{'os_type'} eq 'solaris') {
3160 my $hn = &read_file_contents("/etc/nodename");
3167 # If we found a hostname, use it if value
3168 if ($fromfile && ($m || $fromfile =~ /\./)) {
3170 $fromfile =~ s/\..*$//;
3172 $main::get_system_hostname[$m] = $fromfile;
3176 # Can use hostname command on Unix
3177 &execute_command("hostname", undef,
3178 \$main::get_system_hostname[$m], undef, 0, 1);
3179 chop($main::get_system_hostname[$m]);
3181 eval "use Sys::Hostname";
3183 $main::get_system_hostname[$m] = eval "hostname()";
3185 if ($@ || !$main::get_system_hostname[$m]) {
3186 $main::get_system_hostname[$m] = "UNKNOWN";
3189 elsif ($main::get_system_hostname[$m] !~ /\./ &&
3190 $gconfig{'os_type'} =~ /linux$/ &&
3191 !$gconfig{'no_hostname_f'} && !$_[0]) {
3192 # Try with -f flag to get fully qualified name
3194 my $ex = &execute_command("hostname -f", undef, \$flag,
3197 if ($ex || $flag eq "") {
3198 # -f not supported! We have probably set the
3199 # hostname to just '-f'. Fix the problem
3202 &execute_command("hostname ".
3203 quotemeta($main::get_system_hostname[$m]),
3204 undef, undef, undef, 0, 1);
3208 $main::get_system_hostname[$m] = $flag;
3213 # On Windows, try computername environment variable
3214 return $ENV{'computername'} if ($ENV{'computername'});
3215 return $ENV{'COMPUTERNAME'} if ($ENV{'COMPUTERNAME'});
3217 # Fall back to net name command
3218 my $out = `net name 2>&1`;
3219 if ($out =~ /\-+\r?\n(\S+)/) {
3220 $main::get_system_hostname[$m] = $1;
3223 $main::get_system_hostname[$m] = "windows";
3227 return $main::get_system_hostname[$m];
3230 =head2 get_webmin_version
3232 Returns the version of Webmin currently being run, such as 1.450.
3235 sub get_webmin_version
3237 if (!$get_webmin_version) {
3238 open(VERSION, "$root_directory/version") || return 0;
3239 ($get_webmin_version = <VERSION>) =~ tr/\r|\n//d;
3242 return $get_webmin_version;
3245 =head2 get_module_acl([user], [module], [no-rbac], [no-default])
3247 Returns a hash containing access control options for the given user and module.
3248 By default the current username and module name are used. If the no-rbac flag
3249 is given, the permissions will not be updated based on the user's RBAC role
3250 (as seen on Solaris). If the no-default flag is given, default permissions for
3251 the module will not be included.
3256 my $u = defined($_[0]) ? $_[0] : $base_remote_user;
3257 my $m = defined($_[1]) ? $_[1] : &get_module_name();
3258 my $mdir = &module_root_directory($m);
3261 # Read default ACL first, to be overridden by per-user settings
3262 &read_file_cached("$mdir/defaultacl", \%rv);
3264 # If this isn't a master admin user, apply the negative permissions
3265 # so that he doesn't un-expectedly gain access to new features
3267 &read_file_cached("$config_directory/$u.acl", \%gaccess);
3268 if ($gaccess{'negative'}) {
3269 &read_file_cached("$mdir/negativeacl", \%rv);
3273 if (!$_[2] && &supports_rbac($m) && &use_rbac_module_acl($u, $m)) {
3274 # RBAC overrides exist for this user in this module
3275 my $rbac = &get_rbac_module_acl(
3276 defined($_[0]) ? $_[0] : $remote_user, $m);
3277 foreach my $r (keys %$rbac) {
3278 $rv{$r} = $rbac->{$r};
3281 elsif ($gconfig{"risk_$u"} && $m) {
3282 # ACL is defined by user's risk level
3283 my $rf = $gconfig{"risk_$u"}.'.risk';
3284 &read_file_cached("$mdir/$rf", \%rv);
3286 my $sf = $gconfig{"skill_$u"}.'.skill';
3287 &read_file_cached("$mdir/$sf", \%rv);
3290 # Use normal Webmin ACL, if a user is set
3292 &get_miniserv_config(\%miniserv);
3293 if ($miniserv{'userdb'}) {
3294 # Look for this user in the user/group DB
3297 &read_file_cached("$config_directory/$m/$u.acl", \%rv);
3298 if ($remote_user ne $base_remote_user && !defined($_[0])) {
3299 &read_file_cached("$config_directory/$m/$remote_user.acl",\%rv);
3302 if ($tconfig{'preload_functions'}) {
3303 &load_theme_library();
3305 if (defined(&theme_get_module_acl)) {
3306 %rv = &theme_get_module_acl($u, $m, \%rv);
3311 =head2 get_group_module_acl(group, [module])
3313 Returns the ACL for a Webmin group, in an optional module (which defaults to
3314 the current module).
3317 sub get_group_module_acl
3320 my $m = defined($_[1]) ? $_[1] : &get_module_name();
3321 my $mdir = &module_root_directory($m);
3323 &read_file_cached("$mdir/defaultacl", \%rv);
3324 &read_file_cached("$config_directory/$m/$g.gacl", \%rv);
3325 if (defined(&theme_get_module_acl)) {
3326 %rv = &theme_get_module_acl($g, $m, \%rv);
3331 =head2 save_module_acl(&acl, [user], [module])
3333 Updates the acl hash for some user and module. The parameters are :
3335 =item acl - Hash reference for the new access control options.
3337 =item user - User to update, defaulting to the current user.
3339 =item module - Module to update, defaulting to the caller.
3344 my $u = defined($_[1]) ? $_[1] : $base_remote_user;
3345 my $m = defined($_[2]) ? $_[2] : &get_module_name();
3346 if (&foreign_check("acl")) {
3347 # Check if this user is a member of a group, and if he gets the
3348 # module from a group. If so, update its ACL as well
3349 &foreign_require("acl", "acl-lib.pl");
3351 foreach my $g (&acl::list_groups()) {
3352 if (&indexof($u, @{$g->{'members'}}) >= 0 &&
3353 &indexof($m, @{$g->{'modules'}}) >= 0) {
3359 &save_group_module_acl($_[0], $group->{'name'}, $m);
3362 if (!-d "$config_directory/$m") {
3363 mkdir("$config_directory/$m", 0755);
3365 &write_file("$config_directory/$m/$u.acl", $_[0]);
3368 =head2 save_group_module_acl(&acl, group, [module])
3370 Updates the acl hash for some group and module. The parameters are :
3372 =item acl - Hash reference for the new access control options.
3374 =item group - Group name to update.
3376 =item module - Module to update, defaulting to the caller.
3379 sub save_group_module_acl
3382 my $m = defined($_[2]) ? $_[2] : &get_module_name();
3383 if (&foreign_check("acl")) {
3384 # Check if this group is a member of a group, and if it gets the
3385 # module from a group. If so, update the parent ACL as well
3386 &foreign_require("acl", "acl-lib.pl");
3388 foreach my $pg (&acl::list_groups()) {
3389 if (&indexof('@'.$g, @{$pg->{'members'}}) >= 0 &&
3390 &indexof($m, @{$pg->{'modules'}}) >= 0) {
3396 &save_group_module_acl($_[0], $group->{'name'}, $m);
3399 if (!-d "$config_directory/$m") {
3400 mkdir("$config_directory/$m", 0755);
3402 &write_file("$config_directory/$m/$g.gacl", $_[0]);
3407 This function must be called by all Webmin CGI scripts, either directly or
3408 indirectly via a per-module lib.pl file. It performs a number of initialization
3409 and housekeeping tasks, such as working out the module name, checking that the
3410 current user has access to the module, and populating global variables. Some
3411 of the variables set include :
3413 =item $config_directory - Base Webmin config directory, typically /etc/webmin
3415 =item $var_directory - Base logs directory, typically /var/webmin
3417 =item %config - Per-module configuration.
3419 =item %gconfig - Global configuration.
3421 =item $scriptname - Base name of the current perl script.
3423 =item $module_name - The name of the current module.
3425 =item $module_config_directory - The config directory for this module.
3427 =item $module_config_file - The config file for this module.
3429 =item $module_root_directory - This module's code directory.
3431 =item $webmin_logfile - The detailed logfile for webmin.
3433 =item $remote_user - The actual username used to login to webmin.
3435 =item $base_remote_user - The username whose permissions are in effect.
3437 =item $current_theme - The theme currently in use.
3439 =item $root_directory - The first root directory of this webmin install.
3441 =item @root_directories - All root directories for this webmin install.
3446 # Record first process ID that called this, so we know when it exited to clean
3448 $main::initial_process_id ||= $$;
3450 # Configuration and spool directories
3451 if (!defined($ENV{'WEBMIN_CONFIG'})) {
3452 die "WEBMIN_CONFIG not set";
3454 $config_directory = $ENV{'WEBMIN_CONFIG'};
3455 if (!defined($ENV{'WEBMIN_VAR'})) {
3456 open(VARPATH, "$config_directory/var-path");
3457 chop($var_directory = <VARPATH>);
3461 $var_directory = $ENV{'WEBMIN_VAR'};
3463 $main::http_cache_directory = $ENV{'WEBMIN_VAR'}."/cache";
3464 $main::default_debug_log_file = $ENV{'WEBMIN_VAR'}."/webmin.debug";
3466 if ($ENV{'SESSION_ID'}) {
3467 # Hide this variable from called programs, but keep it for internal use
3468 $main::session_id = $ENV{'SESSION_ID'};
3469 delete($ENV{'SESSION_ID'});
3471 if ($ENV{'REMOTE_PASS'}) {
3472 # Hide the password too
3473 $main::remote_pass = $ENV{'REMOTE_PASS'};
3474 delete($ENV{'REMOTE_PASS'});
3477 if ($> == 0 && $< != 0 && !$ENV{'FOREIGN_MODULE_NAME'}) {
3478 # Looks like we are running setuid, but the real UID hasn't been set.
3479 # Do so now, so that executed programs don't get confused
3484 # Read the webmin global config file. This contains the OS type and version,
3485 # OS specific configuration and global options such as proxy servers
3486 $config_file = "$config_directory/config";
3488 &read_file_cached($config_file, \%gconfig);
3489 $null_file = $gconfig{'os_type'} eq 'windows' ? "NUL" : "/dev/null";
3490 $path_separator = $gconfig{'os_type'} eq 'windows' ? ';' : ':';
3492 # If debugging is enabled, open the debug log
3493 if ($gconfig{'debug_enabled'} && !$main::opened_debug_log++) {
3494 my $dlog = $gconfig{'debug_file'} || $main::default_debug_log_file;
3495 if ($gconfig{'debug_size'}) {
3496 my @st = stat($dlog);
3497 if ($st[7] > $gconfig{'debug_size'}) {
3498 rename($dlog, $dlog.".0");
3501 open(main::DEBUGLOG, ">>$dlog");
3502 $main::opened_debug_log = 1;
3504 if ($gconfig{'debug_what_start'}) {
3505 my $script_name = $0 =~ /([^\/]+)$/ ? $1 : '-';
3506 $main::debug_log_start_time = time();
3507 &webmin_debug_log("START", "script=$script_name");
3508 $main::debug_log_start_module = $module_name;
3512 # Set PATH and LD_LIBRARY_PATH
3513 if ($gconfig{'path'}) {
3514 if ($gconfig{'syspath'}) {
3516 $ENV{'PATH'} = $gconfig{'path'};
3520 $ENV{'PATH'} = $gconfig{'path'}.$path_separator.$ENV{'PATH'};
3523 $ENV{$gconfig{'ld_env'}} = $gconfig{'ld_path'} if ($gconfig{'ld_env'});
3525 # Set http_proxy and ftp_proxy environment variables, based on Webmin settings
3526 if ($gconfig{'http_proxy'}) {
3527 $ENV{'http_proxy'} = $gconfig{'http_proxy'};
3529 if ($gconfig{'ftp_proxy'}) {
3530 $ENV{'ftp_proxy'} = $gconfig{'ftp_proxy'};
3532 if ($gconfig{'noproxy'}) {
3533 $ENV{'no_proxy'} = $gconfig{'noproxy'};
3536 # Find all root directories
3538 if (&get_miniserv_config(\%miniserv)) {
3539 @root_directories = ( $miniserv{'root'} );
3540 for($i=0; defined($miniserv{"extraroot_$i"}); $i++) {
3541 push(@root_directories, $miniserv{"extraroot_$i"});
3545 # Work out which module we are in, and read the per-module config file
3546 $0 =~ s/\\/\//g; # Force consistent path on Windows
3547 if (defined($ENV{'FOREIGN_MODULE_NAME'})) {
3548 # In a foreign call - use the module name given
3549 $root_directory = $ENV{'FOREIGN_ROOT_DIRECTORY'};
3550 $module_name = $ENV{'FOREIGN_MODULE_NAME'};
3551 @root_directories = ( $root_directory ) if (!@root_directories);
3553 elsif ($ENV{'SCRIPT_NAME'}) {
3554 my $sn = $ENV{'SCRIPT_NAME'};
3555 $sn =~ s/^$gconfig{'webprefix'}//
3556 if (!$gconfig{'webprefixnoredir'});
3557 if ($sn =~ /^\/([^\/]+)\//) {
3558 # Get module name from CGI path
3561 if ($ENV{'SERVER_ROOT'}) {
3562 $root_directory = $ENV{'SERVER_ROOT'};
3564 elsif ($ENV{'SCRIPT_FILENAME'}) {
3565 $root_directory = $ENV{'SCRIPT_FILENAME'};
3566 $root_directory =~ s/$sn$//;
3568 @root_directories = ( $root_directory ) if (!@root_directories);
3571 # Get root directory from miniserv.conf, and deduce module name from $0
3572 $root_directory = $root_directories[0];
3574 foreach my $r (@root_directories) {
3575 if ($0 =~ /^$r\/([^\/]+)\/[^\/]+$/i) {
3576 # Under a module directory
3581 elsif ($0 =~ /^$root_directory\/[^\/]+$/i) {
3587 &error("Script was not run with full path (failed to find $0 under $root_directory)") if (!$rok);
3590 # Work out of this is a web, command line or cron job
3591 if (!$main::webmin_script_type) {
3592 if ($ENV{'SCRIPT_NAME'}) {
3594 $main::webmin_script_type = 'web';
3597 # Cron jobs have no TTY
3598 if ($gconfig{'os_type'} eq 'windows' ||
3599 open(DEVTTY, ">/dev/tty")) {
3600 $main::webmin_script_type = 'cmd';
3604 $main::webmin_script_type = 'cron';
3609 # Set the umask based on config
3610 if ($gconfig{'umask'} && !$main::umask_already++) {
3611 umask(oct($gconfig{'umask'}));
3614 # If this is a cron job or other background task, set the nice level
3615 if (!$main::nice_already && $main::webmin_script_type eq 'cron') {
3617 if ($gconfig{'nice'}) {
3618 eval 'POSIX::nice($gconfig{\'nice\'});';
3621 # Set IO scheduling class and priority
3622 if ($gconfig{'sclass'} ne '' || $gconfig{'sprio'} ne '') {
3624 $cmd .= " -c ".quotemeta($gconfig{'sclass'})
3625 if ($gconfig{'sclass'} ne '');
3626 $cmd .= " -n ".quotemeta($gconfig{'sprio'})
3627 if ($gconfig{'sprio'} ne '');
3629 &execute_command("$cmd >/dev/null 2>&1");
3632 $main::nice_already++;
3635 my $u = $ENV{'BASE_REMOTE_USER'} || $ENV{'REMOTE_USER'};
3636 $base_remote_user = $u;
3637 $remote_user = $ENV{'REMOTE_USER'};
3640 # Find and load the configuration file for this module
3641 my (@ruinfo, $rgroup);
3642 $module_config_directory = "$config_directory/$module_name";
3643 if (&get_product_name() eq "usermin" &&
3644 -r "$module_config_directory/config.$remote_user") {
3646 $module_config_file = "$module_config_directory/config.$remote_user";
3648 elsif (&get_product_name() eq "usermin" &&
3649 (@ruinfo = getpwnam($remote_user)) &&
3650 ($rgroup = getgrgid($ruinfo[3])) &&
3651 -r "$module_config_directory/config.\@$rgroup") {
3652 # Based on group name
3653 $module_config_file = "$module_config_directory/config.\@$rgroup";
3657 $module_config_file = "$module_config_directory/config";
3660 &read_file_cached($module_config_file, \%config);
3662 # Fix up windows-specific substitutions in values
3663 foreach my $k (keys %config) {
3664 if ($config{$k} =~ /\$\{systemroot\}/) {
3665 my $root = &get_windows_root();
3666 $config{$k} =~ s/\$\{systemroot\}/$root/g;
3671 # Record the initial module
3672 $main::initial_module_name ||= $module_name;
3674 # Set some useful variables
3676 $current_themes = $ENV{'MOBILE_DEVICE'} && defined($gconfig{'mobile_theme'}) ?
3677 $gconfig{'mobile_theme'} :
3678 defined($gconfig{'theme_'.$remote_user}) ?
3679 $gconfig{'theme_'.$remote_user} :
3680 defined($gconfig{'theme_'.$base_remote_user}) ?
3681 $gconfig{'theme_'.$base_remote_user} :
3683 @current_themes = split(/\s+/, $current_themes);
3684 $current_theme = $current_themes[0];
3685 @theme_root_directories = map { "$root_directory/$_" } @current_themes;
3686 $theme_root_directory = $theme_root_directories[0];
3687 @theme_configs = ( );
3688 foreach my $troot (@theme_root_directories) {
3690 &read_file_cached("$troot/config", \%onetconfig);
3691 &read_file_cached("$troot/config", \%tconfig);
3692 push(@theme_configs, \%onetconfig);
3694 $tb = defined($tconfig{'cs_header'}) ? "bgcolor=#$tconfig{'cs_header'}" :
3695 defined($gconfig{'cs_header'}) ? "bgcolor=#$gconfig{'cs_header'}" :
3697 $cb = defined($tconfig{'cs_table'}) ? "bgcolor=#$tconfig{'cs_table'}" :
3698 defined($gconfig{'cs_table'}) ? "bgcolor=#$gconfig{'cs_table'}" :
3700 $tb .= ' '.$tconfig{'tb'} if ($tconfig{'tb'});
3701 $cb .= ' '.$tconfig{'cb'} if ($tconfig{'cb'});
3702 if ($tconfig{'preload_functions'}) {
3703 # Force load of theme functions right now, if requested
3704 &load_theme_library();
3706 if ($tconfig{'oofunctions'} && !$main::loaded_theme_oo_library++) {
3707 # Load the theme's Webmin:: package classes
3708 do "$theme_root_directory/$tconfig{'oofunctions'}";
3713 $webmin_logfile = $gconfig{'webmin_log'} ? $gconfig{'webmin_log'}
3714 : "$var_directory/webmin.log";
3716 # Load language strings into %text
3717 my @langs = &list_languages();
3719 if ($gconfig{'acceptlang'}) {
3720 foreach my $a (split(/,/, $ENV{'HTTP_ACCEPT_LANGUAGE'})) {
3721 my ($al) = grep { $_->{'lang'} eq $a } @langs;
3723 $accepted_lang = $al->{'lang'};
3728 $current_lang = $force_lang ? $force_lang :
3729 $accepted_lang ? $accepted_lang :
3730 $gconfig{"lang_$remote_user"} ? $gconfig{"lang_$remote_user"} :
3731 $gconfig{"lang_$base_remote_user"} ? $gconfig{"lang_$base_remote_user"} :
3732 $gconfig{"lang"} ? $gconfig{"lang"} : $default_lang;
3733 foreach my $l (@langs) {
3734 $current_lang_info = $l if ($l->{'lang'} eq $current_lang);
3736 @lang_order_list = &unique($default_lang,
3737 split(/:/, $current_lang_info->{'fallback'}),
3739 %text = &load_language($module_name);
3740 %text || &error("Failed to determine Webmin root from SERVER_ROOT, SCRIPT_FILENAME or the full command line");
3742 # Get the %module_info for this module
3744 my ($mi) = grep { $_->{'dir'} eq $module_name }
3745 &get_all_module_infos(2);
3746 %module_info = %$mi;
3747 $module_root_directory = &module_root_directory($module_name);
3750 if ($module_name && !$main::no_acl_check &&
3751 !defined($ENV{'FOREIGN_MODULE_NAME'})) {
3752 # Check if the HTTP user can access this module
3753 if (!&foreign_available($module_name)) {
3754 if (!&foreign_check($module_name)) {
3755 &error(&text('emodulecheck',
3756 "<i>$module_info{'desc'}</i>"));
3759 &error(&text('emodule', "<i>$u</i>",
3760 "<i>$module_info{'desc'}</i>"));
3763 $main::no_acl_check++;
3766 # Check the Referer: header for nasty redirects
3767 my @referers = split(/\s+/, $gconfig{'referers'});
3769 if ($ENV{'HTTP_REFERER'} =~/^(http|https|ftp):\/\/([^:\/]+:[^@\/]+@)?([^\/:@]+)/) {
3772 my $http_host = $ENV{'HTTP_HOST'};
3773 $http_host =~ s/:\d+$//;
3774 my $unsafe_index = $unsafe_index_cgi ||
3775 &get_module_variable('$unsafe_index_cgi');
3777 ($ENV{'SCRIPT_NAME'} !~ /^\/(index.cgi)?$/ || $unsafe_index) &&
3778 ($ENV{'SCRIPT_NAME'} !~ /^\/([a-z0-9\_\-]+)\/(index.cgi)?$/i ||
3780 $0 !~ /(session_login|pam_login)\.cgi$/ && !$gconfig{'referer'} &&
3781 $ENV{'MINISERV_CONFIG'} && !$main::no_referers_check &&
3782 $ENV{'HTTP_USER_AGENT'} !~ /^Webmin/i &&
3783 ($referer_site && $referer_site ne $http_host &&
3784 &indexof($referer_site, @referers) < 0 ||
3785 !$referer_site && $gconfig{'referers_none'}) &&
3786 !$trust_unknown_referers &&
3787 !&get_module_variable('$trust_unknown_referers')) {
3788 # Looks like a link from elsewhere .. show an error
3789 &header($text{'referer_title'}, "", undef, 0, 1, 1);
3791 $prot = lc($ENV{'HTTPS'}) eq 'on' ? "https" : "http";
3792 my $url = "<tt>".&html_escape("$prot://$ENV{'HTTP_HOST'}$ENV{'REQUEST_URI'}")."</tt>";
3793 if ($referer_site) {
3795 print &text('referer_warn',
3796 "<tt>".&html_escape($ENV{'HTTP_REFERER'})."</tt>", $url);
3798 print &text('referer_fix1', &html_escape($http_host)),"<p>\n";
3799 print &text('referer_fix2', &html_escape($http_host)),"<p>\n";
3802 # No referer info given
3803 print &text('referer_warn_unknown', $url),"<p>\n";
3804 print &text('referer_fix1u'),"<p>\n";
3805 print &text('referer_fix2u'),"<p>\n";
3809 &footer("/", $text{'index'});
3812 $main::no_referers_check++;
3813 $main::completed_referers_check++;
3815 # Call theme post-init
3816 if (defined(&theme_post_init_config)) {
3817 &theme_post_init_config(@_);
3820 # Record that we have done the calling library in this package
3821 my ($callpkg, $lib) = caller();
3823 $main::done_foreign_require{$callpkg,$lib} = 1;
3825 # If a licence checking is enabled, do it now
3826 if ($gconfig{'licence_module'} && !$main::done_licence_module_check &&
3827 &foreign_check($gconfig{'licence_module'}) &&
3828 -r "$root_directory/$gconfig{'licence_module'}/licence_check.pl") {
3829 my $oldpwd = &get_current_dir();
3830 $main::done_licence_module_check++;
3831 $main::licence_module = $gconfig{'licence_module'};
3832 &foreign_require($main::licence_module, "licence_check.pl");
3833 ($main::licence_status, $main::licence_message) =
3834 &foreign_call($main::licence_module, "check_licence");
3838 # Export global variables to caller
3839 if ($main::export_to_caller) {
3840 foreach my $v ('$config_file', '%gconfig', '$null_file',
3841 '$path_separator', '@root_directories',
3842 '$root_directory', '$module_name',
3843 '$base_remote_user', '$remote_user',
3844 '$module_config_directory', '$module_config_file',
3845 '%config', '@current_themes', '$current_theme',
3846 '@theme_root_directories', '$theme_root_directory',
3847 '%tconfig','@theme_configs', '$tb', '$cb', '$scriptname',
3848 '$webmin_logfile', '$current_lang',
3849 '$current_lang_info', '@lang_order_list', '%text',
3850 '%module_info', '$module_root_directory') {
3851 my ($vt, $vn) = split('', $v, 2);
3852 eval "${vt}${callpkg}::${vn} = ${vt}${vn}";
3859 =head2 load_language([module], [directory])
3861 Returns a hashtable mapping text codes to strings in the appropriate language,
3862 based on the $current_lang global variable, which is in turn set based on
3863 the Webmin user's selection. The optional module parameter tells the function
3864 which module to load strings for, and defaults to the calling module. The
3865 optional directory parameter can be used to load strings from a directory
3868 In regular module development you will never need to call this function
3869 directly, as init_config calls it for you, and places the module's strings
3870 into the %text hash.
3876 my $root = $root_directory;
3877 my $ol = $gconfig{'overlang'};
3878 my ($dir) = ($_[1] || "lang");
3880 # Read global lang files
3881 foreach my $o (@lang_order_list) {
3882 my $ok = &read_file_cached("$root/$dir/$o", \%text);
3883 return () if (!$ok && $o eq $default_lang);
3886 foreach my $o (@lang_order_list) {
3887 &read_file_cached("$root/$ol/$o", \%text);
3890 &read_file_cached("$config_directory/custom-lang", \%text);
3893 # Read module's lang files
3894 my $mdir = &module_root_directory($_[0]);
3895 foreach my $o (@lang_order_list) {
3896 &read_file_cached("$mdir/$dir/$o", \%text);
3899 foreach $o (@lang_order_list) {
3900 &read_file_cached("$mdir/$ol/$o", \%text);
3903 &read_file_cached("$config_directory/$_[0]/custom-lang", \%text);
3905 foreach $k (keys %text) {
3906 $text{$k} =~ s/\$(\{([^\}]+)\}|([A-Za-z0-9\.\-\_]+))/text_subs($2 || $3,\%text)/ge;
3909 if (defined(&theme_load_language)) {
3910 &theme_load_language(\%text, $_[0]);
3915 =head2 text_subs(string)
3917 Used internally by load_language to expand $code substitutions in language
3923 if (substr($_[0], 0, 8) eq "include:") {
3926 open(INCLUDE, substr($_[0], 8));
3934 my $t = $_[1]->{$_[0]};
3935 return defined($t) ? $t : '$'.$_[0];
3939 =head2 text(message, [substitute]+)
3941 Returns a translated message from %text, but with $1, $2, etc.. replaced with
3942 the substitute parameters. This makes it easy to use strings with placeholders
3943 that get replaced with programmatically generated text. For example :
3945 print &text('index_hello', $remote_user),"<p>\n";
3950 my $t = &get_module_variable('%text', 1);
3951 my $rv = exists($t->{$_[0]}) ? $t->{$_[0]} : $text{$_[0]};
3952 for(my $i=1; $i<@_; $i++) {
3953 $rv =~ s/\$$i/$_[$i]/g;
3958 =head2 encode_base64(string)
3960 Encodes a string into base64 format, for use in MIME email or HTTP
3961 authorization headers.
3967 pos($_[0]) = 0; # ensure start at the beginning
3968 while ($_[0] =~ /(.{1,57})/gs) {
3969 $res .= substr(pack('u57', $1), 1)."\n";
3972 $res =~ tr|\` -_|AA-Za-z0-9+/|;
3973 my $padding = (3 - length($_[0]) % 3) % 3;
3974 $res =~ s/.{$padding}$/'=' x $padding/e if ($padding);
3978 =head2 decode_base64(string)
3980 Converts a base64-encoded string into plain text. The opposite of encode_base64.
3987 $str =~ tr|A-Za-z0-9+=/||cd; # remove non-base64 chars
3988 if (length($str) % 4) {
3991 $str =~ s/=+$//; # remove padding
3992 $str =~ tr|A-Za-z0-9+/| -_|; # convert to uuencoded format
3993 while ($str =~ /(.{1,60})/gs) {
3994 my $len = chr(32 + length($1)*3/4); # compute length byte
3995 $res .= unpack("u", $len . $1 ); # uudecode
4000 =head2 get_module_info(module, [noclone], [forcache])
4002 Returns a hash containg details of the given module. Some useful keys are :
4004 =item dir - The module directory, like sendmail.
4006 =item desc - Human-readable description, in the current users' language.
4008 =item version - Optional module version number.
4010 =item os_support - List of supported operating systems and versions.
4012 =item category - Category on Webmin's left menu, like net.
4017 return () if ($_[0] =~ /^\./);
4018 my (%rv, $clone, $o);
4019 my $mdir = &module_root_directory($_[0]);
4020 &read_file_cached("$mdir/module.info", \%rv) || return ();
4022 foreach $o (@lang_order_list) {
4023 $rv{"desc"} = $rv{"desc_$o"} if ($rv{"desc_$o"});
4024 $rv{"longdesc"} = $rv{"longdesc_$o"} if ($rv{"longdesc_$o"});
4026 if ($clone && !$_[1] && $config_directory) {
4027 $rv{'clone'} = $rv{'desc'};
4028 &read_file("$config_directory/$_[0]/clone", \%rv);
4031 my %module_categories;
4032 &read_file_cached("$config_directory/webmin.cats", \%module_categories);
4033 my $pn = &get_product_name();
4034 if (defined($rv{'category_'.$pn})) {
4035 # Can override category for webmin/usermin
4036 $rv{'category'} = $rv{'category_'.$pn};
4038 $rv{'realcategory'} = $rv{'category'};
4039 $rv{'category'} = $module_categories{$_[0]}
4040 if (defined($module_categories{$_[0]}));
4042 # Apply description overrides
4043 $rv{'realdesc'} = $rv{'desc'};
4045 &read_file_cached("$config_directory/webmin.descs", \%descs);
4046 if ($descs{$_[0]." ".$current_lang}) {
4047 $rv{'desc'} = $descs{$_[0]." ".$current_lang};
4049 elsif ($descs{$_[0]}) {
4050 $rv{'desc'} = $descs{$_[0]};
4054 # Apply per-user description overridde
4055 my %gaccess = &get_module_acl(undef, "");
4056 if ($gaccess{'desc_'.$_[0]}) {
4057 $rv{'desc'} = $gaccess{'desc_'.$_[0]};
4061 if ($rv{'longdesc'}) {
4062 # All standard modules have an index.cgi
4063 $rv{'index_link'} = 'index.cgi';
4066 # Call theme-specific override function
4067 if (defined(&theme_get_module_info)) {
4068 %rv = &theme_get_module_info(\%rv, $_[0], $_[1], $_[2]);
4074 =head2 get_all_module_infos(cachemode)
4076 Returns a list contains the information on all modules in this webmin
4077 install, including clones. Uses caching to reduce the number of module.info
4078 files that need to be read. Each element of the array is a hash reference
4079 in the same format as returned by get_module_info. The cache mode flag can be :
4080 0 = read and write, 1 = don't read or write, 2 = read only
4083 sub get_all_module_infos
4087 # Is the cache out of date? (ie. have any of the root's changed?)
4088 my $cache_file = "$config_directory/module.infos.cache";
4090 if (&read_file_cached($cache_file, \%cache)) {
4091 foreach my $r (@root_directories) {
4093 if ($st[9] != $cache{'mtime_'.$r}) {
4103 if ($_[0] != 1 && !$changed && $cache{'lang'} eq $current_lang) {
4104 # Can use existing module.info cache
4106 foreach my $k (keys %cache) {
4107 if ($k =~ /^(\S+) (\S+)$/) {
4108 $mods{$1}->{$2} = $cache{$k};
4111 @rv = map { $mods{$_} } (keys %mods) if (%mods);
4114 # Need to rebuild cache
4116 foreach my $r (@root_directories) {
4118 foreach my $m (readdir(DIR)) {
4119 next if ($m =~ /^(config-|\.)/ || $m =~ /\.(cgi|pl)$/);
4120 my %minfo = &get_module_info($m, 0, 1);
4121 next if (!%minfo || !$minfo{'dir'});
4123 foreach $k (keys %minfo) {
4124 $cache{"${m} ${k}"} = $minfo{$k};
4129 $cache{'mtime_'.$r} = $st[9];
4131 $cache{'lang'} = $current_lang;
4132 &write_file($cache_file, \%cache) if (!$_[0] && $< == 0 && $> == 0);
4135 # Override descriptions for modules for current user
4136 my %gaccess = &get_module_acl(undef, "");
4137 foreach my $m (@rv) {
4138 if ($gaccess{"desc_".$m->{'dir'}}) {
4139 $m->{'desc'} = $gaccess{"desc_".$m->{'dir'}};
4143 # Apply installed flags
4145 &read_file_cached("$config_directory/installed.cache", \%installed);
4146 foreach my $m (@rv) {
4147 $m->{'installed'} = $installed{$m->{'dir'}};
4153 =head2 get_theme_info(theme)
4155 Returns a hash containing a theme's details, taken from it's theme.info file.
4156 Some useful keys are :
4158 =item dir - The theme directory, like blue-theme.
4160 =item desc - Human-readable description, in the current users' language.
4162 =item version - Optional module version number.
4164 =item os_support - List of supported operating systems and versions.
4169 return () if ($_[0] =~ /^\./);
4171 my $tdir = &module_root_directory($_[0]);
4172 &read_file("$tdir/theme.info", \%rv) || return ();
4173 foreach my $o (@lang_order_list) {
4174 $rv{"desc"} = $rv{"desc_$o"} if ($rv{"desc_$o"});
4180 =head2 list_languages
4182 Returns an array of supported languages, taken from Webmin's os_list.txt file.
4183 Each is a hash reference with the following keys :
4185 =item lang - The short language code, like es for Spanish.
4187 =item desc - A human-readable description, in English.
4189 =item charset - An optional character set to use when displaying the language.
4191 =item titles - Set to 1 only if Webmin has title images for the language.
4193 =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.
4198 if (!@main::list_languages_cache) {
4201 open(LANG, "$root_directory/lang_list.txt");
4203 if (/^(\S+)\s+(.*)/) {
4204 my $l = { 'desc' => $2 };
4205 foreach $o (split(/,/, $1)) {
4206 if ($o =~ /^([^=]+)=(.*)$/) {
4210 $l->{'index'} = scalar(@rv);
4211 push(@main::list_languages_cache, $l);
4215 @main::list_languages_cache = sort { $a->{'desc'} cmp $b->{'desc'} }
4216 @main::list_languages_cache;
4218 return @main::list_languages_cache;
4221 =head2 read_env_file(file, &hash)
4223 Similar to Webmin's read_file function, but handles files containing shell
4224 environment variables formatted like :
4229 The file parameter is the full path to the file to read, and hash a Perl hash
4230 ref to read names and values into.
4236 &open_readfile(FILE, $_[0]) || return 0;
4239 if (/^\s*(export\s*)?([A-Za-z0-9_\.]+)\s*=\s*"(.*)"/i ||
4240 /^\s*(export\s*)?([A-Za-z0-9_\.]+)\s*=\s*'(.*)'/i ||
4241 /^\s*(export\s*)?([A-Za-z0-9_\.]+)\s*=\s*(.*)/i) {
4249 =head2 write_env_file(file, &hash, [export])
4251 Writes out a hash to a file in name='value' format, suitable for use in a shell
4252 script. The parameters are :
4254 =item file - Full path for a file to write to
4256 =item hash - Hash reference of names and values to write.
4258 =item export - If set to 1, preceed each variable setting with the word 'export'.
4263 my $exp = $_[2] ? "export " : "";
4264 &open_tempfile(FILE, ">$_[0]");
4265 foreach my $k (keys %{$_[1]}) {
4266 my $v = $_[1]->{$k};
4267 if ($v =~ /^\S+$/) {
4268 &print_tempfile(FILE, "$exp$k=$v\n");
4271 &print_tempfile(FILE, "$exp$k=\"$v\"\n");
4274 &close_tempfile(FILE);
4277 =head2 lock_file(filename, [readonly], [forcefile])
4279 Lock a file for exclusive access. If the file is already locked, spin
4280 until it is freed. Uses a .lock file, which is not 100% reliable, but seems
4281 to work OK. The parameters are :
4283 =item filename - File or directory to lock.
4285 =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.
4287 =item forcefile - Force the file to be considered as a real file and not a symlink for Webmin actions logging purposes.
4292 my $realfile = &translate_filename($_[0]);
4293 return 0 if (!$_[0] || defined($main::locked_file_list{$realfile}));
4294 my $no_lock = !&can_lock_file($realfile);
4295 my $lock_tries_count = 0;
4298 if (!$no_lock && open(LOCKING, "$realfile.lock")) {
4303 if ($no_lock || !$pid || !kill(0, $pid) || $pid == $$) {
4306 # Create the .lock file
4307 open(LOCKING, ">$realfile.lock") || return 0;
4308 my $lck = eval "flock(LOCKING, 2+4)";
4310 # Lock of lock file failed! Wait till later
4313 print LOCKING $$,"\n";
4314 eval "flock(LOCKING, 8)";
4317 $main::locked_file_list{$realfile} = int($_[1]);
4318 push(@main::temporary_files, "$realfile.lock");
4319 if (($gconfig{'logfiles'} || $gconfig{'logfullfiles'}) &&
4320 !&get_module_variable('$no_log_file_changes') &&
4322 # Grab a copy of this file for later diffing
4324 $main::locked_file_data{$realfile} = undef;
4326 $main::locked_file_type{$realfile} = 1;
4327 $main::locked_file_data{$realfile} = '';
4329 elsif (!$_[2] && ($lnk = readlink($realfile))) {
4330 $main::locked_file_type{$realfile} = 2;
4331 $main::locked_file_data{$realfile} = $lnk;
4333 elsif (open(ORIGFILE, $realfile)) {
4334 $main::locked_file_type{$realfile} = 0;
4335 $main::locked_file_data{$realfile} = '';
4338 $main::locked_file_data{$realfile} .=$_;
4347 if ($lock_tries_count++ > 5*60) {
4348 # Give up after 5 minutes
4349 &error(&text('elock_tries', "<tt>$realfile</tt>", 5));
4355 =head2 unlock_file(filename)
4357 Release a lock on a file taken out by lock_file. If Webmin actions logging of
4358 file changes is enabled, then at unlock file a diff will be taken between the
4359 old and new contents, and stored under /var/webmin/diffs when webmin_log is
4360 called. This can then be viewed in the Webmin Actions Log module.
4365 my $realfile = &translate_filename($_[0]);
4366 return if (!$_[0] || !defined($main::locked_file_list{$realfile}));
4367 unlink("$realfile.lock") if (&can_lock_file($realfile));
4368 delete($main::locked_file_list{$realfile});
4369 if (exists($main::locked_file_data{$realfile})) {
4370 # Diff the new file with the old
4372 my $lnk = readlink($realfile);
4373 my $type = -d _ ? 1 : $lnk ? 2 : 0;
4374 my $oldtype = $main::locked_file_type{$realfile};
4375 my $new = !defined($main::locked_file_data{$realfile});
4376 if ($new && !-e _) {
4377 # file doesn't exist, and never did! do nothing ..
4379 elsif ($new && $type == 1 || !$new && $oldtype == 1) {
4380 # is (or was) a directory ..
4381 if (-d _ && !defined($main::locked_file_data{$realfile})) {
4382 push(@main::locked_file_diff,
4383 { 'type' => 'mkdir', 'object' => $realfile });
4385 elsif (!-d _ && defined($main::locked_file_data{$realfile})) {
4386 push(@main::locked_file_diff,
4387 { 'type' => 'rmdir', 'object' => $realfile });
4390 elsif ($new && $type == 2 || !$new && $oldtype == 2) {
4391 # is (or was) a symlink ..
4392 if ($lnk && !defined($main::locked_file_data{$realfile})) {
4393 push(@main::locked_file_diff,
4394 { 'type' => 'symlink', 'object' => $realfile,
4397 elsif (!$lnk && defined($main::locked_file_data{$realfile})) {
4398 push(@main::locked_file_diff,
4399 { 'type' => 'unsymlink', 'object' => $realfile,
4400 'data' => $main::locked_file_data{$realfile} });
4402 elsif ($lnk ne $main::locked_file_data{$realfile}) {
4403 push(@main::locked_file_diff,
4404 { 'type' => 'resymlink', 'object' => $realfile,
4409 # is a file, or has changed type?!
4410 my ($diff, $delete_file);
4411 my $type = "modify";
4413 open(NEWFILE, ">$realfile");
4418 if (!defined($main::locked_file_data{$realfile})) {
4421 open(ORIGFILE, ">$realfile.webminorig");
4422 print ORIGFILE $main::locked_file_data{$realfile};
4424 $diff = &backquote_command(
4425 "diff ".quotemeta("$realfile.webminorig")." ".
4426 quotemeta($realfile)." 2>/dev/null");
4427 push(@main::locked_file_diff,
4428 { 'type' => $type, 'object' => $realfile,
4429 'data' => $diff } ) if ($diff);
4430 unlink("$realfile.webminorig");
4431 unlink($realfile) if ($delete_file);
4434 if ($gconfig{'logfullfiles'}) {
4435 # Add file details to list of those to fully log
4436 $main::orig_file_data{$realfile} ||=
4437 $main::locked_file_data{$realfile};
4438 $main::orig_file_type{$realfile} ||=
4439 $main::locked_file_type{$realfile};
4442 delete($main::locked_file_data{$realfile});
4443 delete($main::locked_file_type{$realfile});
4447 =head2 test_lock(file)
4449 Returns 1 if some file is currently locked, 0 if not.
4454 my $realfile = &translate_filename($_[0]);
4455 return 0 if (!$_[0]);
4456 return 1 if (defined($main::locked_file_list{$realfile}));
4457 return 0 if (!&can_lock_file($realfile));
4459 if (open(LOCKING, "$realfile.lock")) {
4464 return $pid && kill(0, $pid);
4467 =head2 unlock_all_files
4469 Unlocks all files locked by the current script.
4472 sub unlock_all_files
4474 foreach $f (keys %main::locked_file_list) {
4479 =head2 can_lock_file(file)
4481 Returns 1 if some file should be locked, based on the settings in the
4482 Webmin Configuration module. For internal use by lock_file only.
4487 if (&is_readonly_mode()) {
4488 return 0; # never lock in read-only mode
4490 elsif ($gconfig{'lockmode'} == 0) {
4493 elsif ($gconfig{'lockmode'} == 1) {
4497 # Check if under any of the directories
4499 foreach my $d (split(/\t+/, $gconfig{'lockdirs'})) {
4500 if (&same_file($d, $_[0]) ||
4501 &is_under_directory($d, $_[0])) {
4505 return $gconfig{'lockmode'} == 2 ? $match : !$match;
4509 =head2 webmin_log(action, type, object, ¶ms, [module], [host, script-on-host, client-ip])
4511 Log some action taken by a user. This is typically called at the end of a
4512 script, once all file changes are complete and all commands run. The
4515 =item action - A short code for the action being performed, like 'create'.
4517 =item type - A code for the type of object the action is performed to, like 'user'.
4519 =item object - A short name for the object, like 'joe' if the Unix user 'joe' was just created.
4521 =item params - A hash ref of additional information about the action.
4523 =item module - Name of the module in which the action was performed, which defaults to the current module.
4525 =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.
4527 =item script-on-host - Script name like create_user.cgi on the host the action was performed on.
4529 =item client-ip - IP address of the browser that performed the action.
4534 return if (!$gconfig{'log'} || &is_readonly_mode());
4535 my $m = $_[4] ? $_[4] : &get_module_name();
4537 if ($gconfig{'logclear'}) {
4538 # check if it is time to clear the log
4539 my @st = stat("$webmin_logfile.time");
4540 my $write_logtime = 0;
4542 if ($st[9]+$gconfig{'logtime'}*60*60 < time()) {
4543 # clear logfile and all diff files
4544 &unlink_file("$ENV{'WEBMIN_VAR'}/diffs");
4545 &unlink_file("$ENV{'WEBMIN_VAR'}/files");
4546 &unlink_file("$ENV{'WEBMIN_VAR'}/annotations");
4547 unlink($webmin_logfile);
4554 if ($write_logtime) {
4555 open(LOGTIME, ">$webmin_logfile.time");
4556 print LOGTIME time(),"\n";
4561 # If an action script directory is defined, call the appropriate scripts
4562 if ($gconfig{'action_script_dir'}) {
4563 my ($action, $type, $object) = ($_[0], $_[1], $_[2]);
4564 my ($basedir) = $gconfig{'action_script_dir'};
4566 for my $dir ($basedir/$type/$action, $basedir/$type, $basedir) {
4569 opendir(DIR, $dir) or die "Can't open $dir: $!";
4570 while (defined($file = readdir(DIR))) {
4571 next if ($file =~ /^\.\.?$/); # skip '.' and '..'
4572 if (-x "$dir/$file") {
4573 # Call a script notifying it of the action
4575 $ENV{'ACTION_MODULE'} = &get_module_name();
4576 $ENV{'ACTION_ACTION'} = $_[0];
4577 $ENV{'ACTION_TYPE'} = $_[1];
4578 $ENV{'ACTION_OBJECT'} = $_[2];
4579 $ENV{'ACTION_SCRIPT'} = $script_name;
4580 foreach my $p (keys %param) {
4581 $ENV{'ACTION_PARAM_'.uc($p)} = $param{$p};
4583 system("$dir/$file", @_,
4584 "<$null_file", ">$null_file", "2>&1");
4592 # should logging be done at all?
4593 return if ($gconfig{'logusers'} && &indexof($base_remote_user,
4594 split(/\s+/, $gconfig{'logusers'})) < 0);
4595 return if ($gconfig{'logmodules'} && &indexof($m,
4596 split(/\s+/, $gconfig{'logmodules'})) < 0);
4600 my @tm = localtime($now);
4601 my $script_name = $0 =~ /([^\/]+)$/ ? $1 : '-';
4602 my $id = sprintf "%d.%d.%d", $now, $$, $main::action_id_count;
4603 $main::action_id_count++;
4604 my $line = sprintf "%s [%2.2d/%s/%4.4d %2.2d:%2.2d:%2.2d] %s %s %s %s %s \"%s\" \"%s\" \"%s\"",
4605 $id, $tm[3], $text{"smonth_".($tm[4]+1)}, $tm[5]+1900,
4606 $tm[2], $tm[1], $tm[0],
4607 $remote_user || '-',
4608 $main::session_id || '-',
4609 $_[7] || $ENV{'REMOTE_HOST'} || '-',
4610 $m, $_[5] ? "$_[5]:$_[6]" : $script_name,
4611 $_[0], $_[1] ne '' ? $_[1] : '-', $_[2] ne '' ? $_[2] : '-';
4613 foreach my $k (sort { $a cmp $b } keys %{$_[3]}) {
4614 my $v = $_[3]->{$k};
4620 elsif (ref($v) eq 'ARRAY') {
4624 $vv =~ s/(['"\\\r\n\t\%])/sprintf("%%%2.2X",ord($1))/ge;
4625 $line .= " $k='$vv'";
4629 foreach $vv (split(/\0/, $v)) {
4631 $vv =~ s/(['"\\\r\n\t\%])/sprintf("%%%2.2X",ord($1))/ge;
4632 $line .= " $k='$vv'";
4635 $param{$k} = join(" ", @pv);
4637 open(WEBMINLOG, ">>$webmin_logfile");
4638 print WEBMINLOG $line,"\n";
4640 if ($gconfig{'logperms'}) {
4641 chmod(oct($gconfig{'logperms'}), $webmin_logfile);
4644 chmod(0600, $webmin_logfile);
4647 if ($gconfig{'logfiles'} && !&get_module_variable('$no_log_file_changes')) {
4648 # Find and record the changes made to any locked files, or commands run
4650 mkdir("$ENV{'WEBMIN_VAR'}/diffs", 0700);
4651 foreach my $d (@main::locked_file_diff) {
4652 mkdir("$ENV{'WEBMIN_VAR'}/diffs/$id", 0700);
4653 open(DIFFLOG, ">$ENV{'WEBMIN_VAR'}/diffs/$id/$i");
4654 print DIFFLOG "$d->{'type'} $d->{'object'}\n";
4655 print DIFFLOG $d->{'data'};
4657 if ($d->{'input'}) {
4658 open(DIFFLOG, ">$ENV{'WEBMIN_VAR'}/diffs/$id/$i.input");
4659 print DIFFLOG $d->{'input'};
4662 if ($gconfig{'logperms'}) {
4663 chmod(oct($gconfig{'logperms'}),
4664 "$ENV{'WEBMIN_VAR'}/diffs/$id/$i",
4665 "$ENV{'WEBMIN_VAR'}/diffs/$id/$i.input");
4669 @main::locked_file_diff = undef;
4671 if ($gconfig{'logfullfiles'}) {
4672 # Save the original contents of any modified files
4674 mkdir("$ENV{'WEBMIN_VAR'}/files", 0700);
4675 foreach my $f (keys %main::orig_file_data) {
4676 mkdir("$ENV{'WEBMIN_VAR'}/files/$id", 0700);
4677 open(ORIGLOG, ">$ENV{'WEBMIN_VAR'}/files/$id/$i");
4678 if (!defined($main::orig_file_type{$f})) {
4679 print ORIGLOG -1," ",$f,"\n";
4682 print ORIGLOG $main::orig_file_type{$f}," ",$f,"\n";
4684 print ORIGLOG $main::orig_file_data{$f};
4686 if ($gconfig{'logperms'}) {
4687 chmod(oct($gconfig{'logperms'}),
4688 "$ENV{'WEBMIN_VAR'}/files/$id.$i");
4692 %main::orig_file_data = undef;
4693 %main::orig_file_type = undef;
4697 if ($gconfig{'logsyslog'}) {
4698 eval 'use Sys::Syslog qw(:DEFAULT setlogsock);
4699 openlog(&get_product_name(), "cons,pid,ndelay", "daemon");
4700 setlogsock("inet");';
4702 # Syslog module is installed .. try to convert to a
4703 # human-readable form
4705 my $mod = &get_module_name();
4706 my $mdir = module_root_directory($mod);
4707 if (-r "$mdir/log_parser.pl") {
4708 &foreign_require($mod, "log_parser.pl");
4710 foreach my $k (keys %{$_[3]}) {
4711 my $v = $_[3]->{$k};
4712 if (ref($v) eq 'ARRAY') {
4713 $params{$k} = join("\0", @$v);
4719 $msg = &foreign_call($mod, "parse_webmin_log",
4720 $remote_user, $script_name,
4721 $_[0], $_[1], $_[2], \%params);
4722 $msg =~ s/<[^>]*>//g; # Remove tags
4724 elsif ($_[0] eq "_config_") {
4725 my %wtext = &load_language("webminlog");
4726 $msg = $wtext{'search_config'};
4728 $msg ||= "$_[0] $_[1] $_[2]";
4729 my %info = &get_module_info($m);
4730 eval { syslog("info", "%s", "[$info{'desc'}] $msg"); };
4735 =head2 additional_log(type, object, data, [input])
4737 Records additional log data for an upcoming call to webmin_log, such
4738 as a command that was run or SQL that was executed. Typically you will never
4739 need to call this function directory.
4744 if ($gconfig{'logfiles'} && !&get_module_variable('$no_log_file_changes')) {
4745 push(@main::locked_file_diff,
4746 { 'type' => $_[0], 'object' => $_[1], 'data' => $_[2],
4747 'input' => $_[3] } );
4751 =head2 webmin_debug_log(type, message)
4753 Write something to the Webmin debug log. For internal use only.
4756 sub webmin_debug_log
4758 my ($type, $msg) = @_;
4759 return 0 if (!$main::opened_debug_log);
4760 return 0 if ($gconfig{'debug_no'.$main::webmin_script_type});
4761 if ($gconfig{'debug_modules'}) {
4762 my @dmods = split(/\s+/, $gconfig{'debug_modules'});
4763 return 0 if (&indexof($main::initial_module_name, @dmods) < 0);
4766 my @tm = localtime($now);
4768 "%s [%2.2d/%s/%4.4d %2.2d:%2.2d:%2.2d] %s %s %s %s \"%s\"",
4769 $$, $tm[3], $text{"smonth_".($tm[4]+1)}, $tm[5]+1900,
4770 $tm[2], $tm[1], $tm[0],
4771 $remote_user || "-",
4772 $ENV{'REMOTE_HOST'} || "-",
4773 &get_module_name() || "-",
4776 seek(main::DEBUGLOG, 0, 2);
4777 print main::DEBUGLOG $line."\n";
4781 =head2 system_logged(command)
4783 Just calls the Perl system() function, but also logs the command run.
4788 if (&is_readonly_mode()) {
4789 print STDERR "Vetoing command $_[0]\n";
4792 my @realcmd = ( &translate_command($_[0]), @_[1..$#_] );
4793 my $cmd = join(" ", @realcmd);
4795 if ($cmd =~ s/(\s*&\s*)$//) {
4798 while($cmd =~ s/(\d*)(<|>)((\/(tmp|dev)\S+)|&\d+)\s*$//) { }
4799 $cmd =~ s/^\((.*)\)\s*$/$1/;
4801 &additional_log('exec', undef, $cmd);
4802 return system(@realcmd);
4805 =head2 backquote_logged(command)
4807 Executes a command and returns the output (like `command`), but also logs it.
4810 sub backquote_logged
4812 if (&is_readonly_mode()) {
4814 print STDERR "Vetoing command $_[0]\n";
4817 my $realcmd = &translate_command($_[0]);
4820 if ($cmd =~ s/(\s*&\s*)$//) {
4823 while($cmd =~ s/(\d*)(<|>)((\/(tmp\/.webmin|dev)\S+)|&\d+)\s*$//) { }
4824 $cmd =~ s/^\((.*)\)\s*$/$1/;
4826 &additional_log('exec', undef, $cmd);
4827 &webmin_debug_log('CMD', "cmd=$cmd") if ($gconfig{'debug_what_cmd'});
4831 =head2 backquote_with_timeout(command, timeout, safe?, [maxlines])
4833 Runs some command, waiting at most the given number of seconds for it to
4834 complete, and returns the output. The maxlines parameter sets the number
4835 of lines of output to capture. The safe parameter should be set to 1 if the
4836 command is safe for read-only mode users to run.
4839 sub backquote_with_timeout
4841 my $realcmd = &translate_command($_[0]);
4842 &webmin_debug_log('CMD', "cmd=$realcmd timeout=$_[1]")
4843 if ($gconfig{'debug_what_cmd'});
4845 my $pid = &open_execute_command(OUT, "($realcmd) <$null_file", 1, $_[2]);
4850 my $elapsed = time() - $start;
4851 last if ($elapsed > $_[1]);
4853 vec($rmask, fileno(OUT), 1) = 1;
4854 my $sel = select($rmask, undef, undef, $_[1] - $elapsed);
4855 last if (!$sel || $sel < 0);
4857 last if (!defined($line));
4860 if ($_[3] && $linecount >= $_[3]) {
4865 if (kill('TERM', $pid) && time() - $start >= $_[1]) {
4869 return wantarray ? ($out, $timed_out) : $out;
4872 =head2 backquote_command(command, safe?)
4874 Executes a command and returns the output (like `command`), subject to
4875 command translation. The safe parameter should be set to 1 if the command
4876 is safe for read-only mode users to run.
4879 sub backquote_command
4881 if (&is_readonly_mode() && !$_[1]) {
4882 print STDERR "Vetoing command $_[0]\n";
4886 my $realcmd = &translate_command($_[0]);
4887 &webmin_debug_log('CMD', "cmd=$realcmd") if ($gconfig{'debug_what_cmd'});
4891 =head2 kill_logged(signal, pid, ...)
4893 Like Perl's built-in kill function, but also logs the fact that some process
4894 was killed. On Windows, falls back to calling process.exe to terminate a
4900 return scalar(@_)-1 if (&is_readonly_mode());
4901 &webmin_debug_log('KILL', "signal=$_[0] pids=".join(" ", @_[1..@_-1]))
4902 if ($gconfig{'debug_what_procs'});
4903 &additional_log('kill', $_[0], join(" ", @_[1..@_-1])) if (@_ > 1);
4904 if ($gconfig{'os_type'} eq 'windows') {
4905 # Emulate some kills with process.exe
4906 my $arg = $_[0] eq "KILL" ? "-k" :
4907 $_[0] eq "TERM" ? "-q" :
4908 $_[0] eq "STOP" ? "-s" :
4909 $_[0] eq "CONT" ? "-r" : undef;
4911 foreach my $p (@_[1..@_-1]) {
4913 $ok ||= kill($_[0], $p);
4916 &execute_command("process $arg $p");
4928 =head2 rename_logged(old, new)
4930 Re-names a file and logs the rename. If the old and new files are on different
4931 filesystems, calls mv or the Windows rename function to do the job.
4936 &additional_log('rename', $_[0], $_[1]) if ($_[0] ne $_[1]);
4937 return &rename_file($_[0], $_[1]);
4940 =head2 rename_file(old, new)
4942 Renames a file or directory. If the old and new files are on different
4943 filesystems, calls mv or the Windows rename function to do the job.
4948 if (&is_readonly_mode()) {
4949 print STDERR "Vetoing rename from $_[0] to $_[1]\n";
4952 my $src = &translate_filename($_[0]);
4953 my $dst = &translate_filename($_[1]);
4954 &webmin_debug_log('RENAME', "src=$src dst=$dst")
4955 if ($gconfig{'debug_what_ops'});
4956 my $ok = rename($src, $dst);
4957 if (!$ok && $! !~ /permission/i) {
4958 # Try the mv command, in case this is a cross-filesystem rename
4959 if ($gconfig{'os_type'} eq 'windows') {
4960 # Need to use rename
4961 my $out = &backquote_command("rename ".quotemeta($_[0]).
4962 " ".quotemeta($_[1])." 2>&1");
4964 $! = $out if (!$ok);
4968 my $out = &backquote_command("mv ".quotemeta($_[0]).
4969 " ".quotemeta($_[1])." 2>&1");
4971 $! = $out if (!$ok);
4977 =head2 symlink_logged(src, dest)
4979 Create a symlink, and logs it. Effectively does the same thing as the Perl
4986 my $rv = &symlink_file($_[0], $_[1]);
4987 &unlock_file($_[1]);
4991 =head2 symlink_file(src, dest)
4993 Creates a soft link, unless in read-only mode. Effectively does the same thing
4994 as the Perl symlink function.
4999 if (&is_readonly_mode()) {
5000 print STDERR "Vetoing symlink from $_[0] to $_[1]\n";
5003 my $src = &translate_filename($_[0]);
5004 my $dst = &translate_filename($_[1]);
5005 &webmin_debug_log('SYMLINK', "src=$src dst=$dst")
5006 if ($gconfig{'debug_what_ops'});
5007 return symlink($src, $dst);
5010 =head2 link_file(src, dest)
5012 Creates a hard link, unless in read-only mode. The existing new link file
5013 will be deleted if necessary. Effectively the same as Perl's link function.
5018 if (&is_readonly_mode()) {
5019 print STDERR "Vetoing link from $_[0] to $_[1]\n";
5022 my $src = &translate_filename($_[0]);
5023 my $dst = &translate_filename($_[1]);
5024 &webmin_debug_log('LINK', "src=$src dst=$dst")
5025 if ($gconfig{'debug_what_ops'});
5026 unlink($dst); # make sure link works
5027 return link($src, $dst);
5030 =head2 make_dir(dir, perms, recursive)
5032 Creates a directory and sets permissions on it, unless in read-only mode.
5033 The perms parameter sets the octal permissions to apply, which unlike Perl's
5034 mkdir will really get set. The recursive flag can be set to 1 to have the
5035 function create parent directories too.
5040 my ($dir, $perms, $recur) = @_;
5041 if (&is_readonly_mode()) {
5042 print STDERR "Vetoing directory $dir\n";
5045 $dir = &translate_filename($dir);
5046 my $exists = -d $dir ? 1 : 0;
5047 return 1 if ($exists && $recur); # already exists
5048 &webmin_debug_log('MKDIR', $dir) if ($gconfig{'debug_what_ops'});
5049 my $rv = mkdir($dir, $perms);
5050 if (!$rv && $recur) {
5051 # Failed .. try mkdir -p
5052 my $param = $gconfig{'os_type'} eq 'windows' ? "" : "-p";
5053 my $ex = &execute_command("mkdir $param "."e_path($dir));
5059 chmod($perms, $dir);
5064 =head2 set_ownership_permissions(user, group, perms, file, ...)
5066 Sets the user, group owner and permissions on some files. The parameters are :
5068 =item user - UID or username to change the file owner to. If undef, then the owner is not changed.
5070 =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.
5072 =item perms - Octal permissions set to set on the file. If undef, they are left alone.
5074 =item file - One or more files or directories to modify.
5077 sub set_ownership_permissions
5079 my ($user, $group, $perms, @files) = @_;
5080 if (&is_readonly_mode()) {
5081 print STDERR "Vetoing permission changes on ",join(" ", @files),"\n";
5084 @files = map { &translate_filename($_) } @files;
5085 if ($gconfig{'debug_what_ops'}) {
5086 foreach my $f (@files) {
5087 &webmin_debug_log('PERMS',
5088 "file=$f user=$user group=$group perms=$perms");
5092 if (defined($user)) {
5093 my $uid = $user !~ /^\d+$/ ? getpwnam($user) : $user;
5095 if (defined($group)) {
5096 $gid = $group !~ /^\d+$/ ? getgrnam($group) : $group;
5099 my @uinfo = getpwuid($uid);
5102 $rv = chown($uid, $gid, @files);
5104 if ($rv && defined($perms)) {
5105 $rv = chmod($perms, @files);
5110 =head2 unlink_logged(file, ...)
5112 Like Perl's unlink function, but locks the files beforehand and un-locks them
5113 after so that the deletion is logged by Webmin.
5119 foreach my $f (@_) {
5120 if (!&test_lock($f)) {
5125 my @rv = &unlink_file(@_);
5126 foreach my $f (@_) {
5131 return wantarray ? @rv : $rv[0];
5134 =head2 unlink_file(file, ...)
5136 Deletes some files or directories. Like Perl's unlink function, but also
5137 recursively deletes directories with the rm command if needed.
5142 return 1 if (&is_readonly_mode());
5145 foreach my $f (@_) {
5146 &unflush_file_lines($f);
5147 my $realf = &translate_filename($f);
5148 &webmin_debug_log('UNLINK', $realf) if ($gconfig{'debug_what_ops'});
5150 if (!rmdir($realf)) {
5152 if ($gconfig{'os_type'} eq 'windows') {
5153 # Call del and rmdir commands
5156 my $out = `del /q "$qm" 2>&1`;
5158 $out = `rmdir "$qm" 2>&1`;
5163 my $qm = quotemeta($realf);
5164 $out = `rm -rf $qm 2>&1`;
5173 if (!unlink($realf)) {
5179 return wantarray ? ($rv, $err) : $rv;
5182 =head2 copy_source_dest(source, dest)
5184 Copy some file or directory to a new location. Returns 1 on success, or 0
5185 on failure - also sets $! on failure. If the source is a directory, uses
5186 piped tar commands to copy a whole directory structure including permissions
5190 sub copy_source_dest
5192 return (1, undef) if (&is_readonly_mode());
5193 my ($src, $dst) = @_;
5196 &webmin_debug_log('COPY', "src=$src dst=$dst")
5197 if ($gconfig{'debug_what_ops'});
5198 if ($gconfig{'os_type'} eq 'windows') {
5199 # No tar or cp on windows, so need to use copy command
5203 $out = &backquote_logged("xcopy \"$src\" \"$dst\" /Y /E /I 2>&1");
5206 $out = &backquote_logged("copy /Y \"$src\" \"$dst\" 2>&1");
5214 # A directory .. need to copy with tar command
5215 my @st = stat($src);
5218 &set_ownership_permissions($st[4], $st[5], $st[2], $dst);
5219 $out = &backquote_logged("(cd ".quotemeta($src)." ; tar cf - . | (cd ".quotemeta($dst)." ; tar xf -)) 2>&1");
5226 # Can just copy with cp
5227 my $out = &backquote_logged("cp -p ".quotemeta($src).
5228 " ".quotemeta($dst)." 2>&1");
5234 return wantarray ? ($ok, $err) : $ok;
5237 =head2 remote_session_name(host|&server)
5239 Generates a session ID for some server. For this server, this will always
5240 be an empty string. For a server object it will include the hostname and
5241 port and PID. For a server name, it will include the hostname and PID. For
5245 sub remote_session_name
5247 return ref($_[0]) && $_[0]->{'host'} && $_[0]->{'port'} ?
5248 "$_[0]->{'host'}:$_[0]->{'port'}.$$" :
5249 $_[0] eq "" || ref($_[0]) && $_[0]->{'id'} == 0 ? "" :
5250 ref($_[0]) ? "" : "$_[0].$$";
5253 =head2 remote_foreign_require(server, module, file)
5255 Connects to rpc.cgi on a remote webmin server and have it open a session
5256 to a process that will actually do the require and run functions. This is the
5257 equivalent for foreign_require, but for a remote Webmin system. The server
5258 parameter can either be a hostname of a system registered in the Webmin
5259 Servers Index module, or a hash reference for a system from that module.
5262 sub remote_foreign_require
5264 my $call = { 'action' => 'require',
5267 my $sn = &remote_session_name($_[0]);
5268 if ($remote_session{$sn}) {
5269 $call->{'session'} = $remote_session{$sn};
5272 $call->{'newsession'} = 1;
5274 my $rv = &remote_rpc_call($_[0], $call);
5275 if ($rv->{'session'}) {
5276 $remote_session{$sn} = $rv->{'session'};
5277 $remote_session_server{$sn} = $_[0];
5281 =head2 remote_foreign_call(server, module, function, [arg]*)
5283 Call a function on a remote server. Must have been setup first with
5284 remote_foreign_require for the same server and module. Equivalent to
5285 foreign_call, but with the extra server parameter to specify the remote
5289 sub remote_foreign_call
5291 return undef if (&is_readonly_mode());
5292 my $sn = &remote_session_name($_[0]);
5293 return &remote_rpc_call($_[0], { 'action' => 'call',
5296 'session' => $remote_session{$sn},
5297 'args' => [ @_[3 .. $#_] ] } );
5300 =head2 remote_foreign_check(server, module, [api-only])
5302 Checks if some module is installed and supported on a remote server. Equivilant
5303 to foreign_check, but for the remote Webmin system specified by the server
5307 sub remote_foreign_check
5309 return &remote_rpc_call($_[0], { 'action' => 'check',
5314 =head2 remote_foreign_config(server, module)
5316 Gets the configuration for some module from a remote server, as a hash.
5317 Equivalent to foreign_config, but for a remote system.
5320 sub remote_foreign_config
5322 return &remote_rpc_call($_[0], { 'action' => 'config',
5323 'module' => $_[1] });
5326 =head2 remote_eval(server, module, code)
5328 Evaluates some perl code in the context of a module on a remote webmin server.
5329 The server parameter must be the hostname of a remote system, module must
5330 be a module directory name, and code a string of Perl code to run. This can
5331 only be called after remote_foreign_require for the same server and module.
5336 return undef if (&is_readonly_mode());
5337 my $sn = &remote_session_name($_[0]);
5338 return &remote_rpc_call($_[0], { 'action' => 'eval',
5341 'session' => $remote_session{$sn} });
5344 =head2 remote_write(server, localfile, [remotefile], [remotebasename])
5346 Transfers some local file to another server via Webmin's RPC protocol, and
5347 returns the resulting remote filename. If the remotefile parameter is given,
5348 that is the destination filename which will be used. Otherwise a randomly
5349 selected temporary filename will be used, and returned by the function.
5354 return undef if (&is_readonly_mode());
5356 my $sn = &remote_session_name($_[0]);
5357 if (!$_[0] || $remote_server_version{$sn} >= 0.966) {
5358 # Copy data over TCP connection
5359 my $rv = &remote_rpc_call($_[0], { 'action' => 'tcpwrite',
5361 'name' => $_[3] } );
5363 my $serv = ref($_[0]) ? $_[0]->{'host'} : $_[0];
5364 &open_socket($serv || "localhost", $rv->[1], TWRITE, \$error);
5365 return &$main::remote_error_handler("Failed to transfer file : $error")
5368 while(read(FILE, $got, 1024) > 0) {
5372 shutdown(TWRITE, 1);
5374 if ($error && $error !~ /^OK/) {
5375 # Got back an error!
5376 return &$main::remote_error_handler("Failed to transfer file : $error");
5382 # Just pass file contents as parameters
5384 while(read(FILE, $got, 1024) > 0) {
5388 return &remote_rpc_call($_[0], { 'action' => 'write',
5391 'session' => $remote_session{$sn} });
5395 =head2 remote_read(server, localfile, remotefile)
5397 Transfers a file from a remote server to this system, using Webmin's RPC
5398 protocol. The server parameter must be the hostname of a system registered
5399 in the Webmin Servers Index module, localfile is the destination path on this
5400 system, and remotefile is the file to fetch from the remote server.
5405 my $sn = &remote_session_name($_[0]);
5406 if (!$_[0] || $remote_server_version{$sn} >= 0.966) {
5407 # Copy data over TCP connection
5408 my $rv = &remote_rpc_call($_[0], { 'action' => 'tcpread',
5409 'file' => $_[2] } );
5411 return &$main::remote_error_handler("Failed to transfer file : $rv->[1]");
5414 my $serv = ref($_[0]) ? $_[0]->{'host'} : $_[0];
5415 &open_socket($serv || "localhost", $rv->[1], TREAD, \$error);
5416 return &$main::remote_error_handler("Failed to transfer file : $error")
5419 open(FILE, ">$_[1]");
5420 while(read(TREAD, $got, 1024) > 0) {
5427 # Just get data as return value
5428 my $d = &remote_rpc_call($_[0], { 'action' => 'read',
5430 'session' => $remote_session{$sn} });
5431 open(FILE, ">$_[1]");
5437 =head2 remote_finished
5439 Close all remote sessions. This happens automatically after a while
5440 anyway, but this function should be called to clean things up faster.
5445 foreach my $sn (keys %remote_session) {
5446 my $server = $remote_session_server{$sn};
5447 &remote_rpc_call($server, { 'action' => 'quit',
5448 'session' => $remote_session{$sn} } );
5449 delete($remote_session{$sn});
5450 delete($remote_session_server{$sn});
5452 foreach $fh (keys %fast_fh_cache) {
5454 delete($fast_fh_cache{$fh});
5458 =head2 remote_error_setup(&function)
5460 Sets a function to be called instead of &error when a remote RPC operation
5461 fails. Useful if you want to have more control over your remote operations.
5464 sub remote_error_setup
5466 $main::remote_error_handler = $_[0] || \&error;
5469 =head2 remote_rpc_call(server, structure)
5471 Calls rpc.cgi on some server and passes it a perl structure (hash,array,etc)
5472 and then reads back a reply structure. This is mainly for internal use only,
5473 and is called by the other remote_* functions.
5479 my $sn = &remote_session_name($_[0]); # Will be undef for local connection
5481 # Server structure was given
5483 $serv->{'user'} || $serv->{'id'} == 0 ||
5484 return &$main::remote_error_handler(
5485 "No Webmin login set for server");
5488 # lookup the server in the webmin servers module if needed
5489 if (!%main::remote_servers_cache) {
5490 &foreign_require("servers", "servers-lib.pl");
5491 foreach $s (&foreign_call("servers", "list_servers")) {
5492 $main::remote_servers_cache{$s->{'host'}} = $s;
5493 $main::remote_servers_cache{$s->{'host'}.":".$s->{'port'}} = $s;
5496 $serv = $main::remote_servers_cache{$_[0]};
5497 $serv || return &$main::remote_error_handler(
5498 "No Webmin Servers entry for $_[0]");
5499 $serv->{'user'} || return &$main::remote_error_handler(
5500 "No login set for server $_[0]");
5502 my $ip = $serv->{'ip'} || $serv->{'host'};
5504 # Work out the username and password
5506 if ($serv->{'sameuser'}) {
5507 $user = $remote_user;
5508 defined($main::remote_pass) || return &$main::remote_error_handler(
5509 "Password for this server is not available");
5510 $pass = $main::remote_pass;
5513 $user = $serv->{'user'};
5514 $pass = $serv->{'pass'};
5517 if ($serv->{'fast'} || !$sn) {
5518 # Make TCP connection call to fastrpc.cgi
5519 if (!$fast_fh_cache{$sn} && $sn) {
5520 # Need to open the connection
5521 my $con = &make_http_connection(
5522 $ip, $serv->{'port'}, $serv->{'ssl'},
5523 "POST", "/fastrpc.cgi");
5524 return &$main::remote_error_handler(
5525 "Failed to connect to $serv->{'host'} : $con")
5527 &write_http_connection($con, "Host: $serv->{'host'}\r\n");
5528 &write_http_connection($con, "User-agent: Webmin\r\n");
5529 my $auth = &encode_base64("$user:$pass");
5531 &write_http_connection($con, "Authorization: basic $auth\r\n");
5532 &write_http_connection($con, "Content-length: ",
5533 length($tostr),"\r\n");
5534 &write_http_connection($con, "\r\n");
5535 &write_http_connection($con, $tostr);
5537 # read back the response
5538 my $line = &read_http_connection($con);
5539 $line =~ tr/\r\n//d;
5540 if ($line =~ /^HTTP\/1\..\s+401\s+/) {
5541 return &$main::remote_error_handler("Login to RPC server as $user rejected");
5543 $line =~ /^HTTP\/1\..\s+200\s+/ ||
5544 return &$main::remote_error_handler("HTTP error : $line");
5546 $line = &read_http_connection($con);
5547 $line =~ tr/\r\n//d;
5549 $line = &read_http_connection($con);
5550 if ($line =~ /^0\s+(.*)/) {
5551 return &$main::remote_error_handler("RPC error : $1");
5553 elsif ($line =~ /^1\s+(\S+)\s+(\S+)\s+(\S+)/ ||
5554 $line =~ /^1\s+(\S+)\s+(\S+)/) {
5555 # Started ok .. connect and save SID
5556 &close_http_connection($con);
5557 my ($port, $sid, $version, $error) = ($1, $2, $3);
5558 &open_socket($ip, $port, $sid, \$error);
5559 return &$main::remote_error_handler("Failed to connect to fastrpc.cgi : $error")
5561 $fast_fh_cache{$sn} = $sid;
5562 $remote_server_version{$sn} = $version;
5565 while($stuff = &read_http_connection($con)) {
5568 return &$main::remote_error_handler("Bad response from fastrpc.cgi : $line");
5571 elsif (!$fast_fh_cache{$sn}) {
5572 # Open the connection by running fastrpc.cgi locally
5573 pipe(RPCOUTr, RPCOUTw);
5577 open(STDOUT, ">&RPCOUTw");
5581 $ENV{'REQUEST_METHOD'} = 'GET';
5582 $ENV{'SCRIPT_NAME'} = '/fastrpc.cgi';
5583 $ENV{'SERVER_ROOT'} ||= $root_directory;
5585 if ($base_remote_user ne 'root' &&
5586 $base_remote_user ne 'admin') {
5587 # Need to fake up a login for the CGI!
5588 &read_acl(undef, \%acl);
5589 $ENV{'BASE_REMOTE_USER'} =
5590 $ENV{'REMOTE_USER'} =
5591 $acl{'root'} ? 'root' : 'admin';
5593 delete($ENV{'FOREIGN_MODULE_NAME'});
5594 delete($ENV{'FOREIGN_ROOT_DIRECTORY'});
5595 chdir($root_directory);
5596 if (!exec("$root_directory/fastrpc.cgi")) {
5597 print "exec failed : $!\n";
5604 ($line = <RPCOUTr>) =~ tr/\r\n//d;
5608 if ($line =~ /^0\s+(.*)/) {
5609 return &$main::remote_error_handler("RPC error : $2");
5611 elsif ($line =~ /^1\s+(\S+)\s+(\S+)/) {
5612 # Started ok .. connect and save SID
5614 my ($port, $sid, $error) = ($1, $2, undef);
5615 &open_socket("localhost", $port, $sid, \$error);
5616 return &$main::remote_error_handler("Failed to connect to fastrpc.cgi : $error") if ($error);
5617 $fast_fh_cache{$sn} = $sid;
5624 &error("Bad response from fastrpc.cgi : $line");
5627 # Got a connection .. send off the request
5628 my $fh = $fast_fh_cache{$sn};
5629 my $tostr = &serialise_variable($_[1]);
5630 print $fh length($tostr)," $fh\n";
5632 my $rlen = int(<$fh>);
5633 my ($fromstr, $got);
5634 while(length($fromstr) < $rlen) {
5635 return &$main::remote_error_handler("Failed to read from fastrpc.cgi")
5636 if (read($fh, $got, $rlen - length($fromstr)) <= 0);
5639 my $from = &unserialise_variable($fromstr);
5641 return &$main::remote_error_handler("Remote Webmin error");
5643 if (defined($from->{'arv'})) {
5644 return @{$from->{'arv'}};
5647 return $from->{'rv'};
5651 # Call rpc.cgi on remote server
5652 my $tostr = &serialise_variable($_[1]);
5654 my $con = &make_http_connection($ip, $serv->{'port'},
5655 $serv->{'ssl'}, "POST", "/rpc.cgi");
5656 return &$main::remote_error_handler("Failed to connect to $serv->{'host'} : $con") if (!ref($con));
5658 &write_http_connection($con, "Host: $serv->{'host'}\r\n");
5659 &write_http_connection($con, "User-agent: Webmin\r\n");
5660 my $auth = &encode_base64("$user:$pass");
5662 &write_http_connection($con, "Authorization: basic $auth\r\n");
5663 &write_http_connection($con, "Content-length: ",length($tostr),"\r\n");
5664 &write_http_connection($con, "\r\n");
5665 &write_http_connection($con, $tostr);
5667 # read back the response
5668 my $line = &read_http_connection($con);
5669 $line =~ tr/\r\n//d;
5670 if ($line =~ /^HTTP\/1\..\s+401\s+/) {
5671 return &$main::remote_error_handler("Login to RPC server as $user rejected");
5673 $line =~ /^HTTP\/1\..\s+200\s+/ || return &$main::remote_error_handler("RPC HTTP error : $line");
5675 $line = &read_http_connection($con);
5676 $line =~ tr/\r\n//d;
5679 while($line = &read_http_connection($con)) {
5683 my $from = &unserialise_variable($fromstr);
5684 return &$main::remote_error_handler("Invalid RPC login to $serv->{'host'}") if (!$from->{'status'});
5685 if (defined($from->{'arv'})) {
5686 return @{$from->{'arv'}};
5689 return $from->{'rv'};
5694 =head2 remote_multi_callback(&servers, parallel, &function, arg|&args, &returns, &errors, [module, library])
5696 Executes some function in parallel on multiple servers at once. Fills in
5697 the returns and errors arrays respectively. If the module and library
5698 parameters are given, that module is remotely required on the server first,
5699 to check if it is connectable. The parameters are :
5701 =item servers - A list of Webmin system hash references.
5703 =item parallel - Number of parallel operations to perform.
5705 =item function - Reference to function to call for each system.
5707 =item args - Additional parameters to the function.
5709 =item returns - Array ref to place return values into, in same order as servers.
5711 =item errors - Array ref to place error messages into.
5713 =item module - Optional module to require on the remote system first.
5715 =item library - Optional library to require in the module.
5718 sub remote_multi_callback
5720 my ($servs, $parallel, $func, $args, $rets, $errs, $mod, $lib) = @_;
5721 &remote_error_setup(\&remote_multi_callback_error);
5723 # Call the functions
5725 foreach my $g (@$servs) {
5731 $remote_multi_callback_err = undef;
5733 # Require the remote lib
5734 &remote_foreign_require($g->{'host'}, $mod, $lib);
5735 if ($remote_multi_callback_err) {
5736 # Failed .. return error
5737 print $wh &serialise_variable(
5738 [ undef, $remote_multi_callback_err ]);
5744 my $a = ref($args) ? $args->[$p] : $args;
5745 my $rv = &$func($g, $a);
5748 print $wh &serialise_variable(
5749 [ $rv, $remote_multi_callback_err ]);
5757 # Read back the results
5759 foreach my $g (@$servs) {
5763 $errs->[$p] = "Failed to read response from $g->{'host'}";
5766 my $rv = &unserialise_variable($line);
5768 $rets->[$p] = $rv->[0];
5769 $errs->[$p] = $rv->[1];
5774 &remote_error_setup(undef);
5777 sub remote_multi_callback_error
5779 $remote_multi_callback_err = $_[0];
5782 =head2 serialise_variable(variable)
5784 Converts some variable (maybe a scalar, hash ref, array ref or scalar ref)
5785 into a url-encoded string. In the cases of arrays and hashes, it is recursively
5786 called on each member to serialize the entire object.
5789 sub serialise_variable
5791 if (!defined($_[0])) {
5797 $rv = &urlize($_[0]);
5799 elsif ($r eq 'SCALAR') {
5800 $rv = &urlize(${$_[0]});
5802 elsif ($r eq 'ARRAY') {
5803 $rv = join(",", map { &urlize(&serialise_variable($_)) } @{$_[0]});
5805 elsif ($r eq 'HASH') {
5806 $rv = join(",", map { &urlize(&serialise_variable($_)).",".
5807 &urlize(&serialise_variable($_[0]->{$_})) }
5810 elsif ($r eq 'REF') {
5811 $rv = &serialise_variable(${$_[0]});
5813 elsif ($r eq 'CODE') {
5818 # An object - treat as a hash
5819 $r = "OBJECT ".&urlize($r);
5820 $rv = join(",", map { &urlize(&serialise_variable($_)).",".
5821 &urlize(&serialise_variable($_[0]->{$_})) }
5824 return ($r ? $r : 'VAL').",".$rv;
5827 =head2 unserialise_variable(string)
5829 Converts a string created by serialise_variable() back into the original
5830 scalar, hash ref, array ref or scalar ref. If the original variable was a Perl
5831 object, the same class is used on this system, if available.
5834 sub unserialise_variable
5836 my @v = split(/,/, $_[0]);
5838 if ($v[0] eq 'VAL') {
5839 @v = split(/,/, $_[0], -1);
5840 $rv = &un_urlize($v[1]);
5842 elsif ($v[0] eq 'SCALAR') {
5843 local $r = &un_urlize($v[1]);
5846 elsif ($v[0] eq 'ARRAY') {
5848 for(my $i=1; $i<@v; $i++) {
5849 push(@$rv, &unserialise_variable(&un_urlize($v[$i])));
5852 elsif ($v[0] eq 'HASH') {
5854 for(my $i=1; $i<@v; $i+=2) {
5855 $rv->{&unserialise_variable(&un_urlize($v[$i]))} =
5856 &unserialise_variable(&un_urlize($v[$i+1]));
5859 elsif ($v[0] eq 'REF') {
5860 local $r = &unserialise_variable($v[1]);
5863 elsif ($v[0] eq 'UNDEF') {
5866 elsif ($v[0] =~ /^OBJECT\s+(.*)$/) {
5867 # An object hash that we have to re-bless
5870 for(my $i=1; $i<@v; $i+=2) {
5871 $rv->{&unserialise_variable(&un_urlize($v[$i]))} =
5872 &unserialise_variable(&un_urlize($v[$i+1]));
5880 =head2 other_groups(user)
5882 Returns a list of secondary groups a user is a member of, as a list of
5891 while(my @g = getgrent()) {
5892 my @m = split(/\s+/, $g[3]);
5893 push(@rv, $g[2]) if (&indexof($user, @m) >= 0);
5895 endgrent() if ($gconfig{'os_type'} ne 'hpux');
5899 =head2 date_chooser_button(dayfield, monthfield, yearfield)
5901 Returns HTML for a button that pops up a data chooser window. The parameters
5904 =item dayfield - Name of the text field to place the day of the month into.
5906 =item monthfield - Name of the select field to select the month of the year in, indexed from 1.
5908 =item yearfield - Name of the text field to place the year into.
5911 sub date_chooser_button
5913 return &theme_date_chooser_button(@_)
5914 if (defined(&theme_date_chooser_button));
5915 my ($w, $h) = (250, 225);
5916 if ($gconfig{'db_sizedate'}) {
5917 ($w, $h) = split(/x/, $gconfig{'db_sizedate'});
5919 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";
5922 =head2 help_file(module, file)
5924 Returns the path to a module's help file of some name, typically under the
5925 help directory with a .html extension.
5930 my $mdir = &module_root_directory($_[0]);
5931 my $dir = "$mdir/help";
5932 foreach my $o (@lang_order_list) {
5933 my $lang = "$dir/$_[1].$o.html";
5934 return $lang if (-r $lang);
5936 return "$dir/$_[1].html";
5941 Seeds the random number generator, if not already done in this script. On Linux
5942 this makes use of the current time, process ID and a read from /dev/urandom.
5943 On other systems, only the current time and process ID are used.
5948 if (!$main::done_seed_random) {
5949 if (open(RANDOM, "/dev/urandom")) {
5951 read(RANDOM, $buf, 4);
5953 srand(time() ^ $$ ^ $buf);
5958 $main::done_seed_random = 1;
5962 =head2 disk_usage_kb(directory)
5964 Returns the number of kB used by some directory and all subdirs. Implemented
5965 by calling the C<du -k> command.
5970 my $dir = &translate_filename($_[0]);
5972 my $ex = &execute_command("du -sk ".quotemeta($dir), undef, \$out, undef, 0, 1);
5974 &execute_command("du -s ".quotemeta($dir), undef, \$out, undef, 0, 1);
5976 return $out =~ /^([0-9]+)/ ? $1 : "???";
5979 =head2 recursive_disk_usage(directory, [skip-regexp], [only-regexp])
5981 Returns the number of bytes taken up by all files in some directory and all
5982 sub-directories, by summing up their lengths. The disk_usage_kb is more
5983 reflective of reality, as the filesystem typically pads file sizes to 1k or
5987 sub recursive_disk_usage
5989 my $dir = &translate_filename($_[0]);
5996 my @st = stat($dir);
6002 my @files = readdir(DIR);
6004 foreach my $f (@files) {
6005 next if ($f eq "." || $f eq "..");
6006 next if ($skip && $f =~ /$skip/);
6007 next if ($only && $f !~ /$only/);
6008 $rv += &recursive_disk_usage("$dir/$f", $skip, $only);
6014 =head2 help_search_link(term, [ section, ... ] )
6016 Returns HTML for a link to the man module for searching local and online
6017 docs for various search terms. The term parameter can either be a single
6018 word like 'bind', or a space-separated list of words. This function is typically
6019 used by modules that want to refer users to additional documentation in man
6020 pages or local system doc files.
6023 sub help_search_link
6025 if (&foreign_available("man") && !$tconfig{'nosearch'}) {
6026 my $for = &urlize(shift(@_));
6027 return "<a href='$gconfig{'webprefix'}/man/search.cgi?".
6028 join("&", map { "section=$_" } @_)."&".
6029 "for=$for&exact=1&check=".&get_module_name()."'>".
6030 $text{'helpsearch'}."</a>\n";
6037 =head2 make_http_connection(host, port, ssl, method, page, [&headers])
6039 Opens a connection to some HTTP server, maybe through a proxy, and returns
6040 a handle object. The handle can then be used to send additional headers
6041 and read back a response. If anything goes wrong, returns an error string.
6042 The parameters are :
6044 =item host - Hostname or IP address of the webserver to connect to.
6046 =item port - HTTP port number to connect to.
6048 =item ssl - Set to 1 to connect in SSL mode.
6050 =item method - HTTP method, like GET or POST.
6052 =item page - Page to request on the webserver, like /foo/index.html
6054 =item headers - Array ref of additional HTTP headers, each of which is a 2-element array ref.
6057 sub make_http_connection
6059 my ($host, $port, $ssl, $method, $page, $headers) = @_;
6062 foreach my $h (@$headers) {
6063 $htxt .= $h->[0].": ".$h->[1]."\r\n";
6067 if (&is_readonly_mode()) {
6068 return "HTTP connections not allowed in readonly mode";
6070 my $rv = { 'fh' => time().$$ };
6073 eval "use Net::SSLeay";
6074 $@ && return $text{'link_essl'};
6075 eval "Net::SSLeay::SSLeay_add_ssl_algorithms()";
6076 eval "Net::SSLeay::load_error_strings()";
6077 $rv->{'ssl_ctx'} = Net::SSLeay::CTX_new() ||
6078 return "Failed to create SSL context";
6079 $rv->{'ssl_con'} = Net::SSLeay::new($rv->{'ssl_ctx'}) ||
6080 return "Failed to create SSL connection";
6082 if ($gconfig{'http_proxy'} =~ /^http:\/\/(\S+):(\d+)/ &&
6083 !&no_proxy($host)) {
6086 &open_socket($1, $2, $rv->{'fh'}, \$error);
6089 my $fh = $rv->{'fh'};
6090 print $fh "CONNECT $host:$port HTTP/1.0\r\n";
6091 if ($gconfig{'proxy_user'}) {
6092 my $auth = &encode_base64(
6093 "$gconfig{'proxy_user'}:".
6094 "$gconfig{'proxy_pass'}");
6095 $auth =~ tr/\r\n//d;
6096 print $fh "Proxy-Authorization: Basic $auth\r\n";
6100 if ($line =~ /^HTTP(\S+)\s+(\d+)\s+(.*)/) {
6101 return "Proxy error : $3" if ($2 != 200);
6104 return "Proxy error : $line";
6109 elsif (!$gconfig{'proxy_fallback'}) {
6110 # Connection to proxy failed - give up
6117 &open_socket($host, $port, $rv->{'fh'}, \$error);
6118 return $error if ($error);
6120 Net::SSLeay::set_fd($rv->{'ssl_con'}, fileno($rv->{'fh'}));
6121 Net::SSLeay::connect($rv->{'ssl_con'}) ||
6122 return "SSL connect() failed";
6123 my $rtxt = "$method $page HTTP/1.0\r\n".$htxt;
6124 Net::SSLeay::write($rv->{'ssl_con'}, $rtxt);
6127 # Plain HTTP request
6129 if ($gconfig{'http_proxy'} =~ /^http:\/\/(\S+):(\d+)/ &&
6130 !&no_proxy($host)) {
6133 &open_socket($1, $2, $rv->{'fh'}, \$error);
6137 my $fh = $rv->{'fh'};
6138 my $rtxt = $method." ".
6139 "http://$host:$port$page HTTP/1.0\r\n";
6140 if ($gconfig{'proxy_user'}) {
6141 my $auth = &encode_base64(
6142 "$gconfig{'proxy_user'}:".
6143 "$gconfig{'proxy_pass'}");
6144 $auth =~ tr/\r\n//d;
6145 $rtxt .= "Proxy-Authorization: Basic $auth\r\n";
6150 elsif (!$gconfig{'proxy_fallback'}) {
6155 # Connecting directly
6157 &open_socket($host, $port, $rv->{'fh'}, \$error);
6158 return $error if ($error);
6159 my $fh = $rv->{'fh'};
6160 my $rtxt = "$method $page HTTP/1.0\r\n".$htxt;
6167 =head2 read_http_connection(&handle, [bytes])
6169 Reads either one line or up to the specified number of bytes from the handle,
6170 originally supplied by make_http_connection.
6173 sub read_http_connection
6177 if ($h->{'ssl_con'}) {
6180 while(($idx = index($h->{'buffer'}, "\n")) < 0) {
6181 # need to read more..
6182 if (!($more = Net::SSLeay::read($h->{'ssl_con'}))) {
6184 $rv = $h->{'buffer'};
6185 delete($h->{'buffer'});
6188 $h->{'buffer'} .= $more;
6190 $rv = substr($h->{'buffer'}, 0, $idx+1);
6191 $h->{'buffer'} = substr($h->{'buffer'}, $idx+1);
6194 if (length($h->{'buffer'})) {
6195 $rv = $h->{'buffer'};
6196 delete($h->{'buffer'});
6199 $rv = Net::SSLeay::read($h->{'ssl_con'}, $_[1]);
6205 read($h->{'fh'}, $rv, $_[1]) > 0 || return undef;
6208 my $fh = $h->{'fh'};
6212 $rv = undef if ($rv eq "");
6216 =head2 write_http_connection(&handle, [data+])
6218 Writes the given data to the given HTTP connection handle.
6221 sub write_http_connection
6224 my $fh = $h->{'fh'};
6226 if ($h->{'ssl_ctx'}) {
6227 foreach my $s (@_) {
6228 my $ok = Net::SSLeay::write($h->{'ssl_con'}, $s);
6229 $allok = 0 if (!$ok);
6233 my $ok = (print $fh @_);
6234 $allok = 0 if (!$ok);
6239 =head2 close_http_connection(&handle)
6241 Closes a connection to an HTTP server, identified by the given handle.
6244 sub close_http_connection
6250 =head2 clean_environment
6252 Deletes any environment variables inherited from miniserv so that they
6253 won't be passed to programs started by webmin. This is useful when calling
6254 programs that check for CGI-related environment variables and modify their
6255 behaviour, and to avoid passing sensitive variables to un-trusted programs.
6258 sub clean_environment
6260 %UNCLEAN_ENV = %ENV;
6261 foreach my $k (keys %ENV) {
6262 if ($k =~ /^(HTTP|VIRTUALSERVER|QUOTA|USERADMIN)_/) {
6266 foreach my $e ('WEBMIN_CONFIG', 'SERVER_NAME', 'CONTENT_TYPE', 'REQUEST_URI',
6267 'PATH_INFO', 'WEBMIN_VAR', 'REQUEST_METHOD', 'GATEWAY_INTERFACE',
6268 'QUERY_STRING', 'REMOTE_USER', 'SERVER_SOFTWARE', 'SERVER_PROTOCOL',
6269 'REMOTE_HOST', 'SERVER_PORT', 'DOCUMENT_ROOT', 'SERVER_ROOT',
6270 'MINISERV_CONFIG', 'SCRIPT_NAME', 'SERVER_ADMIN', 'CONTENT_LENGTH',
6271 'HTTPS', 'FOREIGN_MODULE_NAME', 'FOREIGN_ROOT_DIRECTORY',
6272 'SCRIPT_FILENAME', 'PATH_TRANSLATED', 'BASE_REMOTE_USER',
6273 'DOCUMENT_REALROOT', 'MINISERV_CONFIG', 'MYSQL_PWD',
6279 =head2 reset_environment
6281 Puts the environment back how it was before clean_environment was callled.
6284 sub reset_environment
6287 foreach my $k (keys %UNCLEAN_ENV) {
6288 $ENV{$k} = $UNCLEAN_ENV{$k};
6290 undef(%UNCLEAN_ENV);
6294 =head2 progress_callback
6296 Never called directly, but useful for passing to &http_download to print
6297 out progress of an HTTP request.
6300 sub progress_callback
6302 if (defined(&theme_progress_callback)) {
6303 # Call the theme override
6304 return &theme_progress_callback(@_);
6308 print $progress_callback_prefix;
6310 $progress_size = $_[1];
6311 $progress_step = int($_[1] / 10);
6312 print &text('progress_size2', $progress_callback_url,
6313 &nice_size($progress_size)),"<br>\n";
6316 print &text('progress_nosize', $progress_callback_url),"<br>\n";
6318 $last_progress_time = $last_progress_size = undef;
6320 elsif ($_[0] == 3) {
6322 my $sp = $progress_callback_prefix.(" " x 5);
6323 if ($progress_size) {
6324 # And we have a size to compare against
6325 my $st = int(($_[1] * 10) / $progress_size);
6326 my $time_now = time();
6327 if ($st != $progress_step ||
6328 $time_now - $last_progress_time > 60) {
6329 # Show progress every 10% or 60 seconds
6330 print $sp,&text('progress_datan', &nice_size($_[1]),
6331 int($_[1]*100/$progress_size)),"<br>\n";
6332 $last_progress_time = $time_now;
6334 $progress_step = $st;
6337 # No total size .. so only show in 100k jumps
6338 if ($_[1] > $last_progress_size+100*1024) {
6339 print $sp,&text('progress_data2n',
6340 &nice_size($_[1])),"<br>\n";
6341 $last_progress_size = $_[1];
6345 elsif ($_[0] == 4) {
6346 # All done downloading
6347 print $progress_callback_prefix,&text('progress_done'),"<br>\n";
6349 elsif ($_[0] == 5) {
6350 # Got new location after redirect
6351 $progress_callback_url = $_[1];
6353 elsif ($_[0] == 6) {
6355 $progress_callback_url = $_[1];
6356 print &text('progress_incache', $progress_callback_url),"<br>\n";
6360 =head2 switch_to_remote_user
6362 Changes the user and group of the current process to that of the unix user
6363 with the same name as the current webmin login, or fails if there is none.
6364 This should be called by Usermin module scripts that only need to run with
6365 limited permissions.
6368 sub switch_to_remote_user
6370 @remote_user_info = $remote_user ? getpwnam($remote_user) :
6372 @remote_user_info || &error(&text('switch_remote_euser', $remote_user));
6373 &create_missing_homedir(\@remote_user_info);
6375 &switch_to_unix_user(\@remote_user_info);
6376 $ENV{'USER'} = $ENV{'LOGNAME'} = $remote_user;
6377 $ENV{'HOME'} = $remote_user_info[7];
6379 # Export global variables to caller
6380 if ($main::export_to_caller) {
6381 my ($callpkg) = caller();
6382 eval "\@${callpkg}::remote_user_info = \@remote_user_info";
6386 =head2 switch_to_unix_user(&user-details)
6388 Switches the current process to the UID and group ID from the given list
6389 of user details, which must be in the format returned by getpwnam.
6392 sub switch_to_unix_user
6395 if (!defined($uinfo->[0])) {
6396 # No username given, so just use given GID
6397 ($(, $)) = ( $uinfo->[3], "$uinfo->[3] $uinfo->[3]" );
6400 # Use all groups from user
6401 ($(, $)) = ( $uinfo->[3],
6402 "$uinfo->[3] ".join(" ", $uinfo->[3],
6403 &other_groups($uinfo->[0])) );
6406 POSIX::setuid($uinfo->[2]);
6408 if ($< != $uinfo->[2] || $> != $uinfo->[2]) {
6409 ($>, $<) = ( $uinfo->[2], $uinfo->[2] );
6413 =head2 eval_as_unix_user(username, &code)
6415 Runs some code fragment with the effective UID and GID switch to that
6416 of the given Unix user, so that file IO takes place with his permissions.
6420 sub eval_as_unix_user
6422 my ($user, $code) = @_;
6423 my @uinfo = getpwnam($user);
6424 if (!scalar(@uinfo)) {
6425 &error("eval_as_unix_user called with invalid user $user");
6427 $) = $uinfo[3]." ".join(" ", $uinfo[3], &other_groups($user));
6431 local $main::error_must_die = 1;
6438 $err =~ s/\s+at\s+(\/\S+)\s+line\s+(\d+)\.?//;
6441 return wantarray ? @rv : $rv[0];
6444 =head2 create_user_config_dirs
6446 Creates per-user config directories and sets $user_config_directory and
6447 $user_module_config_directory to them. Also reads per-user module configs
6448 into %userconfig. This should be called by Usermin module scripts that need
6449 to store per-user preferences or other settings.
6452 sub create_user_config_dirs
6454 return if (!$gconfig{'userconfig'});
6455 my @uinfo = @remote_user_info ? @remote_user_info : getpwnam($remote_user);
6456 return if (!@uinfo || !$uinfo[7]);
6457 &create_missing_homedir(\@uinfo);
6458 $user_config_directory = "$uinfo[7]/$gconfig{'userconfig'}";
6459 if (!-d $user_config_directory) {
6460 mkdir($user_config_directory, 0700) ||
6461 &error("Failed to create $user_config_directory : $!");
6462 if ($< == 0 && $uinfo[2]) {
6463 chown($uinfo[2], $uinfo[3], $user_config_directory);
6466 if (&get_module_name()) {
6467 $user_module_config_directory = $user_config_directory."/".
6469 if (!-d $user_module_config_directory) {
6470 mkdir($user_module_config_directory, 0700) ||
6471 &error("Failed to create $user_module_config_directory : $!");
6472 if ($< == 0 && $uinfo[2]) {
6473 chown($uinfo[2], $uinfo[3], $user_config_directory);
6477 &read_file_cached("$module_root_directory/defaultuconfig",
6479 &read_file_cached("$module_config_directory/uconfig", \%userconfig);
6480 &read_file_cached("$user_module_config_directory/config",
6484 # Export global variables to caller
6485 if ($main::export_to_caller) {
6486 my ($callpkg) = caller();
6487 foreach my $v ('$user_config_directory',
6488 '$user_module_config_directory', '%userconfig') {
6489 my ($vt, $vn) = split('', $v, 2);
6490 eval "${vt}${callpkg}::${vn} = ${vt}${vn}";
6495 =head2 create_missing_homedir(&uinfo)
6497 If auto homedir creation is enabled, create one for this user if needed.
6498 For internal use only.
6501 sub create_missing_homedir
6504 if (!-e $uinfo->[7] && $gconfig{'create_homedir'}) {
6505 # Use has no home dir .. make one
6506 system("mkdir -p ".quotemeta($uinfo->[7]));
6507 chown($uinfo->[2], $uinfo->[3], $uinfo->[7]);
6508 if ($gconfig{'create_homedir_perms'} ne '') {
6509 chmod(oct($gconfig{'create_homedir_perms'}), $uinfo->[7]);
6514 =head2 filter_javascript(text)
6516 Disables all javascript <script>, onClick= and so on tags in the given HTML,
6517 and returns the new HTML. Useful for displaying HTML from an un-trusted source.
6520 sub filter_javascript
6523 $rv =~ s/<\s*script[^>]*>([\000-\377]*?)<\s*\/script\s*>//gi;
6524 $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;
6525 $rv =~ s/(javascript:)/x$1/gi;
6526 $rv =~ s/(vbscript:)/x$1/gi;
6530 =head2 resolve_links(path)
6532 Given a path that may contain symbolic links, returns the real path.
6538 $path =~ s/\/+/\//g;
6539 $path =~ s/\/$// if ($path ne "/");
6540 my @p = split(/\/+/, $path);
6542 for(my $i=0; $i<@p; $i++) {
6543 my $sofar = "/".join("/", @p[0..$i]);
6544 my $lnk = readlink($sofar);
6545 if ($lnk eq $sofar) {
6546 # Link to itself! Cannot do anything more really ..
6549 elsif ($lnk =~ /^\//) {
6550 # Link is absolute..
6551 return &resolve_links($lnk."/".join("/", @p[$i+1 .. $#p]));
6555 return &resolve_links("/".join("/", @p[0..$i-1])."/".$lnk."/".join("/", @p[$i+1 .. $#p]));
6561 =head2 simplify_path(path, bogus)
6563 Given a path, maybe containing elements ".." and "." , convert it to a
6564 clean, absolute form. Returns undef if this is not possible.
6572 my @bits = split(/\/+/, $dir);
6575 foreach my $b (@bits) {
6579 elsif ($b eq "..") {
6581 if (scalar(@fixedbits) == 0) {
6582 # Cannot! Already at root!
6589 push(@fixedbits, $b);
6592 return "/".join('/', @fixedbits);
6595 =head2 same_file(file1, file2)
6597 Returns 1 if two files are actually the same
6602 return 1 if ($_[0] eq $_[1]);
6603 return 0 if ($_[0] !~ /^\// || $_[1] !~ /^\//);
6604 my @stat1 = $stat_cache{$_[0]} ? @{$stat_cache{$_[0]}}
6605 : (@{$stat_cache{$_[0]}} = stat($_[0]));
6606 my @stat2 = $stat_cache{$_[1]} ? @{$stat_cache{$_[1]}}
6607 : (@{$stat_cache{$_[1]}} = stat($_[1]));
6608 return 0 if (!@stat1 || !@stat2);
6609 return $stat1[0] == $stat2[0] && $stat1[1] == $stat2[1];
6612 =head2 flush_webmin_caches
6614 Clears all in-memory and on-disk caches used by Webmin.
6617 sub flush_webmin_caches
6619 undef(%main::read_file_cache);
6620 undef(%main::acl_hash_cache);
6621 undef(%main::acl_array_cache);
6622 undef(%main::has_command_cache);
6623 undef(@main::list_languages_cache);
6624 undef($main::got_list_usermods_cache);
6625 undef(@main::list_usermods_cache);
6626 undef(%main::foreign_installed_cache);
6627 unlink("$config_directory/module.infos.cache");
6628 &get_all_module_infos();
6631 =head2 list_usermods
6633 Returns a list of additional module restrictions. For internal use in
6639 if (!$main::got_list_usermods_cache) {
6640 @main::list_usermods_cache = ( );
6642 open(USERMODS, "$config_directory/usermin.mods");
6644 if (/^([^:]+):(\+|-|):(.*)/) {
6645 push(@main::list_usermods_cache,
6646 [ $1, $2, [ split(/\s+/, $3) ] ]);
6650 $main::got_list_usermods_cache = 1;
6652 return @main::list_usermods_cache;
6655 =head2 available_usermods(&allmods, &usermods)
6657 Returns a list of modules that are available to the given user, based
6658 on usermod additional/subtractions. For internal use by Usermin only.
6661 sub available_usermods
6663 return @{$_[0]} if (!@{$_[1]});
6665 my %mods = map { $_->{'dir'}, 1 } @{$_[0]};
6666 my @uinfo = @remote_user_info;
6667 @uinfo = getpwnam($remote_user) if (!@uinfo);
6668 foreach my $u (@{$_[1]}) {
6670 if ($u->[0] eq "*" || $u->[0] eq $remote_user) {
6673 elsif ($u->[0] =~ /^\@(.*)$/) {
6674 # Check for group membership
6675 my @ginfo = getgrnam($1);
6676 $applies++ if (@ginfo && ($ginfo[2] == $uinfo[3] ||
6677 &indexof($remote_user, split(/\s+/, $ginfo[3])) >= 0));
6679 elsif ($u->[0] =~ /^\//) {
6680 # Check users and groups in file
6682 open(USERFILE, $u->[0]);
6685 if ($_ eq $remote_user) {
6688 elsif (/^\@(.*)$/) {
6689 my @ginfo = getgrnam($1);
6691 if (@ginfo && ($ginfo[2] == $uinfo[3] ||
6692 &indexof($remote_user,
6693 split(/\s+/, $ginfo[3])) >= 0));
6700 if ($u->[1] eq "+") {
6701 map { $mods{$_}++ } @{$u->[2]};
6703 elsif ($u->[1] eq "-") {
6704 map { delete($mods{$_}) } @{$u->[2]};
6708 map { $mods{$_}++ } @{$u->[2]};
6712 return grep { $mods{$_->{'dir'}} } @{$_[0]};
6715 =head2 get_available_module_infos(nocache)
6717 Returns a list of modules available to the current user, based on
6718 operating system support, access control and usermod restrictions. Useful
6719 in themes that need to display a list of modules the user can use.
6720 Each element of the returned array is a hash reference in the same format as
6721 returned by get_module_info.
6724 sub get_available_module_infos
6727 &read_acl(\%acl, \%uacl);
6728 my $risk = $gconfig{'risk_'.$base_remote_user};
6730 foreach my $minfo (&get_all_module_infos($_[0])) {
6731 next if (!&check_os_support($minfo));
6733 # Check module risk level
6734 next if ($risk ne 'high' && $minfo->{'risk'} &&
6735 $minfo->{'risk'} !~ /$risk/);
6739 next if (!$acl{$base_remote_user,$minfo->{'dir'}} &&
6740 !$acl{$base_remote_user,"*"});
6742 next if (&is_readonly_mode() && !$minfo->{'readonly'});
6746 # Check usermod restrictions
6747 my @usermods = &list_usermods();
6748 @rv = sort { $a->{'desc'} cmp $b->{'desc'} }
6749 &available_usermods(\@rv, \@usermods);
6751 # Check RBAC restrictions
6753 foreach my $m (@rv) {
6754 if (&supports_rbac($m->{'dir'}) &&
6755 &use_rbac_module_acl(undef, $m->{'dir'})) {
6756 local $rbacs = &get_rbac_module_acl($remote_user,
6764 # Module or system doesn't support RBAC
6765 push(@rbacrv, $m) if (!$gconfig{'rbacdeny_'.$base_remote_user});
6771 if (defined(&theme_foreign_available)) {
6772 foreach my $m (@rbacrv) {
6773 if (&theme_foreign_available($m->{'dir'})) {
6782 # Check licence module vetos
6784 if ($main::licence_module) {
6785 foreach my $m (@themerv) {
6786 if (&foreign_call($main::licence_module,
6787 "check_module_licence", $m->{'dir'})) {
6799 =head2 get_visible_module_infos(nocache)
6801 Like get_available_module_infos, but excludes hidden modules from the list.
6802 Each element of the returned array is a hash reference in the same format as
6803 returned by get_module_info.
6806 sub get_visible_module_infos
6809 my $pn = &get_product_name();
6810 return grep { !$_->{'hidden'} &&
6811 !$_->{$pn.'_hidden'} } &get_available_module_infos($nocache);
6814 =head2 get_visible_modules_categories(nocache)
6816 Returns a list of Webmin module categories, each of which is a hash ref
6817 with 'code', 'desc' and 'modules' keys. The modules value is an array ref
6818 of modules in the category, in the format returned by get_module_info.
6819 Un-used modules are automatically assigned to the 'unused' category, and
6820 those with no category are put into 'others'.
6823 sub get_visible_modules_categories
6826 my @mods = &get_visible_module_infos($nocache);
6828 if (&get_product_name() eq 'webmin') {
6829 @unmods = grep { $_->{'installed'} eq '0' } @mods;
6830 @mods = grep { $_->{'installed'} ne '0' } @mods;
6832 my %cats = &list_categories(\@mods);
6834 foreach my $c (keys %cats) {
6835 my $cat = { 'code' => $c || 'other',
6836 'desc' => $cats{$c} };
6837 $cat->{'modules'} = [ grep { $_->{'category'} eq $c } @mods ];
6840 @rv = sort { ($b->{'code'} eq "others" ? "" : $b->{'code'}) cmp
6841 ($a->{'code'} eq "others" ? "" : $a->{'code'}) } @rv;
6843 # Add un-installed modules in magic category
6844 my $cat = { 'code' => 'unused',
6845 'desc' => $text{'main_unused'},
6847 'modules' => \@unmods };
6853 =head2 is_under_directory(directory, file)
6855 Returns 1 if the given file is under the specified directory, 0 if not.
6856 Symlinks are taken into account in the file to find it's 'real' location.
6859 sub is_under_directory
6861 my ($dir, $file) = @_;
6862 return 1 if ($dir eq "/");
6863 return 0 if ($file =~ /\.\./);
6864 my $ld = &resolve_links($dir);
6866 return &is_under_directory($ld, $file);
6868 my $lp = &resolve_links($file);
6870 return &is_under_directory($dir, $lp);
6872 return 0 if (length($file) < length($dir));
6873 return 1 if ($dir eq $file);
6875 return substr($file, 0, length($dir)) eq $dir;
6878 =head2 parse_http_url(url, [basehost, baseport, basepage, basessl])
6880 Given an absolute URL, returns the host, port, page and ssl flag components.
6881 Relative URLs can also be parsed, if the base information is provided.
6886 if ($_[0] =~ /^(http|https):\/\/([^:\/]+)(:(\d+))?(\/\S*)?$/) {
6888 my $ssl = $1 eq 'https';
6889 return ($2, $3 ? $4 : $ssl ? 443 : 80, $5 || "/", $ssl);
6895 elsif ($_[0] =~ /^\/\S*$/) {
6896 # A relative to the server URL
6897 return ($_[1], $_[2], $_[0], $_[4]);
6900 # A relative to the directory URL
6902 $page =~ s/[^\/]+$//;
6903 return ($_[1], $_[2], $page.$_[0], $_[4]);
6907 =head2 check_clicks_function
6909 Returns HTML for a JavaScript function called check_clicks that returns
6910 true when first called, but false subsequently. Useful on onClick for
6911 critical buttons. Deprecated, as this method of preventing duplicate actions
6915 sub check_clicks_function
6920 function check_clicks(form)
6927 for(i=0; i<form.length; i++)
6928 form.elements[i].disabled = true;
6937 =head2 load_entities_map
6939 Returns a hash ref containing mappings between HTML entities (like ouml) and
6940 ascii values (like 246). Mainly for internal use.
6943 sub load_entities_map
6945 if (!%entities_map_cache) {
6947 open(EMAP, "$root_directory/entities_map.txt");
6949 if (/^(\d+)\s+(\S+)/) {
6950 $entities_map_cache{$2} = $1;
6955 return \%entities_map_cache;
6958 =head2 entities_to_ascii(string)
6960 Given a string containing HTML entities like ö and 7, replace them
6961 with their ASCII equivalents.
6964 sub entities_to_ascii
6967 my $emap = &load_entities_map();
6968 $str =~ s/&([a-z]+);/chr($emap->{$1})/ge;
6969 $str =~ s/&#(\d+);/chr($1)/ge;
6973 =head2 get_product_name
6975 Returns either 'webmin' or 'usermin', depending on which program the current
6976 module is in. Useful for modules that can be installed into either.
6979 sub get_product_name
6981 return $gconfig{'product'} if (defined($gconfig{'product'}));
6982 return defined($gconfig{'userconfig'}) ? 'usermin' : 'webmin';
6987 Returns the character set for the current language, such as iso-8859-1.
6992 my $charset = defined($gconfig{'charset'}) ? $gconfig{'charset'} :
6993 $current_lang_info->{'charset'} ?
6994 $current_lang_info->{'charset'} : $default_charset;
6998 =head2 get_display_hostname
7000 Returns the system's hostname for UI display purposes. This may be different
7001 from the actual hostname if you administrator has configured it so in the
7002 Webmin Configuration module.
7005 sub get_display_hostname
7007 if ($gconfig{'hostnamemode'} == 0) {
7008 return &get_system_hostname();
7010 elsif ($gconfig{'hostnamemode'} == 3) {
7011 return $gconfig{'hostnamedisplay'};
7014 my $h = $ENV{'HTTP_HOST'};
7016 if ($gconfig{'hostnamemode'} == 2) {
7017 $h =~ s/^(www|ftp|mail)\.//i;
7023 =head2 save_module_config([&config], [modulename])
7025 Saves the configuration for some module. The config parameter is an optional
7026 hash reference of names and values to save, which defaults to the global
7027 %config hash. The modulename parameter is the module to update the config
7028 file, which defaults to the current module.
7031 sub save_module_config
7033 my $c = $_[0] || { &get_module_variable('%config') };
7034 my $m = defined($_[1]) ? $_[1] : &get_module_name();
7035 &write_file("$config_directory/$m/config", $c);
7038 =head2 save_user_module_config([&config], [modulename])
7040 Saves the user's Usermin preferences for some module. The config parameter is
7041 an optional hash reference of names and values to save, which defaults to the
7042 global %userconfig hash. The modulename parameter is the module to update the
7043 config file, which defaults to the current module.
7046 sub save_user_module_config
7048 my $c = $_[0] || { &get_module_variable('%userconfig') };
7049 my $m = $_[1] || &get_module_name();
7050 my $ucd = $user_config_directory;
7052 my @uinfo = @remote_user_info ? @remote_user_info
7053 : getpwnam($remote_user);
7054 return if (!@uinfo || !$uinfo[7]);
7055 $ucd = "$uinfo[7]/$gconfig{'userconfig'}";
7057 &write_file("$ucd/$m/config", $c);
7060 =head2 nice_size(bytes, [min])
7062 Converts a number of bytes into a number followed by a suffix like GB, MB
7063 or kB. Rounding is to two decimal digits. The optional min parameter sets the
7064 smallest units to use - so you could pass 1024*1024 to never show bytes or kB.
7069 my ($units, $uname);
7070 if (abs($_[0]) > 1024*1024*1024*1024 || $_[1] >= 1024*1024*1024*1024) {
7071 $units = 1024*1024*1024*1024;
7074 elsif (abs($_[0]) > 1024*1024*1024 || $_[1] >= 1024*1024*1024) {
7075 $units = 1024*1024*1024;
7078 elsif (abs($_[0]) > 1024*1024 || $_[1] >= 1024*1024) {
7082 elsif (abs($_[0]) > 1024 || $_[1] >= 1024) {
7090 my $sz = sprintf("%.2f", ($_[0]*1.0 / $units));
7092 return $sz." ".$uname;
7095 =head2 get_perl_path
7097 Returns the path to Perl currently in use, such as /usr/bin/perl.
7102 if (open(PERL, "$config_directory/perl-path")) {
7108 return $^X if (-x $^X);
7109 return &has_command("perl");
7112 =head2 get_goto_module([&mods])
7114 Returns the details of a module that the current user should be re-directed
7115 to after logging in, or undef if none. Useful for themes.
7120 my @mods = $_[0] ? @{$_[0]} : &get_visible_module_infos();
7121 if ($gconfig{'gotomodule'}) {
7122 my ($goto) = grep { $_->{'dir'} eq $gconfig{'gotomodule'} } @mods;
7123 return $goto if ($goto);
7125 if (@mods == 1 && $gconfig{'gotoone'}) {
7131 =head2 select_all_link(field, form, [text])
7133 Returns HTML for a 'Select all' link that uses Javascript to select
7134 multiple checkboxes with the same name. The parameters are :
7136 =item field - Name of the checkbox inputs.
7138 =item form - Index of the form on the page.
7140 =item text - Message for the link, defaulting to 'Select all'.
7145 return &theme_select_all_link(@_) if (defined(&theme_select_all_link));
7146 my ($field, $form, $text) = @_;
7148 $text ||= $text{'ui_selall'};
7149 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>";
7152 =head2 select_invert_link(field, form, text)
7154 Returns HTML for an 'Invert selection' link that uses Javascript to invert the
7155 selection on multiple checkboxes with the same name. The parameters are :
7157 =item field - Name of the checkbox inputs.
7159 =item form - Index of the form on the page.
7161 =item text - Message for the link, defaulting to 'Invert selection'.
7164 sub select_invert_link
7166 return &theme_select_invert_link(@_) if (defined(&theme_select_invert_link));
7167 my ($field, $form, $text) = @_;
7169 $text ||= $text{'ui_selinv'};
7170 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>";
7173 =head2 select_rows_link(field, form, text, &rows)
7175 Returns HTML for a link that uses Javascript to select rows with particular
7176 values for their checkboxes. The parameters are :
7178 =item field - Name of the checkbox inputs.
7180 =item form - Index of the form on the page.
7182 =item text - Message for the link, de
7184 =item rows - Reference to an array of 1 or 0 values, indicating which rows to check.
7187 sub select_rows_link
7189 return &theme_select_rows_link(@_) if (defined(&theme_select_rows_link));
7190 my ($field, $form, $text, $rows) = @_;
7192 my $js = "var sel = { ".join(",", map { "\""."e_escape($_)."\":1" } @$rows)." }; ";
7193 $js .= "for(var i=0; i<document.forms[$form].${field}.length; i++) { var r = document.forms[$form].${field}[i]; r.checked = sel[r.value]; } ";
7194 $js .= "return false;";
7195 return "<a href='#' onClick='$js'>$text</a>";
7198 =head2 check_pid_file(file)
7200 Given a pid file, returns the PID it contains if the process is running.
7205 open(PIDFILE, $_[0]) || return undef;
7206 my $pid = <PIDFILE>;
7208 $pid =~ /^\s*(\d+)/ || return undef;
7209 kill(0, $1) || return undef;
7215 Return the local os-specific library name to this module. For internal use only.
7220 my $mn = &get_module_name();
7221 my $md = &module_root_directory($mn);
7222 if (-r "$md/$mn-$gconfig{'os_type'}-$gconfig{'os_version'}-lib.pl") {
7223 return "$mn-$gconfig{'os_type'}-$gconfig{'os_version'}-lib.pl";
7225 elsif (-r "$md/$mn-$gconfig{'os_type'}-lib.pl") {
7226 return "$mn-$gconfig{'os_type'}-lib.pl";
7228 elsif (-r "$md/$mn-generic-lib.pl") {
7229 return "$mn-generic-lib.pl";
7236 =head2 module_root_directory(module)
7238 Given a module name, returns its root directory. On a typical Webmin install,
7239 all modules are under the same directory - but it is theoretically possible to
7243 sub module_root_directory
7245 my $d = ref($_[0]) ? $_[0]->{'dir'} : $_[0];
7246 if (@root_directories > 1) {
7247 foreach my $r (@root_directories) {
7253 return "$root_directories[0]/$d";
7256 =head2 list_mime_types
7258 Returns a list of all known MIME types and their extensions, as a list of hash
7259 references with keys :
7261 =item type - The MIME type, like text/plain.
7263 =item exts - A list of extensions, like .doc and .avi.
7265 =item desc - A human-readable description for the MIME type.
7270 if (!@list_mime_types_cache) {
7272 open(MIME, "$root_directory/mime.types");
7276 if (s/#\s*(.*)$//g) {
7279 my ($type, @exts) = split(/\s+/);
7281 push(@list_mime_types_cache, { 'type' => $type,
7288 return @list_mime_types_cache;
7291 =head2 guess_mime_type(filename, [default])
7293 Given a file name like xxx.gif or foo.html, returns a guessed MIME type.
7294 The optional default parameter sets a default type of use if none is found,
7295 which defaults to application/octet-stream.
7300 if ($_[0] =~ /\.([A-Za-z0-9\-]+)$/) {
7302 foreach my $t (&list_mime_types()) {
7303 foreach my $e (@{$t->{'exts'}}) {
7304 return $t->{'type'} if (lc($e) eq lc($ext));
7308 return @_ > 1 ? $_[1] : "application/octet-stream";
7311 =head2 open_tempfile([handle], file, [no-error], [no-tempfile], [safe?])
7313 Opens a file handle for writing to a temporary file, which will only be
7314 renamed over the real file when the handle is closed. This allows critical
7315 files like /etc/shadow to be updated safely, even if writing fails part way
7316 through due to lack of disk space. The parameters are :
7318 =item handle - File handle to open, as you would use in Perl's open function.
7320 =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.
7322 =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.
7324 =item no-tempfile - If set to 1, writing will be direct to the file instead of using a temporary file.
7326 =item safe - Indicates to users in read-only mode that this write is safe and non-destructive.
7332 # Just getting a temp file
7333 if (!defined($main::open_tempfiles{$_[0]})) {
7334 $_[0] =~ /^(.*)\/(.*)$/ || return $_[0];
7335 my $dir = $1 || "/";
7336 my $tmp = "$dir/$2.webmintmp.$$";
7337 $main::open_tempfiles{$_[0]} = $tmp;
7338 push(@main::temporary_files, $tmp);
7340 return $main::open_tempfiles{$_[0]};
7344 my ($fh, $file, $noerror, $notemp, $safe) = @_;
7345 $fh = &callers_package($fh);
7347 my %gaccess = &get_module_acl(undef, "");
7348 my $db = $gconfig{'debug_what_write'};
7349 if ($file =~ /\r|\n|\0/) {
7350 if ($noerror) { return 0; }
7351 else { &error("Filename contains invalid characters"); }
7353 if (&is_readonly_mode() && $file =~ />/ && !$safe) {
7354 # Read-only mode .. veto all writes
7355 print STDERR "vetoing write to $file\n";
7356 return open($fh, ">$null_file");
7358 elsif ($file =~ /^(>|>>|)nul$/i) {
7359 # Write to Windows null device
7360 &webmin_debug_log($1 eq ">" ? "WRITE" :
7361 $1 eq ">>" ? "APPEND" : "READ", "nul") if ($db);
7363 elsif ($file =~ /^(>|>>)(\/dev\/.*)/ || lc($file) eq "nul") {
7364 # Writes to /dev/null or TTYs don't need to be handled
7365 &webmin_debug_log($1 eq ">" ? "WRITE" : "APPEND", $2) if ($db);
7366 return open($fh, $file);
7368 elsif ($file =~ /^>\s*(([a-zA-Z]:)?\/.*)$/ && !$notemp) {
7369 &webmin_debug_log("WRITE", $1) if ($db);
7370 # Over-writing a file, via a temp file
7372 $file = &translate_filename($file);
7374 # Open the link target instead
7375 $file = &resolve_links($file);
7378 # Cannot open a directory!
7379 if ($noerror) { return 0; }
7380 else { &error("Cannot write to directory $file"); }
7382 my $tmp = &open_tempfile($file);
7383 my $ex = open($fh, ">$tmp");
7384 if (!$ex && $! =~ /permission/i) {
7385 # Could not open temp file .. try opening actual file
7387 $ex = open($fh, ">$file");
7388 delete($main::open_tempfiles{$file});
7391 $main::open_temphandles{$fh} = $file;
7394 if (!$ex && !$noerror) {
7395 &error(&text("efileopen", $file, $!));
7399 elsif ($file =~ /^>\s*(([a-zA-Z]:)?\/.*)$/ && $notemp) {
7400 # Just writing direct to a file
7401 &webmin_debug_log("WRITE", $1) if ($db);
7403 $file = &translate_filename($file);
7404 my @old_attributes = &get_clear_file_attributes($file);
7405 my $ex = open($fh, ">$file");
7406 &reset_file_attributes($file, \@old_attributes);
7407 $main::open_temphandles{$fh} = $file;
7408 if (!$ex && !$noerror) {
7409 &error(&text("efileopen", $file, $!));
7414 elsif ($file =~ /^>>\s*(([a-zA-Z]:)?\/.*)$/) {
7415 # Appending to a file .. nothing special to do
7416 &webmin_debug_log("APPEND", $1) if ($db);
7418 $file = &translate_filename($file);
7419 my @old_attributes = &get_clear_file_attributes($file);
7420 my $ex = open($fh, ">>$file");
7421 &reset_file_attributes($file, \@old_attributes);
7422 $main::open_temphandles{$fh} = $file;
7423 if (!$ex && !$noerror) {
7424 &error(&text("efileopen", $file, $!));
7429 elsif ($file =~ /^([a-zA-Z]:)?\//) {
7430 # Read mode .. nothing to do here
7431 &webmin_debug_log("READ", $file) if ($db);
7432 $file = &translate_filename($file);
7433 return open($fh, $file);
7435 elsif ($file eq ">" || $file eq ">>") {
7436 my ($package, $filename, $line) = caller;
7437 if ($noerror) { return 0; }
7438 else { &error("Missing file to open at ${package}::${filename} line $line"); }
7441 my ($package, $filename, $line) = caller;
7442 &error("Unsupported file or mode $file at ${package}::${filename} line $line");
7447 =head2 close_tempfile(file|handle)
7449 Copies a temp file to the actual file, assuming that all writes were
7450 successful. The handle must have been one passed to open_tempfile.
7456 my $fh = &callers_package($_[0]);
7458 if (defined($file = $main::open_temphandles{$fh})) {
7460 close($fh) || &error(&text("efileclose", $file, $!));
7461 delete($main::open_temphandles{$fh});
7462 return &close_tempfile($file);
7464 elsif (defined($main::open_tempfiles{$_[0]})) {
7466 &webmin_debug_log("CLOSE", $_[0]) if ($gconfig{'debug_what_write'});
7467 my @st = stat($_[0]);
7468 if (&is_selinux_enabled() && &has_command("chcon")) {
7469 # Set original security context
7470 system("chcon --reference=".quotemeta($_[0]).
7471 " ".quotemeta($main::open_tempfiles{$_[0]}).
7472 " >/dev/null 2>&1");
7474 my @old_attributes = &get_clear_file_attributes($_[0]);
7475 rename($main::open_tempfiles{$_[0]}, $_[0]) || &error("Failed to replace $_[0] with $main::open_tempfiles{$_[0]} : $!");
7477 # Set original permissions and ownership
7478 chmod($st[2], $_[0]);
7479 chown($st[4], $st[5], $_[0]);
7481 &reset_file_attributes($_[0], \@old_attributes);
7482 delete($main::open_tempfiles{$_[0]});
7483 @main::temporary_files = grep { $_ ne $main::open_tempfiles{$_[0]} } @main::temporary_files;
7484 if ($main::open_templocks{$_[0]}) {
7485 &unlock_file($_[0]);
7486 delete($main::open_templocks{$_[0]});
7491 # Must be closing a handle not associated with a file
7497 =head2 print_tempfile(handle, text, ...)
7499 Like the normal print function, but calls &error on failure. Useful when
7500 combined with open_tempfile, to ensure that a criticial file is never
7501 only partially written.
7506 my ($fh, @args) = @_;
7507 $fh = &callers_package($fh);
7508 (print $fh @args) || &error(&text("efilewrite",
7509 $main::open_temphandles{$fh} || $fh, $!));
7512 =head2 is_selinux_enabled
7514 Returns 1 if SElinux is supported on this system and enabled, 0 if not.
7517 sub is_selinux_enabled
7519 if (!defined($main::selinux_enabled_cache)) {
7521 if ($gconfig{'os_type'} !~ /-linux$/) {
7522 # Not on linux, so no way
7523 $main::selinux_enabled_cache = 0;
7525 elsif (&read_env_file("/etc/selinux/config", \%seconfig)) {
7526 # Use global config file
7527 $main::selinux_enabled_cache =
7528 $seconfig{'SELINUX'} eq 'disabled' ||
7529 !$seconfig{'SELINUX'} ? 0 : 1;
7532 # Use selinuxenabled command
7533 #$selinux_enabled_cache =
7534 # system("selinuxenabled >/dev/null 2>&1") ? 0 : 1;
7535 $main::selinux_enabled_cache = 0;
7538 return $main::selinux_enabled_cache;
7541 =head2 get_clear_file_attributes(file)
7543 Finds file attributes that may prevent writing, clears them and returns them
7544 as a list. May call error. Mainly for internal use by open_tempfile and
7548 sub get_clear_file_attributes
7552 if ($gconfig{'chattr'}) {
7553 # Get original immutable bit
7554 my $out = &backquote_command(
7555 "lsattr ".quotemeta($file)." 2>/dev/null");
7557 $out =~ s/\s\S+\n//;
7558 @old_attributes = grep { $_ ne '-' } split(//, $out);
7560 if (&indexof("i", @old_attributes) >= 0) {
7561 my $err = &backquote_logged(
7562 "chattr -i ".quotemeta($file)." 2>&1");
7564 &error("Failed to remove immutable bit on ".
7569 return @old_attributes;
7572 =head2 reset_file_attributes(file, &attributes)
7574 Put back cleared attributes on some file. May call error. Mainly for internal
7575 use by close_tempfile.
7578 sub reset_file_attributes
7580 my ($file, $old_attributes) = @_;
7581 if (&indexof("i", @$old_attributes) >= 0) {
7582 my $err = &backquote_logged(
7583 "chattr +i ".quotemeta($file)." 2>&1");
7585 &error("Failed to restore immutable bit on ".
7591 =head2 cleanup_tempnames
7593 Remove all temporary files generated using transname. Typically only called
7594 internally when a Webmin script exits.
7597 sub cleanup_tempnames
7599 foreach my $t (@main::temporary_files) {
7602 @main::temporary_files = ( );
7605 =head2 open_lock_tempfile([handle], file, [no-error])
7607 Returns a temporary file for writing to some actual file, and also locks it.
7608 Effectively the same as calling lock_file and open_tempfile on the same file,
7609 but calls the unlock for you automatically when it is closed.
7612 sub open_lock_tempfile
7614 my ($fh, $file, $noerror, $notemp, $safe) = @_;
7615 $fh = &callers_package($fh);
7616 my $lockfile = $file;
7617 $lockfile =~ s/^[^\/]*//;
7618 if ($lockfile =~ /^\//) {
7619 $main::open_templocks{$lockfile} = &lock_file($lockfile);
7621 return &open_tempfile($fh, $file, $noerror, $notemp, $safe);
7626 $main::end_exit_status ||= $?;
7627 if ($$ == $main::initial_process_id) {
7628 # Exiting from initial process
7629 &cleanup_tempnames();
7630 if ($gconfig{'debug_what_start'} && $main::debug_log_start_time &&
7631 $main::debug_log_start_module eq &get_module_name()) {
7632 my $len = time() - $main::debug_log_start_time;
7633 &webmin_debug_log("STOP", "runtime=$len");
7634 $main::debug_log_start_time = 0;
7636 if (!$ENV{'SCRIPT_NAME'} &&
7637 $main::initial_module_name eq &get_module_name()) {
7638 # In a command-line script - call the real exit, so that the
7639 # exit status gets properly propogated. In some cases this
7640 # was not happening.
7641 exit($main::end_exit_status);
7646 =head2 month_to_number(month)
7648 Converts a month name like feb to a number like 1.
7653 return $month_to_number_map{lc(substr($_[0], 0, 3))};
7656 =head2 number_to_month(number)
7658 Converts a number like 1 to a month name like Feb.
7663 return ucfirst($number_to_month_map{$_[0]});
7666 =head2 get_rbac_module_acl(user, module)
7668 Returns a hash reference of RBAC overrides ACLs for some user and module.
7669 May return undef if none exist (indicating access denied), or the string *
7670 if full access is granted.
7673 sub get_rbac_module_acl
7675 my ($user, $mod) = @_;
7676 eval "use Authen::SolarisRBAC";
7677 return undef if ($@);
7680 if (Authen::SolarisRBAC::chkauth("webmin.$mod.admin", $user)) {
7681 # Automagic webmin.modulename.admin authorization exists .. allow access
7683 if (!Authen::SolarisRBAC::chkauth("webmin.$mod.config", $user)) {
7684 %rv = ( 'noconfig' => 1 );
7691 open(RBAC, &module_root_directory($mod)."/rbac-mapping");
7695 my ($auths, $acls) = split(/\s+/, $_);
7696 my @auths = split(/,/, $auths);
7698 my ($merge) = ($acls =~ s/^\+//);
7700 if ($auths eq "*") {
7701 # These ACLs apply to all RBAC users.
7702 # Only if there is some that match a specific authorization
7703 # later will they be used though.
7706 # Check each of the RBAC authorizations
7707 foreach my $a (@auths) {
7708 if (!Authen::SolarisRBAC::chkauth($a, $user)) {
7713 $foundany++ if ($gotall);
7716 # Found an RBAC authorization - return the ACLs
7717 return "*" if ($acls eq "*");
7718 my %acl = map { split(/=/, $_, 2) } split(/,/, $acls);
7720 # Just add to current set
7721 foreach my $a (keys %acl) {
7732 return !$foundany ? undef : %rv ? \%rv : undef;
7735 =head2 supports_rbac([module])
7737 Returns 1 if RBAC client support is available, such as on Solaris.
7742 return 0 if ($gconfig{'os_type'} ne 'solaris');
7743 eval "use Authen::SolarisRBAC";
7746 #return 0 if (!-r &module_root_directory($_[0])."/rbac-mapping");
7751 =head2 use_rbac_module_acl(user, module)
7753 Returns 1 if some user should use RBAC to get permissions for a module
7756 sub use_rbac_module_acl
7758 my $u = defined($_[0]) ? $_[0] : $base_remote_user;
7759 my $m = defined($_[1]) ? $_[1] : &get_module_name();
7760 return 1 if ($gconfig{'rbacdeny_'.$u}); # RBAC forced for user
7761 my %access = &get_module_acl($u, $m, 1);
7762 return $access{'rbac'} ? 1 : 0;
7765 =head2 execute_command(command, stdin, stdout, stderr, translate-files?, safe?)
7767 Runs some command, possibly feeding it input and capturing output to the
7768 give files or scalar references. The parameters are :
7770 =item command - Full command to run, possibly including shell meta-characters.
7772 =item stdin - File to read input from, or a scalar ref containing input, or undef if no input should be given.
7774 =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.
7776 =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.
7778 =item translate-files - Set to 1 to apply filename translation to any filenames. Usually has no effect.
7780 =item safe - Set to 1 if this command is safe and does not modify the state of the system.
7785 my ($cmd, $stdin, $stdout, $stderr, $trans, $safe) = @_;
7786 if (&is_readonly_mode() && !$safe) {
7787 print STDERR "Vetoing command $_[0]\n";
7791 $cmd = &translate_command($cmd);
7793 # Use ` operator where possible
7794 &webmin_debug_log('CMD', "cmd=$cmd") if ($gconfig{'debug_what_cmd'});
7795 if (!$stdin && ref($stdout) && !$stderr) {
7796 $cmd = "($cmd)" if ($gconfig{'os_type'} ne 'windows');
7797 $$stdout = `$cmd 2>$null_file`;
7800 elsif (!$stdin && ref($stdout) && $stdout eq $stderr) {
7801 $cmd = "($cmd)" if ($gconfig{'os_type'} ne 'windows');
7802 $$stdout = `$cmd 2>&1`;
7805 elsif (!$stdin && !$stdout && !$stderr) {
7806 $cmd = "($cmd)" if ($gconfig{'os_type'} ne 'windows');
7807 return system("$cmd >$null_file 2>$null_file <$null_file");
7811 $| = 1; # needed on some systems to flush before forking
7812 pipe(EXECSTDINr, EXECSTDINw);
7813 pipe(EXECSTDOUTr, EXECSTDOUTw);
7814 pipe(EXECSTDERRr, EXECSTDERRw);
7816 if (!($pid = fork())) {
7820 open(STDIN, "<&EXECSTDINr");
7821 open(STDOUT, ">&EXECSTDOUTw");
7822 if (ref($stderr) && $stderr eq $stdout) {
7823 open(STDERR, ">&EXECSTDOUTw");
7826 open(STDERR, ">&EXECSTDERRw");
7833 my $fullcmd = "($cmd)";
7834 if ($stdin && !ref($stdin)) {
7835 $fullcmd .= " <$stdin";
7837 if ($stdout && !ref($stdout)) {
7838 $fullcmd .= " >$stdout";
7840 if ($stderr && !ref($stderr)) {
7841 if ($stderr eq $stdout) {
7842 $fullcmd .= " 2>&1";
7845 $fullcmd .= " 2>$stderr";
7848 if ($gconfig{'os_type'} eq 'windows') {
7852 exec("/bin/sh", "-c", $fullcmd);
7854 print "Exec failed : $!\n";
7861 # Feed input and capture output
7863 if ($stdin && ref($stdin)) {
7864 print EXECSTDINw $$stdin;
7867 if ($stdout && ref($stdout)) {
7869 while(<EXECSTDOUTr>) {
7874 if ($stderr && ref($stderr) && $stderr ne $stdout) {
7876 while(<EXECSTDERRr>) {
7887 =head2 open_readfile(handle, file)
7889 Opens some file for reading. Returns 1 on success, 0 on failure. Pretty much
7890 exactly the same as Perl's open function.
7895 my ($fh, $file) = @_;
7896 $fh = &callers_package($fh);
7897 my $realfile = &translate_filename($file);
7898 &webmin_debug_log('READ', $file) if ($gconfig{'debug_what_read'});
7899 return open($fh, "<".$realfile);
7902 =head2 open_execute_command(handle, command, output?, safe?)
7904 Runs some command, with the specified file handle set to either write to it if
7905 in-or-out is set to 0, or read to it if output is set to 1. The safe flag
7906 indicates if the command modifies the state of the system or not.
7909 sub open_execute_command
7911 my ($fh, $cmd, $mode, $safe) = @_;
7912 $fh = &callers_package($fh);
7913 my $realcmd = &translate_command($cmd);
7914 if (&is_readonly_mode() && !$safe) {
7915 # Don't actually run it
7916 print STDERR "vetoing command $cmd\n";
7919 return open($fh, ">$null_file");
7922 return open($fh, $null_file);
7926 &webmin_debug_log('CMD', "cmd=$realcmd mode=$mode")
7927 if ($gconfig{'debug_what_cmd'});
7929 return open($fh, "| $cmd");
7931 elsif ($mode == 1) {
7932 return open($fh, "$cmd 2>$null_file |");
7934 elsif ($mode == 2) {
7935 return open($fh, "$cmd 2>&1 |");
7939 =head2 translate_filename(filename)
7941 Applies all relevant registered translation functions to a filename. Mostly
7942 for internal use, and typically does nothing.
7945 sub translate_filename
7947 my ($realfile) = @_;
7948 my @funcs = grep { $_->[0] eq &get_module_name() ||
7949 !defined($_->[0]) } @main::filename_callbacks;
7950 foreach my $f (@funcs) {
7952 $realfile = &$func($realfile, @{$f->[2]});
7957 =head2 translate_command(filename)
7959 Applies all relevant registered translation functions to a command. Mostly
7960 for internal use, and typically does nothing.
7963 sub translate_command
7966 my @funcs = grep { $_->[0] eq &get_module_name() ||
7967 !defined($_->[0]) } @main::command_callbacks;
7968 foreach my $f (@funcs) {
7970 $realcmd = &$func($realcmd, @{$f->[2]});
7975 =head2 register_filename_callback(module|undef, &function, &args)
7977 Registers some function to be called when the specified module (or all
7978 modules) tries to open a file for reading and writing. The function must
7979 return the actual file to open. This allows you to override which files
7980 other code actually operates on, via the translate_filename function.
7983 sub register_filename_callback
7985 my ($mod, $func, $args) = @_;
7986 push(@main::filename_callbacks, [ $mod, $func, $args ]);
7989 =head2 register_command_callback(module|undef, &function, &args)
7991 Registers some function to be called when the specified module (or all
7992 modules) tries to execute a command. The function must return the actual
7993 command to run. This allows you to override which commands other other code
7994 actually runs, via the translate_command function.
7997 sub register_command_callback
7999 my ($mod, $func, $args) = @_;
8000 push(@main::command_callbacks, [ $mod, $func, $args ]);
8003 =head2 capture_function_output(&function, arg, ...)
8005 Captures output that some function prints to STDOUT, and returns it. Useful
8006 for functions outside your control that print data when you really want to
8007 manipulate it before output.
8010 sub capture_function_output
8012 my ($func, @args) = @_;
8013 socketpair(SOCKET2, SOCKET1, AF_UNIX, SOCK_STREAM, PF_UNSPEC);
8014 my $old = select(SOCKET1);
8015 my @rv = &$func(@args);
8024 return wantarray ? ($out, \@rv) : $out;
8027 =head2 capture_function_output_tempfile(&function, arg, ...)
8029 Behaves the same as capture_function_output, but uses a temporary file
8030 to avoid buffer full problems.
8033 sub capture_function_output_tempfile
8035 my ($func, @args) = @_;
8036 my $temp = &transname();
8037 open(BUFFER, ">$temp");
8038 my $old = select(BUFFER);
8039 my @rv = &$func(@args);
8042 my $out = &read_file_contents($temp);
8043 &unlink_file($temp);
8044 return wantarray ? ($out, \@rv) : $out;
8047 =head2 modules_chooser_button(field, multiple, [form])
8049 Returns HTML for a button for selecting one or many Webmin modules.
8050 field - Name of the HTML field to place the module names into.
8051 multiple - Set to 1 if multiple modules can be selected.
8052 form - Index of the form on the page.
8055 sub modules_chooser_button
8057 return &theme_modules_chooser_button(@_)
8058 if (defined(&theme_modules_chooser_button));
8059 my $form = defined($_[2]) ? $_[2] : 0;
8060 my $w = $_[1] ? 700 : 500;
8062 if ($_[1] && $gconfig{'db_sizemodules'}) {
8063 ($w, $h) = split(/x/, $gconfig{'db_sizemodules'});
8065 elsif (!$_[1] && $gconfig{'db_sizemodule'}) {
8066 ($w, $h) = split(/x/, $gconfig{'db_sizemodule'});
8068 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";
8071 =head2 substitute_template(text, &hash)
8073 Given some text and a hash reference, for each ocurrance of $FOO or ${FOO} in
8074 the text replaces it with the value of the hash key foo. Also supports blocks
8075 like ${IF-FOO} ... ${ENDIF-FOO}, whose contents are only included if foo is
8076 non-zero, and ${IF-FOO} ... ${ELSE-FOO} ... ${ENDIF-FOO}.
8079 sub substitute_template
8081 # Add some extra fixed parameters to the hash
8082 my %hash = %{$_[1]};
8083 $hash{'hostname'} = &get_system_hostname();
8084 $hash{'webmin_config'} = $config_directory;
8085 $hash{'webmin_etc'} = $config_directory;
8086 $hash{'module_config'} = &get_module_variable('$module_config_directory');
8087 $hash{'webmin_var'} = $var_directory;
8089 # Add time-based parameters, for use in DNS
8090 $hash{'current_time'} = time();
8091 my @tm = localtime($hash{'current_time'});
8092 $hash{'current_year'} = $tm[5]+1900;
8093 $hash{'current_month'} = sprintf("%2.2d", $tm[4]+1);
8094 $hash{'current_day'} = sprintf("%2.2d", $tm[3]);
8095 $hash{'current_hour'} = sprintf("%2.2d", $tm[2]);
8096 $hash{'current_minute'} = sprintf("%2.2d", $tm[1]);
8097 $hash{'current_second'} = sprintf("%2.2d", $tm[0]);
8099 # Actually do the substition
8101 foreach my $s (keys %hash) {
8102 next if ($s eq ''); # Prevent just $ from being subbed
8105 $rv =~ s/\$\{\Q$us\E\}/$sv/g;
8106 $rv =~ s/\$\Q$us\E/$sv/g;
8108 # Replace ${IF}..${ELSE}..${ENDIF} block with first value,
8109 # and ${IF}..${ENDIF} with value
8110 $rv =~ s/\$\{IF-\Q$us\E\}(\n?)([\000-\377]*?)\$\{ELSE-\Q$us\E\}(\n?)([\000-\377]*?)\$\{ENDIF-\Q$us\E\}(\n?)/$2/g;
8111 $rv =~ s/\$\{IF-\Q$us\E\}(\n?)([\000-\377]*?)\$\{ENDIF-\Q$us\E\}(\n?)/$2/g;
8113 # Replace $IF..$ELSE..$ENDIF block with first value,
8114 # and $IF..$ENDIF with value
8115 $rv =~ s/\$IF-\Q$us\E(\n?)([\000-\377]*?)\$ELSE-\Q$us\E(\n?)([\000-\377]*?)\$ENDIF-\Q$us\E(\n?)/$2/g;
8116 $rv =~ s/\$IF-\Q$us\E(\n?)([\000-\377]*?)\$ENDIF-\Q$us\E(\n?)/$2/g;
8118 # Replace ${IFEQ}..${ENDIFEQ} block with first value if
8119 # matching, nothing if not
8120 $rv =~ s/\$\{IFEQ-\Q$us\E-\Q$sv\E\}(\n?)([\000-\377]*?)\$\{ENDIFEQ-\Q$us\E-\Q$sv\E\}(\n?)/$2/g;
8121 $rv =~ s/\$\{IFEQ-\Q$us\E-[^\}]+}(\n?)([\000-\377]*?)\$\{ENDIFEQ-\Q$us\E-[^\}]+\}(\n?)//g;
8123 # Replace $IFEQ..$ENDIFEQ block with first value if
8124 # matching, nothing if not
8125 $rv =~ s/\$IFEQ-\Q$us\E-\Q$sv\E(\n?)([\000-\377]*?)\$ENDIFEQ-\Q$us\E-\Q$sv\E(\n?)/$2/g;
8126 $rv =~ s/\$IFEQ-\Q$us\E-\S+(\n?)([\000-\377]*?)\$ENDIFEQ-\Q$us\E-\S+(\n?)//g;
8129 # Replace ${IF}..${ELSE}..${ENDIF} block with second value,
8130 # and ${IF}..${ENDIF} with nothing
8131 $rv =~ s/\$\{IF-\Q$us\E\}(\n?)([\000-\377]*?)\$\{ELSE-\Q$us\E\}(\n?)([\000-\377]*?)\$\{ENDIF-\Q$us\E\}(\n?)/$4/g;
8132 $rv =~ s/\$\{IF-\Q$us\E\}(\n?)([\000-\377]*?)\$\{ENDIF-\Q$us\E\}(\n?)//g;
8134 # Replace $IF..$ELSE..$ENDIF block with second value,
8135 # and $IF..$ENDIF with nothing
8136 $rv =~ s/\$IF-\Q$us\E(\n?)([\000-\377]*?)\$ELSE-\Q$us\E(\n?)([\000-\377]*?)\$ENDIF-\Q$us\E(\n?)/$4/g;
8137 $rv =~ s/\$IF-\Q$us\E(\n?)([\000-\377]*?)\$ENDIF-\Q$us\E(\n?)//g;
8139 # Replace ${IFEQ}..${ENDIFEQ} block with nothing
8140 $rv =~ s/\$\{IFEQ-\Q$us\E-[^\}]+}(\n?)([\000-\377]*?)\$\{ENDIFEQ-\Q$us\E-[^\}]+\}(\n?)//g;
8141 $rv =~ s/\$IFEQ-\Q$us\E-\S+(\n?)([\000-\377]*?)\$ENDIFEQ-\Q$us\E-\S+(\n?)//g;
8145 # Now assume any $IF blocks whose variables are not present in the hash
8146 # evaluate to false.
8147 # $IF...$ELSE x $ENDIF => x
8148 $rv =~ s/\$\{IF\-([A-Z]+)\}.*?\$\{ELSE\-\1\}(.*?)\$\{ENDIF\-\1\}/$2/gs;
8149 # $IF...x...$ENDIF => (nothing)
8150 $rv =~ s/\$\{IF\-([A-Z]+)\}.*?\$\{ENDIF\-\1\}//gs;
8151 # ${var} => (nothing)
8152 $rv =~ s/\$\{[A-Z]+\}//g;
8157 =head2 running_in_zone
8159 Returns 1 if the current Webmin instance is running in a Solaris zone. Used to
8160 disable module and features that are not appropriate, like those that modify
8161 mounted filesystems.
8166 return 0 if ($gconfig{'os_type'} ne 'solaris' ||
8167 $gconfig{'os_version'} < 10);
8168 my $zn = `zonename 2>$null_file`;
8170 return $zn && $zn ne "global";
8173 =head2 running_in_vserver
8175 Returns 1 if the current Webmin instance is running in a Linux VServer.
8176 Used to disable modules and features that are not appropriate.
8179 sub running_in_vserver
8181 return 0 if ($gconfig{'os_type'} !~ /^\*-linux$/);
8184 open(MTAB, "/etc/mtab");
8186 my ($dev, $mp) = split(/\s+/, $_);
8187 if ($mp eq "/" && $dev =~ /^\/dev\/hdv/) {
8196 =head2 running_in_xen
8198 Returns 1 if Webmin is running inside a Xen instance, by looking
8199 at /proc/xen/capabilities.
8204 return 0 if (!-r "/proc/xen/capabilities");
8205 my $cap = &read_file_contents("/proc/xen/capabilities");
8206 return $cap =~ /control_d/ ? 0 : 1;
8209 =head2 running_in_openvz
8211 Returns 1 if Webmin is running inside an OpenVZ container, by looking
8212 at /proc/vz/veinfo for a non-zero line.
8215 sub running_in_openvz
8217 return 0 if (!-r "/proc/vz/veinfo");
8218 my $lref = &read_file_lines("/proc/vz/veinfo", 1);
8219 return 0 if (!$lref || !@$lref);
8220 foreach my $l (@$lref) {
8222 my @ll = split(/\s+/, $l);
8223 return 0 if ($ll[0] eq '0');
8228 =head2 list_categories(&modules, [include-empty])
8230 Returns a hash mapping category codes to names, including any custom-defined
8231 categories. The modules parameter must be an array ref of module hash objects,
8232 as returned by get_all_module_infos.
8237 my ($mods, $empty) = @_;
8238 my (%cats, %catnames);
8239 &read_file("$config_directory/webmin.catnames", \%catnames);
8240 foreach my $o (@lang_order_list) {
8241 &read_file("$config_directory/webmin.catnames.$o", \%catnames);
8246 foreach my $m (@$mods) {
8247 my $c = $m->{'category'};
8248 next if ($cats{$c});
8249 if (defined($catnames{$c})) {
8250 $cats{$c} = $catnames{$c};
8252 elsif ($text{"category_$c"}) {
8253 $cats{$c} = $text{"category_$c"};
8256 # try to get category name from module ..
8257 my %mtext = &load_language($m->{'dir'});
8258 if ($mtext{"category_$c"}) {
8259 $cats{$c} = $mtext{"category_$c"};
8262 $c = $m->{'category'} = "";
8263 $cats{$c} = $text{"category_$c"};
8270 =head2 is_readonly_mode
8272 Returns 1 if the current user is in read-only mode, and thus all writes
8273 to files and command execution should fail.
8276 sub is_readonly_mode
8278 if (!defined($main::readonly_mode_cache)) {
8279 my %gaccess = &get_module_acl(undef, "");
8280 $main::readonly_mode_cache = $gaccess{'readonly'} ? 1 : 0;
8282 return $main::readonly_mode_cache;
8285 =head2 command_as_user(user, with-env?, command, ...)
8287 Returns a command to execute some command as the given user, using the
8288 su statement. If on Linux, the /bin/sh shell is forced in case the user
8289 does not have a valid shell. If with-env is set to 1, the -s flag is added
8290 to the su command to read the user's .profile or .bashrc file.
8295 my ($user, $env, @args) = @_;
8296 my @uinfo = getpwnam($user);
8297 if ($uinfo[8] ne "/bin/sh" && $uinfo[8] !~ /\/bash$/) {
8298 # User shell doesn't appear to be valid
8299 if ($gconfig{'os_type'} =~ /-linux$/) {
8300 # Use -s /bin/sh to force it
8301 $shellarg = " -s /bin/sh";
8303 elsif ($gconfig{'os_type'} eq 'freebsd' ||
8304 $gconfig{'os_type'} eq 'solaris' &&
8305 $gconfig{'os_version'} >= 11 ||
8306 $gconfig{'os_type'} eq 'macos') {
8307 # Use -m and force /bin/sh
8308 @args = ( "/bin/sh", "-c", quotemeta(join(" ", @args)) );
8312 my $rv = "su".($env ? " -" : "").$shellarg.
8313 " ".quotemeta($user)." -c ".quotemeta(join(" ", @args));
8317 =head2 list_osdn_mirrors(project, file)
8319 This function is now deprecated in favor of letting sourceforge just
8320 redirect to the best mirror, and now just returns their primary download URL.
8323 sub list_osdn_mirrors
8325 my ($project, $file) = @_;
8326 return ( { 'url' => "http://downloads.sourceforge.net/$project/$file",
8328 'mirror' => 'downloads' } );
8331 =head2 convert_osdn_url(url)
8333 Given a URL like http://osdn.dl.sourceforge.net/sourceforge/project/file.zip
8334 or http://prdownloads.sourceforge.net/project/file.zip , convert it
8335 to a real URL on the sourceforge download redirector.
8338 sub convert_osdn_url
8341 if ($url =~ /^http:\/\/[^\.]+.dl.sourceforge.net\/sourceforge\/([^\/]+)\/(.*)$/ ||
8342 $url =~ /^http:\/\/prdownloads.sourceforge.net\/([^\/]+)\/(.*)$/) {
8343 # Always use the Sourceforge mail download URL, which does
8344 # a location-based redirect for us
8345 my ($project, $file) = ($1, $2);
8346 $url = "http://prdownloads.sourceforge.net/sourceforge/".
8348 return wantarray ? ( $url, 0 ) : $url;
8351 # Some other source .. don't change
8352 return wantarray ? ( $url, 2 ) : $url;
8356 =head2 get_current_dir
8358 Returns the directory the current process is running in.
8364 if ($gconfig{'os_type'} eq 'windows') {
8377 =head2 supports_users
8379 Returns 1 if the current OS supports Unix user concepts and functions like
8380 su , getpw* and so on. This will be true on Linux and other Unixes, but false
8386 return $gconfig{'os_type'} ne 'windows';
8389 =head2 supports_symlinks
8391 Returns 1 if the current OS supports symbolic and hard links. This will not
8392 be the case on Windows.
8395 sub supports_symlinks
8397 return $gconfig{'os_type'} ne 'windows';
8400 =head2 quote_path(path)
8402 Returns a path with safe quoting for the current operating system.
8408 if ($gconfig{'os_type'} eq 'windows' || $path =~ /^[a-z]:/i) {
8409 # Windows only supports "" style quoting
8413 return quotemeta($path);
8417 =head2 get_windows_root
8419 Returns the base windows system directory, like c:/windows.
8422 sub get_windows_root
8424 if ($ENV{'SystemRoot'}) {
8425 my $rv = $ENV{'SystemRoot'};
8430 return -d "c:/windows" ? "c:/windows" : "c:/winnt";
8434 =head2 read_file_contents(file)
8436 Given a filename, returns its complete contents as a string. Effectively
8437 the same as the Perl construct `cat file`.
8440 sub read_file_contents
8442 &open_readfile(FILE, $_[0]) || return undef;
8449 =head2 unix_crypt(password, salt)
8451 Performs Unix encryption on a password, using the built-in crypt function or
8452 the Crypt::UnixCrypt module if the former does not work. The salt parameter
8453 must be either an already-hashed password, or a two-character alpha-numeric
8459 my ($pass, $salt) = @_;
8460 return "" if ($salt !~ /^[a-zA-Z0-9\.\/]{2}/); # same as real crypt
8461 my $rv = eval "crypt(\$pass, \$salt)";
8463 return $rv if ($rv && !$@);
8464 eval "use Crypt::UnixCrypt";
8466 return Crypt::UnixCrypt::crypt($pass, $salt);
8469 &error("Failed to encrypt password : $err");
8473 =head2 split_quoted_string(string)
8475 Given a string like I<foo "bar baz" quux>, returns the array :
8479 sub split_quoted_string
8483 while($str =~ /^"([^"]*)"\s*([\000-\377]*)$/ ||
8484 $str =~ /^'([^']*)'\s*([\000-\377]*)$/ ||
8485 $str =~ /^(\S+)\s*([\000-\377]*)$/) {
8492 =head2 write_to_http_cache(url, file|&data)
8494 Updates the Webmin cache with the contents of the given file, possibly also
8495 clearing out old data. Mainly for internal use by http_download.
8498 sub write_to_http_cache
8500 my ($url, $file) = @_;
8501 return 0 if (!$gconfig{'cache_size'});
8503 # Don't cache downloads that look dynamic
8504 if ($url =~ /cgi-bin/ || $url =~ /\?/) {
8508 # Check if the current module should do caching
8509 if ($gconfig{'cache_mods'} =~ /^\!(.*)$/) {
8510 # Caching all except some modules
8511 my @mods = split(/\s+/, $1);
8512 return 0 if (&indexof(&get_module_name(), @mods) != -1);
8514 elsif ($gconfig{'cache_mods'}) {
8515 # Only caching some modules
8516 my @mods = split(/\s+/, $gconfig{'cache_mods'});
8517 return 0 if (&indexof(&get_module_name(), @mods) == -1);
8523 $size = length($$file);
8526 my @st = stat($file);
8530 if ($size > $gconfig{'cache_size'}) {
8531 # Bigger than the whole cache - so don't save it
8536 $cfile = "$main::http_cache_directory/$cfile";
8538 # See how much we have cached currently, clearing old files
8540 mkdir($main::http_cache_directory, 0700) if (!-d $main::http_cache_directory);
8541 opendir(CACHEDIR, $main::http_cache_directory);
8542 foreach my $f (readdir(CACHEDIR)) {
8543 next if ($f eq "." || $f eq "..");
8544 my $path = "$main::http_cache_directory/$f";
8545 my @st = stat($path);
8546 if ($gconfig{'cache_days'} &&
8547 time()-$st[9] > $gconfig{'cache_days'}*24*60*60) {
8548 # This file is too old .. trash it
8553 push(@cached, [ $path, $st[7], $st[9] ]);
8557 @cached = sort { $a->[2] <=> $b->[2] } @cached;
8558 while($total+$size > $gconfig{'cache_size'} && @cached) {
8559 # Cache is too big .. delete some files until the new one will fit
8560 unlink($cached[0]->[0]);
8561 $total -= $cached[0]->[1];
8565 # Finally, write out the new file
8567 &open_tempfile(CACHEFILE, ">$cfile");
8568 &print_tempfile(CACHEFILE, $$file);
8569 &close_tempfile(CACHEFILE);
8572 my ($ok, $err) = ©_source_dest($file, $cfile);
8578 =head2 check_in_http_cache(url)
8580 If some URL is in the cache and valid, return the filename for it. Mainly
8581 for internal use by http_download.
8584 sub check_in_http_cache
8587 return undef if (!$gconfig{'cache_size'});
8589 # Check if the current module should do caching
8590 if ($gconfig{'cache_mods'} =~ /^\!(.*)$/) {
8591 # Caching all except some modules
8592 my @mods = split(/\s+/, $1);
8593 return 0 if (&indexof(&get_module_name(), @mods) != -1);
8595 elsif ($gconfig{'cache_mods'}) {
8596 # Only caching some modules
8597 my @mods = split(/\s+/, $gconfig{'cache_mods'});
8598 return 0 if (&indexof(&get_module_name(), @mods) == -1);
8603 $cfile = "$main::http_cache_directory/$cfile";
8604 my @st = stat($cfile);
8605 return undef if (!@st || !$st[7]);
8606 if ($gconfig{'cache_days'} && time()-$st[9] > $gconfig{'cache_days'}*24*60*60) {
8611 open(TOUCH, ">>$cfile"); # Update the file time, to keep it in the cache
8616 =head2 supports_javascript
8618 Returns 1 if the current browser is assumed to support javascript.
8621 sub supports_javascript
8623 if (defined(&theme_supports_javascript)) {
8624 return &theme_supports_javascript();
8626 return $ENV{'MOBILE_DEVICE'} ? 0 : 1;
8629 =head2 get_module_name
8631 Returns the name of the Webmin module that called this function. For internal
8632 use only by other API functions.
8637 return &get_module_variable('$module_name');
8640 =head2 get_module_variable(name, [ref])
8642 Returns the value of some variable which is set in the caller's context, if
8643 using the new WebminCore package. For internal use only.
8646 sub get_module_variable
8648 my ($v, $wantref) = @_;
8649 my $slash = $wantref ? "\\" : "";
8650 my $thispkg = &web_libs_package();
8651 if ($thispkg eq 'WebminCore') {
8652 my ($vt, $vn) = split('', $v, 2);
8654 for(my $i=0; ($callpkg) = caller($i); $i++) {
8655 last if ($callpkg ne $thispkg);
8657 return eval "${slash}${vt}${callpkg}::${vn}";
8659 return eval "${slash}${v}";
8662 =head2 clear_time_locale()
8664 Temporarily force the locale to C, until reset_time_locale is called. This is
8665 useful if your code is going to call C<strftime> from the POSIX package, and
8666 you want to ensure that the output is in a consistent format.
8669 sub clear_time_locale
8671 if ($main::clear_time_locale_count == 0) {
8674 $main::clear_time_locale_old = POSIX::setlocale(POSIX::LC_TIME);
8675 POSIX::setlocale(POSIX::LC_TIME, "C");
8678 $main::clear_time_locale_count++;
8681 =head2 reset_time_locale()
8683 Revert the locale to whatever it was before clear_time_locale was called
8686 sub reset_time_locale
8688 if ($main::clear_time_locale_count == 1) {
8690 POSIX::setlocale(POSIX::LC_TIME, $main::clear_time_locale_old);
8691 $main::clear_time_locale_old = undef;
8694 $main::clear_time_locale_count--;
8697 =head2 callers_package(filehandle)
8699 Convert a non-module filehandle like FOO to one qualified with the
8700 caller's caller's package, like fsdump::FOO. For internal use only.
8706 my $callpkg = (caller(1))[0];
8707 my $thispkg = &web_libs_package();
8708 if (!ref($fh) && $fh !~ /::/ &&
8709 $callpkg ne $thispkg && $thispkg eq 'WebminCore') {
8710 $fh = $callpkg."::".$fh;
8715 =head2 web_libs_package()
8717 Returns the package this code is in. We can't always trust __PACKAGE__. For
8721 sub web_libs_package
8723 if ($called_from_webmin_core) {
8724 return "WebminCore";
8729 =head2 get_userdb_string
8731 Returns the URL-style string for connecting to the users and groups database
8734 sub get_userdb_string
8737 &get_miniserv_config(\%miniserv);
8738 return $miniserv{'userdb'};
8741 =head2 connect_userdb(string)
8743 Returns a handle for talking to a user database - may be a DBI or LDAP handle.
8744 On failure returns an error message string.
8750 my ($proto, $user, $pass, $host, $prefix, $args) = &split_userdb_string($str);
8751 if ($proto eq "mysql") {
8752 # Connect to MySQL with DBI
8753 my $drh = eval "use DBI; DBI->install_driver('mysql');";
8754 $drh || return $text{'sql_emysqldriver'};
8755 my ($host, $port) = split(/:/, $host);
8756 my $cstr = "database=$prefix;host=$host";
8757 $cstr .= ";port=$port" if ($port);
8758 my $dbh = $drh->connect($cstr, $user, $pass, { });
8759 $dbh || return &text('sql_emysqlconnect', $drh->errstr);
8762 elsif ($proto eq "postgresql") {
8763 # Connect to PostgreSQL with DBI
8764 my $drh = eval "use DBI; DBI->install_driver('Pg');";
8765 $drh || return $text{'sql_epostgresqldriver'};
8766 my ($host, $port) = split(/:/, $host);
8767 my $cstr = "dbname=$prefix;host=$host";
8768 $cstr .= ";port=$port" if ($port);
8769 my $dbh = $drh->connect($cstr, $user, $pass);
8770 $dbh || return &text('sql_epostgresqlconnect', $drh->errstr);
8773 elsif ($proto eq "ldap") {
8775 return "LDAP not done yet";
8778 return "Unknown protocol $proto";
8782 =head2 disconnect_userdb(string, &handle)
8784 Closes a handle opened by connect_userdb
8787 sub disconnect_userdb
8790 if ($str =~ /^(mysql|postgresql):/) {
8794 elsif ($str =~ /^ldap:/) {
8800 =head2 split_userdb_string(string)
8802 Converts a string like mysql://user:pass@host/db into separate parts
8805 sub split_userdb_string
8808 if ($str =~ /^([a-z]+):\/\/([^:]*):([^\@]*)\@([a-z0-9\.\-\_]+)\/([^\?]+)(\?(.*))?$/) {
8809 my ($proto, $user, $pass, $host, $prefix, $argstr) =
8810 ($1, $2, $3, $4, $5, $7);
8811 my %args = map { split(/=/, $_, 2) } split(/\&/, $argstr);
8812 return ($proto, $user, $pass, $host, $prefix, \%args);
8817 $done_web_lib_funcs = 1;