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 $ipv6_module_error = $@;
21 use vars qw($user_risk_level $loaded_theme_library $wait_for_input
22 $done_webmin_header $trust_unknown_referers $unsafe_index_cgi
23 %done_foreign_require $webmin_feedback_address
24 $user_skill_level $pragma_no_cache $foreign_args);
26 use vars qw($module_index_name $number_to_month_map $month_to_number_map
27 $umask_already $default_charset $licence_status $os_type
28 $licence_message $script_name $loaded_theme_oo_library
29 $done_web_lib_funcs $os_version $module_index_link
30 $called_from_webmin_core $ipv6_module_error);
32 =head2 read_file(file, &hash, [&order], [lowercase], [split-char])
34 Fill the given hash reference with name=value pairs from a file. The required
37 =item file - The file to head, which must be text with each line like name=value
39 =item hash - The hash reference to add values read from the file to.
41 =item order - If given, an array reference to add names to in the order they were read
43 =item lowercase - If set to 1, names are converted to lower case
45 =item split-char - If set, names and values are split on this character instead of =
51 my $split = defined($_[4]) ? $_[4] : "=";
52 my $realfile = &translate_filename($_[0]);
53 &open_readfile(ARFILE, $_[0]) || return 0;
56 my $hash = index($_, "#");
57 my $eq = index($_, $split);
58 if ($hash != 0 && $eq >= 0) {
59 my $n = substr($_, 0, $eq);
60 my $v = substr($_, $eq+1);
62 $_[1]->{$_[3] ? lc($n) : $n} = $v;
63 push(@{$_[2]}, $n) if ($_[2]);
67 $main::read_file_missing{$realfile} = 0; # It exists now
68 if (defined($main::read_file_cache{$realfile})) {
69 %{$main::read_file_cache{$realfile}} = %{$_[1]};
74 =head2 read_file_cached(file, &hash, [&order], [lowercase], [split-char])
76 Like read_file, but reads from an in-memory cache if the file has already been
77 read in this Webmin script. Recommended, as it behaves exactly the same as
78 read_file, but can be much faster.
83 my $realfile = &translate_filename($_[0]);
84 if (defined($main::read_file_cache{$realfile})) {
86 %{$_[1]} = ( %{$_[1]}, %{$main::read_file_cache{$realfile}} );
89 elsif ($main::read_file_missing{$realfile}) {
90 # Doesn't exist, so don't re-try read
94 # Actually read the file
96 if (&read_file($_[0], \%d, $_[2], $_[3], $_[4])) {
97 %{$main::read_file_cache{$realfile}} = %d;
98 %{$_[1]} = ( %{$_[1]}, %d );
102 # Flag as non-existant
103 $main::read_file_missing{$realfile} = 1;
109 =head2 write_file(file, &hash, [join-char])
111 Write out the contents of a hash as name=value lines. The parameters are :
113 =item file - Full path to write to
115 =item hash - A hash reference containing names and values to output
117 =item join-char - If given, names and values are separated by this instead of =
123 my $join = defined($_[2]) ? $_[2] : "=";
124 my $realfile = &translate_filename($_[0]);
125 &read_file($_[0], \%old, \@order);
126 &open_tempfile(ARFILE, ">$_[0]");
127 foreach $k (@order) {
128 if (exists($_[1]->{$k})) {
129 (print ARFILE $k,$join,$_[1]->{$k},"\n") ||
130 &error(&text("efilewrite", $realfile, $!));
133 foreach $k (keys %{$_[1]}) {
134 if (!exists($old{$k})) {
135 (print ARFILE $k,$join,$_[1]->{$k},"\n") ||
136 &error(&text("efilewrite", $realfile, $!));
139 &close_tempfile(ARFILE);
140 if (defined($main::read_file_cache{$realfile})) {
141 %{$main::read_file_cache{$realfile}} = %{$_[1]};
143 if (defined($main::read_file_missing{$realfile})) {
144 $main::read_file_missing{$realfile} = 0;
148 =head2 html_escape(string)
150 Converts &, < and > codes in text to HTML entities, and returns the new string.
151 This should be used when including data read from other sources in HTML pages.
160 $tmp =~ s/\"/"/g;
161 $tmp =~ s/\'/'/g;
166 =head2 quote_escape(string, [only-quote])
168 Converts ' and " characters in a string into HTML entities, and returns it.
169 Useful for outputing HTML tag values.
174 my ($tmp, $only) = @_;
175 if ($tmp !~ /\&[a-zA-Z]+;/ && $tmp !~ /\&#/) {
176 # convert &, unless it is part of &#nnn; or &foo;
177 $tmp =~ s/&([^#])/&$1/g;
179 $tmp =~ s/&$/&/g;
180 $tmp =~ s/\"/"/g if ($only eq '' || $only eq '"');
181 $tmp =~ s/\'/'/g if ($only eq '' || $only eq "'");
185 =head2 tempname([filename])
187 Returns a mostly random temporary file name, typically under the /tmp/.webmin
188 directory. If filename is given, this will be the base name used. Otherwise
189 a unique name is selected randomly.
194 my $tmp_base = $gconfig{'tempdir_'.&get_module_name()} ?
195 $gconfig{'tempdir_'.&get_module_name()} :
196 $gconfig{'tempdir'} ? $gconfig{'tempdir'} :
197 $ENV{'TEMP'} ? $ENV{'TEMP'} :
198 $ENV{'TMP'} ? $ENV{'TMP'} :
199 -d "c:/temp" ? "c:/temp" : "/tmp/.webmin";
200 my $tmp_dir = -d $remote_user_info[7] && !$gconfig{'nohometemp'} ?
201 "$remote_user_info[7]/.tmp" :
202 @remote_user_info ? $tmp_base."-".$remote_user :
203 $< != 0 ? $tmp_base."-".getpwuid($<) :
205 if ($gconfig{'os_type'} eq 'windows' || $tmp_dir =~ /^[a-z]:/i) {
206 # On Windows system, just create temp dir if missing
208 mkdir($tmp_dir, 0755) ||
209 &error("Failed to create temp directory $tmp_dir : $!");
213 # On Unix systems, need to make sure temp dir is valid
215 while($tries++ < 10) {
216 my @st = lstat($tmp_dir);
217 last if ($st[4] == $< && (-d _) && ($st[2] & 0777) == 0755);
219 unlink($tmp_dir) || rmdir($tmp_dir) ||
220 system("/bin/rm -rf ".quotemeta($tmp_dir));
222 mkdir($tmp_dir, 0755) || next;
223 chown($<, $(, $tmp_dir);
224 chmod(0755, $tmp_dir);
227 my @st = lstat($tmp_dir);
228 &error("Failed to create temp directory $tmp_dir : uid=$st[4] mode=$st[2]");
232 if (defined($_[0]) && $_[0] !~ /\.\./) {
233 $rv = "$tmp_dir/$_[0]";
236 $main::tempfilecount++;
238 $rv = $tmp_dir."/".int(rand(1000000))."_".
239 $main::tempfilecount."_".$scriptname;
244 =head2 transname([filename])
246 Behaves exactly like tempname, but records the temp file for deletion when the
247 current Webmin script process exits.
252 my $rv = &tempname(@_);
253 push(@main::temporary_files, $rv);
257 =head2 trunc(string, maxlen)
259 Truncates a string to the shortest whole word less than or equal to the
260 given width. Useful for word wrapping.
265 if (length($_[0]) <= $_[1]) {
268 my $str = substr($_[0],0,$_[1]);
277 =head2 indexof(string, value, ...)
279 Returns the index of some value in an array of values, or -1 if it was not
285 for(my $i=1; $i <= $#_; $i++) {
286 if ($_[$i] eq $_[0]) { return $i - 1; }
291 =head2 indexoflc(string, value, ...)
293 Like indexof, but does a case-insensitive match
298 my $str = lc(shift(@_));
299 my @arr = map { lc($_) } @_;
300 return &indexof($str, @arr);
303 =head2 sysprint(handle, [string]+)
305 Outputs some strings to a file handle, but bypassing IO buffering. Can be used
306 as a replacement for print when writing to pipes or sockets.
311 my $fh = &callers_package($_[0]);
312 my $str = join('', @_[1..$#_]);
313 syswrite $fh, $str, length($str);
316 =head2 check_ipaddress(ip)
318 Check if some IPv4 address is properly formatted, returning 1 if so or 0 if not.
323 return $_[0] =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/ &&
324 $1 >= 0 && $1 <= 255 &&
325 $2 >= 0 && $2 <= 255 &&
326 $3 >= 0 && $3 <= 255 &&
327 $4 >= 0 && $4 <= 255;
330 =head2 check_ip6address(ip)
332 Check if some IPv6 address is properly formatted, and returns 1 if so.
337 my @blocks = split(/:/, $_[0]);
338 return 0 if (@blocks == 0 || @blocks > 8);
340 # The address/netmask format is accepted. So we're looking for a "/" to isolate a possible netmask.
341 # After that, we delete the netmask to control the address only format, but we verify whether the netmask
342 # value is in [0;128].
344 my $where = index($blocks[$ib],"/");
347 my $b = substr($blocks[$ib],0,$where);
348 $m = substr($blocks[$ib],$where+1,length($blocks[$ib])-($where+1));
352 # The netmask must take its value in [0;128]
353 return 0 if ($m <0 || $m >128);
355 # Check the different blocks of the address : 16 bits block in hexa notation.
356 # Possibility of 1 empty block or 2 if the address begins with "::".
359 foreach $b (@blocks) {
360 return 0 if ($b ne "" && $b !~ /^[0-9a-f]{1,4}$/i);
361 $empty++ if ($b eq "");
363 return 0 if ($empty > 1 && !($_[0] =~ /^::/ && $empty == 2));
369 =head2 generate_icon(image, title, link, [href], [width], [height], [before-title], [after-title])
371 Prints HTML for an icon image. The parameters are :
373 =item image - URL for the image, like images/foo.gif
375 =item title - Text to appear under the icon
377 =item link - Optional destination for the icon's link
379 =item href - Other HTML attributes to be added to the <a href> for the link
381 =item width - Optional width of the icon
383 =item height - Optional height of the icon
385 =item before-title - HTML to appear before the title link, but which is not actually in the link
387 =item after-title - HTML to appear after the title link, but which is not actually in the link
392 &load_theme_library();
393 if (defined(&theme_generate_icon)) {
394 &theme_generate_icon(@_);
397 my $w = !defined($_[4]) ? "width=48" : $_[4] ? "width=$_[4]" : "";
398 my $h = !defined($_[5]) ? "height=48" : $_[5] ? "height=$_[5]" : "";
399 if ($tconfig{'noicons'}) {
401 print "$_[6]<a href=\"$_[2]\" $_[3]>$_[1]</a>$_[7]\n";
404 print "$_[6]$_[1]$_[7]\n";
408 print "<table border><tr><td width=48 height=48>\n",
409 "<a href=\"$_[2]\" $_[3]><img src=\"$_[0]\" alt=\"\" border=0 ",
410 "$w $h></a></td></tr></table>\n";
411 print "$_[6]<a href=\"$_[2]\" $_[3]>$_[1]</a>$_[7]\n";
414 print "<table border><tr><td width=48 height=48>\n",
415 "<img src=\"$_[0]\" alt=\"\" border=0 $w $h>",
416 "</td></tr></table>\n$_[6]$_[1]$_[7]\n";
422 Converts a string to a form ok for putting in a URL, using % escaping.
428 $rv =~ s/([^A-Za-z0-9])/sprintf("%%%2.2X", ord($1))/ge;
432 =head2 un_urlize(string)
434 Converts a URL-encoded string to it's original contents - the reverse of the
442 $rv =~ s/%(..)/pack("c",hex($1))/ge;
446 =head2 include(filename)
448 Read and output the contents of the given file.
454 open(INCLUDE, &translate_filename($_[0])) || return 0;
462 =head2 copydata(in-handle, out-handle)
464 Read from one file handle and write to another, until there is no more to read.
470 $in = &callers_package($in);
471 $out = &callers_package($out);
473 while(read($in, $buf, 1024) > 0) {
474 (print $out $buf) || return 0;
479 =head2 ReadParseMime([maximum], [&cbfunc, &cbargs])
481 Read data submitted via a POST request using the multipart/form-data coding,
482 and store it in the global %in hash. The optional parameters are :
484 =item maximum - If the number of bytes of input exceeds this number, stop reading and call error.
486 =item cbfunc - A function reference to call after reading each block of data.
488 =item cbargs - Additional parameters to the callback function.
493 my ($max, $cbfunc, $cbargs) = @_;
494 my ($boundary, $line, $foo, $name, $got, $file);
495 my $err = &text('readparse_max', $max);
496 $ENV{'CONTENT_TYPE'} =~ /boundary=(.*)$/ || &error($text{'readparse_enc'});
497 if ($ENV{'CONTENT_LENGTH'} && $max && $ENV{'CONTENT_LENGTH'} > $max) {
500 &$cbfunc(0, $ENV{'CONTENT_LENGTH'}, $file, @$cbargs) if ($cbfunc);
502 <STDIN>; # skip first boundary
505 # Read section headers
509 $got += length($line);
510 &$cbfunc($got, $ENV{'CONTENT_LENGTH'}, @$cbargs) if ($cbfunc);
511 if ($max && $got > $max) {
516 if ($line =~ /^(\S+):\s*(.*)$/) {
517 $header{$lastheader = lc($1)} = $2;
519 elsif ($line =~ /^\s+(.*)$/) {
520 $header{$lastheader} .= $line;
524 # Parse out filename and type
525 if ($header{'content-disposition'} =~ /^form-data(.*)/) {
527 while ($rest =~ /([a-zA-Z]*)=\"([^\"]*)\"(.*)/) {
532 $foo = $name . "_$1";
539 &error($text{'readparse_cdheader'});
541 if ($header{'content-type'} =~ /^([^\s;]+)/) {
542 $foo = $name . "_content_type";
545 $file = $in{$name."_filename"};
548 $in{$name} .= "\0" if (defined($in{$name}));
551 $got += length($line);
552 &$cbfunc($got, $ENV{'CONTENT_LENGTH'}, $file, @$cbargs)
554 if ($max && $got > $max) {
555 #print STDERR "over limit of $max\n";
560 &$cbfunc(-1, $ENV{'CONTENT_LENGTH'}, $file, @$cbargs)
565 $ptline =~ s/[^a-zA-Z0-9\-]/\./g;
566 if (index($line, $boundary) != -1) { last; }
569 chop($in{$name}); chop($in{$name});
570 if (index($line,"$boundary--") != -1) { last; }
572 &$cbfunc(-1, $ENV{'CONTENT_LENGTH'}, $file, @$cbargs) if ($cbfunc);
575 =head2 ReadParse([&hash], [method], [noplus])
577 Fills the given hash reference with CGI parameters, or uses the global hash
578 %in if none is given. Also sets the global variables $in and @in. The other
581 =item method - For use of this HTTP method, such as GET
583 =item noplus - Don't convert + in parameters to spaces.
588 my $a = $_[0] || \%in;
590 my $meth = $_[1] ? $_[1] : $ENV{'REQUEST_METHOD'};
592 if ($meth eq 'POST') {
593 my $clen = $ENV{'CONTENT_LENGTH'};
594 &read_fully(STDIN, \$in, $clen) == $clen ||
595 &error("Failed to read POST input : $!");
597 if ($ENV{'QUERY_STRING'}) {
598 if ($in) { $in .= "&".$ENV{'QUERY_STRING'}; }
599 else { $in = $ENV{'QUERY_STRING'}; }
601 @in = split(/\&/, $in);
602 foreach my $i (@in) {
603 my ($k, $v) = split(/=/, $i, 2);
608 $k =~ s/%(..)/pack("c",hex($1))/ge;
609 $v =~ s/%(..)/pack("c",hex($1))/ge;
610 $a->{$k} = defined($a->{$k}) ? $a->{$k}."\0".$v : $v;
614 =head2 read_fully(fh, &buffer, length)
616 Read data from some file handle up to the given length, even in the face
617 of partial reads. Reads the number of bytes read. Stores received data in the
618 string pointed to be the buffer reference.
623 my ($fh, $buf, $len) = @_;
624 $fh = &callers_package($fh);
627 my $r = read(STDIN, $$buf, $len-$got, $got);
634 =head2 read_parse_mime_callback(size, totalsize, upload-id)
636 Called by ReadParseMime as new data arrives from a form-data POST. Only updates
637 the file on every 1% change though. For internal use by the upload progress
641 sub read_parse_mime_callback
643 my ($size, $totalsize, $filename, $id) = @_;
644 return if ($gconfig{'no_upload_tracker'});
647 # Create the upload tracking directory - if running as non-root, this has to
648 # be under the user's home
651 my @uinfo = @remote_user_info ? @remote_user_info : getpwuid($<);
652 $vardir = "$uinfo[7]/.tmp";
655 $vardir = $ENV{'WEBMIN_VAR'};
658 &make_dir($vardir, 0755);
661 # Remove any upload.* files more than 1 hour old
662 if (!$main::read_parse_mime_callback_flushed) {
664 opendir(UPDIR, $vardir);
665 foreach my $f (readdir(UPDIR)) {
666 next if ($f !~ /^upload\./);
667 my @st = stat("$vardir/$f");
668 if ($st[9] < $now-3600) {
669 unlink("$vardir/$f");
673 $main::read_parse_mime_callback_flushed++;
676 # Only update file once per percent
677 my $upfile = "$vardir/upload.$id";
678 if ($totalsize && $size >= 0) {
679 my $pc = int(100 * $size / $totalsize);
680 if ($pc <= $main::read_parse_mime_callback_pc{$upfile}) {
683 $main::read_parse_mime_callback_pc{$upfile} = $pc;
687 &open_tempfile(UPFILE, ">$upfile");
688 print UPFILE $size,"\n";
689 print UPFILE $totalsize,"\n";
690 print UPFILE $filename,"\n";
691 &close_tempfile(UPFILE);
694 =head2 read_parse_mime_javascript(upload-id, [&fields])
696 Returns an onSubmit= Javascript statement to popup a window for tracking
697 an upload with the given ID. For internal use by the upload progress tracker.
700 sub read_parse_mime_javascript
702 my ($id, $fields) = @_;
703 return "" if ($gconfig{'no_upload_tracker'});
704 my $opener = "window.open(\"$gconfig{'webprefix'}/uptracker.cgi?id=$id&uid=$<\", \"uptracker\", \"toolbar=no,menubar=no,scrollbars=no,width=500,height=100\");";
706 my $if = join(" || ", map { "typeof($_) != \"undefined\" && $_.value != \"\"" } @$fields);
707 return "onSubmit='if ($if) { $opener }'";
710 return "onSubmit='$opener'";
714 =head2 PrintHeader(charset)
716 Outputs the HTTP headers for an HTML page. The optional charset parameter
717 can be used to set a character set. Normally this function is not called
718 directly, but is rather called by ui_print_header or header.
723 if ($pragma_no_cache || $gconfig{'pragma_no_cache'}) {
724 print "pragma: no-cache\n";
725 print "Expires: Thu, 1 Jan 1970 00:00:00 GMT\n";
726 print "Cache-Control: no-store, no-cache, must-revalidate\n";
727 print "Cache-Control: post-check=0, pre-check=0\n";
729 if (defined($_[0])) {
730 print "Content-type: text/html; Charset=$_[0]\n\n";
733 print "Content-type: text/html\n\n";
737 =head2 header(title, image, [help], [config], [nomodule], [nowebmin], [rightside], [head-stuff], [body-stuff], [below])
739 Outputs a Webmin HTML page header with a title, including HTTP headers. The
742 =item title - The text to show at the top of the page
744 =item image - An image to show instead of the title text. This is typically left blank.
746 =item help - If set, this is the name of a help page that will be linked to in the title.
748 =item config - If set to 1, the title will contain a link to the module's config page.
750 =item nomodule - If set to 1, there will be no link in the title section to the module's index.
752 =item nowebmin - If set to 1, there will be no link in the title section to the Webmin index.
754 =item rightside - HTML to be shown on the right-hand side of the title. Can contain multiple lines, separated by <br>. Typically this is used for links to stop, start or restart servers.
756 =item head-stuff - HTML to be included in the <head> section of the page.
758 =item body-stuff - HTML attributes to be include in the <body> tag.
760 =item below - HTML to be displayed below the title. Typically this is used for application or server version information.
765 return if ($main::done_webmin_header++);
767 my $charset = defined($main::force_charset) ? $main::force_charset
769 &PrintHeader($charset);
770 &load_theme_library();
771 if (defined(&theme_header)) {
772 $module_name = &get_module_name();
776 print "<!doctype html public \"-//W3C//DTD HTML 3.2 Final//EN\">\n";
779 if (defined(&theme_prehead)) {
783 print "<meta http-equiv=\"Content-Type\" ",
784 "content=\"text/html; Charset="."e_escape($charset)."\">\n";
787 my $title = &get_html_title($_[0]);
788 print "<title>$title</title>\n";
789 print $_[7] if ($_[7]);
790 print &get_html_status_line(0);
792 print "$tconfig{'headhtml'}\n" if ($tconfig{'headhtml'});
793 if ($tconfig{'headinclude'}) {
794 print &read_file_contents(
795 "$theme_root_directory/$tconfig{'headinclude'}");
798 my $bgcolor = defined($tconfig{'cs_page'}) ? $tconfig{'cs_page'} :
799 defined($gconfig{'cs_page'}) ? $gconfig{'cs_page'} : "ffffff";
800 my $link = defined($tconfig{'cs_link'}) ? $tconfig{'cs_link'} :
801 defined($gconfig{'cs_link'}) ? $gconfig{'cs_link'} : "0000ee";
802 my $text = defined($tconfig{'cs_text'}) ? $tconfig{'cs_text'} :
803 defined($gconfig{'cs_text'}) ? $gconfig{'cs_text'} : "000000";
804 my $bgimage = defined($tconfig{'bgimage'}) ? "background=$tconfig{'bgimage'}"
806 my $dir = $current_lang_info->{'dir'} ? "dir=\"$current_lang_info->{'dir'}\""
808 print "<body bgcolor=#$bgcolor link=#$link vlink=#$link text=#$text ",
809 "$bgimage $tconfig{'inbody'} $dir $_[8]>\n";
810 if (defined(&theme_prebody)) {
813 my $hostname = &get_display_hostname();
814 my $version = &get_webmin_version();
815 my $prebody = $tconfig{'prebody'};
817 $prebody =~ s/%HOSTNAME%/$hostname/g;
818 $prebody =~ s/%VERSION%/$version/g;
819 $prebody =~ s/%USER%/$remote_user/g;
820 $prebody =~ s/%OS%/$os_type $os_version/g;
823 if ($tconfig{'prebodyinclude'}) {
825 open(INC, "$theme_root_directory/$tconfig{'prebodyinclude'}");
832 print $tconfig{'preheader'};
833 my %this_module_info = &get_module_info(&get_module_name());
834 print "<table class='header' width=100%><tr>\n";
835 if ($gconfig{'sysinfo'} == 2 && $remote_user) {
836 print "<td id='headln1' colspan=3 align=center>\n";
837 print &get_html_status_line(1);
838 print "</td></tr> <tr>\n";
840 print "<td id='headln2l' width=15% valign=top align=left>";
841 if ($ENV{'HTTP_WEBMIN_SERVERS'} && !$tconfig{'framed'}) {
842 print "<a href='$ENV{'HTTP_WEBMIN_SERVERS'}'>",
843 "$text{'header_servers'}</a><br>\n";
845 if (!$_[5] && !$tconfig{'noindex'}) {
846 my @avail = &get_available_module_infos(1);
847 my $nolo = $ENV{'ANONYMOUS_USER'} ||
848 $ENV{'SSL_USER'} || $ENV{'LOCAL_USER'} ||
849 $ENV{'HTTP_USER_AGENT'} =~ /webmin/i;
850 if ($gconfig{'gotoone'} && $main::session_id && @avail == 1 &&
852 print "<a href='$gconfig{'webprefix'}/session_login.cgi?logout=1'>",
853 "$text{'main_logout'}</a><br>";
855 elsif ($gconfig{'gotoone'} && @avail == 1 && !$nolo) {
856 print "<a href=$gconfig{'webprefix'}/switch_user.cgi>",
857 "$text{'main_switch'}</a><br>";
859 elsif (!$gconfig{'gotoone'} || @avail > 1) {
860 print "<a href='$gconfig{'webprefix'}/?cat=",
861 $this_module_info{'category'},
862 "'>$text{'header_webmin'}</a><br>\n";
865 if (!$_[4] && !$tconfig{'nomoduleindex'}) {
866 my $idx = $this_module_info{'index_link'};
867 my $mi = $module_index_link || "/".&get_module_name()."/$idx";
868 my $mt = $module_index_name || $text{'header_module'};
869 print "<a href=\"$gconfig{'webprefix'}$mi\">$mt</a><br>\n";
871 if (ref($_[2]) eq "ARRAY" && !$ENV{'ANONYMOUS_USER'} &&
872 !$tconfig{'nohelp'}) {
873 print &hlink($text{'header_help'}, $_[2]->[0], $_[2]->[1]),
876 elsif (defined($_[2]) && !$ENV{'ANONYMOUS_USER'} &&
877 !$tconfig{'nohelp'}) {
878 print &hlink($text{'header_help'}, $_[2]),"<br>\n";
881 my %access = &get_module_acl();
882 if (!$access{'noconfig'} && !$config{'noprefs'}) {
883 my $cprog = $user_module_config_directory ?
884 "uconfig.cgi" : "config.cgi";
885 print "<a href=\"$gconfig{'webprefix'}/$cprog?",
886 &get_module_name()."\">",
887 $text{'header_config'},"</a><br>\n";
892 # Title is a single image
893 print "<td id='headln2c' align=center width=70%>",
894 "<img alt=\"$_[0]\" src=\"$_[1]\"></td>\n";
898 my $ts = defined($tconfig{'titlesize'}) ?
899 $tconfig{'titlesize'} : "+2";
900 print "<td id='headln2c' align=center width=70%>",
901 ($ts ? "<font size=$ts>" : ""),$_[0],
902 ($ts ? "</font>" : "");
903 print "<br>$_[9]\n" if ($_[9]);
906 print "<td id='headln2r' width=15% valign=top align=right>";
908 print "</td></tr></table>\n";
909 print $tconfig{'postheader'};
913 =head2 get_html_title(title)
915 Returns the full string to appear in the HTML <title> block.
922 my $os_type = $gconfig{'real_os_type'} || $gconfig{'os_type'};
923 my $os_version = $gconfig{'real_os_version'} || $gconfig{'os_version'};
924 my $host = &get_display_hostname();
925 if ($gconfig{'sysinfo'} == 1 && $remote_user) {
926 $title = sprintf "%s : %s on %s (%s %s)\n",
927 $msg, $remote_user, $host,
928 $os_type, $os_version;
930 elsif ($gconfig{'sysinfo'} == 4 && $remote_user) {
931 $title = sprintf "%s on %s (%s %s)\n",
933 $os_type, $os_version;
938 if ($gconfig{'showlogin'} && $remote_user) {
939 $title = $remote_user.($title ? " : ".$title : "");
941 if ($gconfig{'showhost'}) {
942 $title = $host.($title ? " : ".$title : "");
947 =head2 get_html_framed_title
949 Returns the title text for a framed theme main page.
952 sub get_html_framed_title
955 my $os_type = $gconfig{'real_os_type'} || $gconfig{'os_type'};
956 my $os_version = $gconfig{'real_os_version'} || $gconfig{'os_version'};
958 if (($gconfig{'sysinfo'} == 4 || $gconfig{'sysinfo'} == 1) && $remote_user) {
959 # Alternate title mode requested
960 $title = sprintf "%s on %s (%s %s)\n",
961 $remote_user, &get_display_hostname(),
962 $os_type, $os_version;
965 # Title like 'Webmin x.yy on hostname (Linux 6)'
966 if ($os_version eq "*") {
970 $ostr = "$os_type $os_version";
972 my $host = &get_display_hostname();
973 my $ver = &get_webmin_version();
974 $title = $gconfig{'nohostname'} ? $text{'main_title2'} :
975 $gconfig{'showhost'} ? &text('main_title3', $ver, $ostr) :
976 &text('main_title', $ver, $host, $ostr);
977 if ($gconfig{'showlogin'}) {
978 $title = $remote_user.($title ? " : ".$title : "");
980 if ($gconfig{'showhost'}) {
981 $title = $host.($title ? " : ".$title : "");
987 =head2 get_html_status_line(text-only)
989 Returns HTML for a script block that sets the status line, or if text-only
990 is set to 1, just return the status line text.
993 sub get_html_status_line
996 if (($gconfig{'sysinfo'} != 0 || !$remote_user) && !$textonly) {
997 # Disabled in this mode
1000 my $os_type = $gconfig{'real_os_type'} || $gconfig{'os_type'};
1001 my $os_version = $gconfig{'real_os_version'} || $gconfig{'os_version'};
1002 my $line = &text('header_statusmsg',
1003 ($ENV{'ANONYMOUS_USER'} ? "Anonymous user"
1005 ($ENV{'SSL_USER'} ? " (SSL certified)" :
1006 $ENV{'LOCAL_USER'} ? " (Local user)" : ""),
1007 $text{'programname'},
1008 &get_webmin_version(),
1009 &get_display_hostname(),
1010 $os_type.($os_version eq "*" ? "" :" $os_version"));
1015 $line =~ s/\r|\n//g;
1016 return "<script language=JavaScript type=text/javascript>\n".
1017 "defaultStatus=\""."e_escape($line)."\";\n".
1022 =head2 popup_header([title], [head-stuff], [body-stuff], [no-body])
1024 Outputs a page header, suitable for a popup window. If no title is given,
1025 absolutely no decorations are output. Also useful in framesets. The parameters
1028 =item title - Title text for the popup window.
1030 =item head-stuff - HTML to appear in the <head> section.
1032 =item body-stuff - HTML attributes to be include in the <body> tag.
1034 =item no-body - If set to 1, don't generate a body tag
1039 return if ($main::done_webmin_header++);
1041 my $charset = defined($main::force_charset) ? $main::force_charset
1043 &PrintHeader($charset);
1044 &load_theme_library();
1045 if (defined(&theme_popup_header)) {
1046 &theme_popup_header(@_);
1049 print "<!doctype html public \"-//W3C//DTD HTML 3.2 Final//EN\">\n";
1052 if (defined(&theme_popup_prehead)) {
1053 &theme_popup_prehead(@_);
1055 print "<title>$_[0]</title>\n";
1057 print "$tconfig{'headhtml'}\n" if ($tconfig{'headhtml'});
1058 if ($tconfig{'headinclude'}) {
1059 print &read_file_contents(
1060 "$theme_root_directory/$tconfig{'headinclude'}");
1063 my $bgcolor = defined($tconfig{'cs_page'}) ? $tconfig{'cs_page'} :
1064 defined($gconfig{'cs_page'}) ? $gconfig{'cs_page'} : "ffffff";
1065 my $link = defined($tconfig{'cs_link'}) ? $tconfig{'cs_link'} :
1066 defined($gconfig{'cs_link'}) ? $gconfig{'cs_link'} : "0000ee";
1067 my $text = defined($tconfig{'cs_text'}) ? $tconfig{'cs_text'} :
1068 defined($gconfig{'cs_text'}) ? $gconfig{'cs_text'} : "000000";
1069 my $bgimage = defined($tconfig{'bgimage'}) ? "background=$tconfig{'bgimage'}"
1072 print "<body id='popup' bgcolor=#$bgcolor link=#$link vlink=#$link ",
1073 "text=#$text $bgimage $tconfig{'inbody'} $_[2]>\n";
1074 if (defined(&theme_popup_prebody)) {
1075 &theme_popup_prebody(@_);
1080 =head2 footer([page, name]+, [noendbody])
1082 Outputs the footer for a Webmin HTML page, possibly with links back to other
1083 pages. The links are specified by pairs of parameters, the first of which is
1084 a link destination, and the second the link text. For example :
1086 footer('/', 'Webmin index', '', 'Module menu');
1091 &load_theme_library();
1092 my %this_module_info = &get_module_info(&get_module_name());
1093 if (defined(&theme_footer)) {
1094 $module_name = &get_module_name(); # Old themes use these
1095 %module_info = %this_module_info;
1099 for(my $i=0; $i+1<@_; $i+=2) {
1101 if ($url ne '/' || !$tconfig{'noindex'}) {
1103 $url = "/?cat=$this_module_info{'category'}";
1105 elsif ($url eq '' && &get_module_name()) {
1106 $url = "/".&get_module_name()."/".
1107 $this_module_info{'index_link'};
1109 elsif ($url =~ /^\?/ && &get_module_name()) {
1110 $url = "/".&get_module_name()."/$url";
1112 $url = "$gconfig{'webprefix'}$url" if ($url =~ /^\//);
1114 print "<a href=\"$url\"><img alt=\"<-\" align=middle border=0 src=$gconfig{'webprefix'}/images/left.gif></a>\n";
1119 print " <a href=\"$url\">",&text('main_return', $_[$i+1]),"</a>\n";
1124 my $postbody = $tconfig{'postbody'};
1126 my $hostname = &get_display_hostname();
1127 my $version = &get_webmin_version();
1128 my $os_type = $gconfig{'real_os_type'} ||
1129 $gconfig{'os_type'};
1130 my $os_version = $gconfig{'real_os_version'} ||
1131 $gconfig{'os_version'};
1132 $postbody =~ s/%HOSTNAME%/$hostname/g;
1133 $postbody =~ s/%VERSION%/$version/g;
1134 $postbody =~ s/%USER%/$remote_user/g;
1135 $postbody =~ s/%OS%/$os_type $os_version/g;
1136 print "$postbody\n";
1138 if ($tconfig{'postbodyinclude'}) {
1140 open(INC, "$theme_root_directory/$tconfig{'postbodyinclude'}");
1146 if (defined(&theme_postbody)) {
1147 &theme_postbody(@_);
1149 print "</body></html>\n";
1153 =head2 popup_footer([no-body])
1155 Outputs html for a footer for a popup window, started by popup_header.
1160 &load_theme_library();
1161 if (defined(&theme_popup_footer)) {
1162 &theme_popup_footer(@_);
1171 =head2 load_theme_library
1173 Immediately loads the current theme's theme.pl file. Not generally useful for
1174 most module developers, as this is called automatically by the header function.
1177 sub load_theme_library
1179 return if (!$current_theme || $loaded_theme_library++);
1180 for(my $i=0; $i<@theme_root_directories; $i++) {
1181 if ($theme_configs[$i]->{'functions'}) {
1182 do $theme_root_directories[$i]."/".
1183 $theme_configs[$i]->{'functions'};
1188 =head2 redirect(url)
1190 Output HTTP headers to redirect the browser to some page. The url parameter is
1191 typically a relative URL like index.cgi or list_users.cgi.
1196 my $port = $ENV{'SERVER_PORT'} == 443 && uc($ENV{'HTTPS'}) eq "ON" ? "" :
1197 $ENV{'SERVER_PORT'} == 80 && uc($ENV{'HTTPS'}) ne "ON" ? "" :
1198 ":$ENV{'SERVER_PORT'}";
1199 my $prot = uc($ENV{'HTTPS'}) eq "ON" ? "https" : "http";
1200 my $wp = $gconfig{'webprefixnoredir'} ? undef : $gconfig{'webprefix'};
1202 if ($_[0] =~ /^(http|https|ftp|gopher):/) {
1203 # Absolute URL (like http://...)
1206 elsif ($_[0] =~ /^\//) {
1207 # Absolute path (like /foo/bar.cgi)
1208 $url = "$prot://$ENV{'SERVER_NAME'}$port$wp$_[0]";
1210 elsif ($ENV{'SCRIPT_NAME'} =~ /^(.*)\/[^\/]*$/) {
1211 # Relative URL (like foo.cgi)
1212 $url = "$prot://$ENV{'SERVER_NAME'}$port$wp$1/$_[0]";
1215 $url = "$prot://$ENV{'SERVER_NAME'}$port/$wp$_[0]";
1217 &load_theme_library();
1218 if (defined(&theme_redirect)) {
1219 $module_name = &get_module_name(); # Old themes use these
1220 %module_info = &get_module_info($module_name);
1221 &theme_redirect($_[0], $url);
1224 print "Location: $url\n\n";
1228 =head2 kill_byname(name, signal)
1230 Finds a process whose command line contains the given name (such as httpd), and
1231 sends some signal to it. The signal can be numeric (like 9) or named
1237 my @pids = &find_byname($_[0]);
1238 return scalar(@pids) if (&is_readonly_mode());
1239 &webmin_debug_log('KILL', "signal=$_[1] name=$_[0]")
1240 if ($gconfig{'debug_what_procs'});
1241 if (@pids) { kill($_[1], @pids); return scalar(@pids); }
1245 =head2 kill_byname_logged(name, signal)
1247 Like kill_byname, but also logs the killing.
1250 sub kill_byname_logged
1252 my @pids = &find_byname($_[0]);
1253 return scalar(@pids) if (&is_readonly_mode());
1254 if (@pids) { &kill_logged($_[1], @pids); return scalar(@pids); }
1258 =head2 find_byname(name)
1260 Finds processes searching for the given name in their command lines, and
1261 returns a list of matching PIDs.
1266 if ($gconfig{'os_type'} =~ /-linux$/ && -r "/proc/$$/cmdline") {
1267 # Linux with /proc filesystem .. use cmdline files, as this is
1268 # faster than forking
1270 opendir(PROCDIR, "/proc");
1271 foreach my $f (readdir(PROCDIR)) {
1272 if ($f eq int($f) && $f != $$) {
1273 my $line = &read_file_contents("/proc/$f/cmdline");
1274 if ($line =~ /$_[0]/) {
1283 if (&foreign_check("proc")) {
1284 # Call the proc module
1285 &foreign_require("proc", "proc-lib.pl");
1286 if (defined(&proc::list_processes)) {
1287 my @procs = &proc::list_processes();
1289 foreach my $p (@procs) {
1290 if ($p->{'args'} =~ /$_[0]/) {
1291 push(@pids, $p->{'pid'});
1294 @pids = grep { $_ != $$ } @pids;
1299 # Fall back to running a command
1301 $cmd = $gconfig{'find_pid_command'};
1302 $cmd =~ s/NAME/"$_[0]"/g;
1303 $cmd = &translate_command($cmd);
1304 @pids = split(/\n/, `($cmd) <$null_file 2>$null_file`);
1305 @pids = grep { $_ != $$ } @pids;
1309 =head2 error([message]+)
1311 Display an error message and exit. This should be used by CGI scripts that
1312 encounter a fatal error or invalid user input to notify users of the problem.
1313 If error_setup has been called, the displayed error message will be prefixed
1314 by the message setup using that function.
1319 $main::no_miniserv_userdb = 1;
1320 my $msg = join("", @_);
1321 $msg =~ s/<[^>]*>//g;
1322 if (!$main::error_must_die) {
1323 print STDERR "Error: ",$msg,"\n";
1325 &load_theme_library();
1326 if ($main::error_must_die) {
1327 if ($gconfig{'error_stack'}) {
1328 print STDERR "Error: ",$msg,"\n";
1329 for(my $i=0; my @stack = caller($i); $i++) {
1330 print STDERR "File: $stack[1] Line: $stack[2] ",
1331 "Function: $stack[3]\n";
1336 elsif (!$ENV{'REQUEST_METHOD'}) {
1337 # Show text-only error
1338 print STDERR "$text{'error'}\n";
1339 print STDERR "-----\n";
1340 print STDERR ($main::whatfailed ? "$main::whatfailed : " : ""),
1342 print STDERR "-----\n";
1343 if ($gconfig{'error_stack'}) {
1345 print STDERR $text{'error_stack'},"\n";
1346 for(my $i=0; my @stack = caller($i); $i++) {
1347 print STDERR &text('error_stackline',
1348 $stack[1], $stack[2], $stack[3]),"\n";
1353 elsif (defined(&theme_error)) {
1357 &header($text{'error'}, "");
1359 print "<h3>",($main::whatfailed ? "$main::whatfailed : " : ""),
1361 if ($gconfig{'error_stack'}) {
1363 print "<h3>$text{'error_stack'}</h3>\n";
1365 print "<tr> <td><b>$text{'error_file'}</b></td> ",
1366 "<td><b>$text{'error_line'}</b></td> ",
1367 "<td><b>$text{'error_sub'}</b></td> </tr>\n";
1368 for($i=0; my @stack = caller($i); $i++) {
1370 print "<td>$stack[1]</td>\n";
1371 print "<td>$stack[2]</td>\n";
1372 print "<td>$stack[3]</td>\n";
1378 if ($ENV{'HTTP_REFERER'} && $main::completed_referers_check) {
1379 &footer($ENV{'HTTP_REFERER'}, $text{'error_previous'});
1385 &unlock_all_files();
1386 &cleanup_tempnames();
1390 =head2 popup_error([message]+)
1392 This function is almost identical to error, but displays the message with HTML
1393 headers suitable for a popup window.
1398 $main::no_miniserv_userdb = 1;
1399 &load_theme_library();
1400 if ($main::error_must_die) {
1403 elsif (defined(&theme_popup_error)) {
1404 &theme_popup_error(@_);
1407 &popup_header($text{'error'}, "");
1408 print "<h3>",($main::whatfailed ? "$main::whatfailed : " : ""),@_,"</h3>\n";
1411 &unlock_all_files();
1412 &cleanup_tempnames();
1416 =head2 error_setup(message)
1418 Registers a message to be prepended to all error messages displayed by the
1424 $main::whatfailed = $_[0];
1427 =head2 wait_for(handle, regexp, regexp, ...)
1429 Reads from the input stream until one of the regexps matches, and returns the
1430 index of the matching regexp, or -1 if input ended before any matched. This is
1431 very useful for parsing the output of interactive programs, and can be used with
1432 a two-way pipe to feed input to a program in response to output matched by
1435 If the matching regexp contains bracketed sub-expressions, their values will
1436 be placed in the global array @matches, indexed starting from 1. You cannot
1437 use the Perl variables $1, $2 and so on to capture matches.
1441 $rv = wait_for($loginfh, "username:");
1443 error("Didn't get username prompt");
1445 print $loginfh "joe\n";
1446 $rv = wait_for($loginfh, "password:");
1448 error("Didn't get password prompt");
1450 print $loginfh "smeg\n";
1455 my ($c, $i, $sw, $rv, $ha);
1456 undef($wait_for_input);
1457 if ($wait_for_debug) {
1458 print STDERR "wait_for(",join(",", @_),")\n";
1460 $ha = &callers_package($_[0]);
1461 if ($wait_for_debug) {
1462 print STDERR "File handle=$ha fd=",fileno($ha),"\n";
1467 " if ((\$c = getc(\$ha)) eq \"\") { return -1; }\n".
1468 " \$wait_for_input .= \$c;\n";
1469 if ($wait_for_debug) {
1470 $codes .= "print STDERR \$wait_for_input,\"\\n\";";
1472 for($i=1; $i<@_; $i++) {
1473 $sw = $i>1 ? "elsif" : "if";
1474 $codes .= " $sw (\$wait_for_input =~ /$_[$i]/i) { \$hit = $i-1; }\n";
1477 " if (defined(\$hit)) {\n".
1478 " \@matches = (-1, \$1, \$2, \$3, \$4, \$5, \$6, \$7, \$8, \$9);\n".
1484 &error("wait_for error : $@\n");
1489 =head2 fast_wait_for(handle, string, string, ...)
1491 This function behaves very similar to wait_for (documented above), but instead
1492 of taking regular expressions as parameters, it takes strings. As soon as the
1493 input contains one of them, it will return the index of the matching string.
1494 If the input ends before any match, it returns -1.
1499 my ($inp, $maxlen, $ha, $i, $c, $inpl);
1500 for($i=1; $i<@_; $i++) {
1501 $maxlen = length($_[$i]) > $maxlen ? length($_[$i]) : $maxlen;
1505 if (($c = getc($ha)) eq "") {
1506 &error("fast_wait_for read error : $!");
1509 if (length($inp) > $maxlen) {
1510 $inp = substr($inp, length($inp)-$maxlen);
1512 $inpl = length($inp);
1513 for($i=1; $i<@_; $i++) {
1514 if ($_[$i] eq substr($inp, $inpl-length($_[$i]))) {
1521 =head2 has_command(command)
1523 Returns the full path to the executable if some command is in the path, or
1524 undef if not found. If the given command is already an absolute path and
1525 exists, then the same path will be returned.
1530 if (!$_[0]) { return undef; }
1531 if (exists($main::has_command_cache{$_[0]})) {
1532 return $main::has_command_cache{$_[0]};
1535 my $slash = $gconfig{'os_type'} eq 'windows' ? '\\' : '/';
1536 if ($_[0] =~ /^\// || $_[0] =~ /^[a-z]:[\\\/]/i) {
1537 # Absolute path given - just use it
1538 my $t = &translate_filename($_[0]);
1539 $rv = (-x $t && !-d _) ? $_[0] : undef;
1542 # Check each directory in the path
1544 foreach my $d (split($path_separator, $ENV{'PATH'})) {
1545 next if ($donedir{$d}++);
1546 $d =~ s/$slash$// if ($d ne $slash);
1547 my $t = &translate_filename("$d/$_[0]");
1548 if (-x $t && !-d _) {
1549 $rv = $d.$slash.$_[0];
1552 if ($gconfig{'os_type'} eq 'windows') {
1553 foreach my $sfx (".exe", ".com", ".bat") {
1554 my $t = &translate_filename("$d/$_[0]").$sfx;
1555 if (-r $t && !-d _) {
1556 $rv = $d.$slash.$_[0].$sfx;
1563 $main::has_command_cache{$_[0]} = $rv;
1567 =head2 make_date(seconds, [date-only], [fmt])
1569 Converts a Unix date/time in seconds to a human-readable form, by default
1570 formatted like dd/mmm/yyyy hh:mm:ss. Parameters are :
1572 =item seconds - Unix time is seconds to convert.
1574 =item date-only - If set to 1, exclude the time from the returned string.
1576 =item fmt - Optional, one of dd/mon/yyyy, dd/mm/yyyy, mm/dd/yyyy or yyyy/mm/dd
1581 my ($secs, $only, $fmt) = @_;
1582 my @tm = localtime($secs);
1585 $fmt = $gconfig{'dateformat'} || 'dd/mon/yyyy';
1587 if ($fmt eq 'dd/mon/yyyy') {
1588 $date = sprintf "%2.2d/%s/%4.4d",
1589 $tm[3], $text{"smonth_".($tm[4]+1)}, $tm[5]+1900;
1591 elsif ($fmt eq 'dd/mm/yyyy') {
1592 $date = sprintf "%2.2d/%2.2d/%4.4d", $tm[3], $tm[4]+1, $tm[5]+1900;
1594 elsif ($fmt eq 'mm/dd/yyyy') {
1595 $date = sprintf "%2.2d/%2.2d/%4.4d", $tm[4]+1, $tm[3], $tm[5]+1900;
1597 elsif ($fmt eq 'yyyy/mm/dd') {
1598 $date = sprintf "%4.4d/%2.2d/%2.2d", $tm[5]+1900, $tm[4]+1, $tm[3];
1600 elsif ($fmt eq 'd. mon yyyy') {
1601 $date = sprintf "%d. %s %4.4d",
1602 $tm[3], $text{"smonth_".($tm[4]+1)}, $tm[5]+1900;
1604 elsif ($fmt eq 'dd.mm.yyyy') {
1605 $date = sprintf "%2.2d.%2.2d.%4.4d", $tm[3], $tm[4]+1, $tm[5]+1900;
1607 elsif ($fmt eq 'yyyy-mm-dd') {
1608 $date = sprintf "%4.4d-%2.2d-%2.2d", $tm[5]+1900, $tm[4]+1, $tm[3];
1611 $date .= sprintf " %2.2d:%2.2d", $tm[2], $tm[1];
1616 =head2 file_chooser_button(input, type, [form], [chroot], [addmode])
1618 Return HTML for a button that pops up a file chooser when clicked, and places
1619 the selected filename into another HTML field. The parameters are :
1621 =item input - Name of the form field to store the filename in.
1623 =item type - 0 for file or directory chooser, or 1 for directory only.
1625 =item form - Index of the form containing the button.
1627 =item chroot - If set to 1, the chooser will be limited to this directory.
1629 =item addmode - If set to 1, the selected filename will be appended to the text box instead of replacing it's contents.
1632 sub file_chooser_button
1634 return &theme_file_chooser_button(@_)
1635 if (defined(&theme_file_chooser_button));
1636 my $form = defined($_[2]) ? $_[2] : 0;
1637 my $chroot = defined($_[3]) ? $_[3] : "/";
1638 my $add = int($_[4]);
1639 my ($w, $h) = (400, 300);
1640 if ($gconfig{'db_sizefile'}) {
1641 ($w, $h) = split(/x/, $gconfig{'db_sizefile'});
1643 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";
1646 =head2 popup_window_button(url, width, height, scrollbars?, &field-mappings)
1648 Returns HTML for a button that will popup a chooser window of some kind. The
1651 =item url - Base URL of the popup window's contents
1653 =item width - Width of the window in pixels
1655 =item height - Height in pixels
1657 =item scrollbars - Set to 1 if the window should have scrollbars
1659 The field-mappings parameter is an array ref of array refs containing
1661 =item - Attribute to assign field to in the popup window
1663 =item - Form field name
1665 =item - CGI parameter to URL for value, if any
1668 sub popup_window_button
1670 return &theme_popup_window_button(@_) if (defined(&theme_popup_window_button));
1671 my ($url, $w, $h, $scroll, $fields) = @_;
1672 my $scrollyn = $scroll ? "yes" : "no";
1673 my $rv = "<input type=button onClick='";
1674 foreach my $m (@$fields) {
1675 $rv .= "$m->[0] = form.$m->[1]; ";
1677 my $sep = $url =~ /\?/ ? "&" : "?";
1678 $rv .= "chooser = window.open(\"$url\"";
1679 foreach my $m (@$fields) {
1681 $rv .= "+\"$sep$m->[2]=\"+escape($m->[0].value)";
1685 $rv .= ", \"chooser\", \"toolbar=no,menubar=no,scrollbars=$scrollyn,resizable=yes,width=$w,height=$h\"); ";
1686 foreach my $m (@$fields) {
1687 $rv .= "chooser.$m->[0] = $m->[0]; ";
1688 $rv .= "window.$m->[0] = $m->[0]; ";
1690 $rv .= "' value=\"...\">";
1694 =head2 read_acl(&user-module-hash, &user-list-hash, [&only-users])
1696 Reads the Webmin acl file into the given hash references. The first is indexed
1697 by a combined key of username,module , with the value being set to 1 when
1698 the user has access to that module. The second is indexed by username, with
1699 the value being an array ref of allowed modules.
1701 This function is deprecated in favour of foreign_available, which performs a
1702 more comprehensive check of module availability.
1704 If the only-users array ref parameter is given, the results may be limited to
1705 users in that list of names.
1710 my ($usermod, $userlist, $only) = @_;
1711 if (!%main::acl_hash_cache) {
1712 # Read from local files
1714 open(ACL, &acl_filename());
1716 if (/^([^:]+):\s*(.*)/) {
1718 my @mods = split(/\s+/, $2);
1719 foreach my $m (@mods) {
1720 $main::acl_hash_cache{$user,$m}++;
1722 $main::acl_array_cache{$user} = \@mods;
1727 %$usermod = %main::acl_hash_cache if ($usermod);
1728 %$userlist = %main::acl_array_cache if ($userlist);
1731 my $userdb = &get_userdb_string();
1732 my ($dbh, $proto, $prefix, $args) =
1733 $userdb ? &connect_userdb($userdb) : ( );
1735 if ($proto eq "mysql" || $proto eq "postgresql") {
1736 # Select usernames and modules from SQL DB
1737 my $cmd = $dbh->prepare(
1738 "select webmin_user.name,webmin_user_attr.value ".
1739 "from webmin_user,webmin_user_attr ".
1740 "where webmin_user.id = webmin_user_attr.id ".
1741 "and webmin_user_attr.attr = 'modules' ".
1742 ($only ? " and webmin_user.name in (".
1743 join(",", map { "'$_'" } @$only).")" : ""));
1744 if ($cmd && $cmd->execute()) {
1745 while(my ($user, $mods) = $cmd->fetchrow()) {
1746 my @mods = split(/\s+/, $mods);
1747 foreach my $m (@mods) {
1748 $usermod->{$user,$m}++ if ($usermod);
1750 $userlist->{$user} = \@mods if ($userlist);
1753 $cmd->finish() if ($cmd);
1755 elsif ($proto eq "ldap") {
1756 # Find users in LDAP
1757 my $filter = '(objectClass='.$args->{'userclass'}.')';
1760 "(|".join("", map { "(cn=$_)" } @$only).")";
1761 $filter = "(&".$filter.$ufilter.")";
1763 my $rv = $dbh->search(
1767 attrs => [ 'cn', 'webminModule' ]);
1768 if ($rv && !$rv->code) {
1769 foreach my $u ($rv->all_entries) {
1770 my $user = $u->get_value('cn');
1771 my @mods =$u->get_value('webminModule');
1772 foreach my $m (@mods) {
1773 $usermod->{$user,$m}++ if ($usermod);
1775 $userlist->{$user} = \@mods if ($userlist);
1779 &disconnect_userdb($userdb, $dbh);
1785 Returns the file containing the webmin ACL, which is usually
1786 /etc/webmin/webmin.acl.
1791 return "$config_directory/webmin.acl";
1796 Does nothing, but kept around for compatability.
1803 =head2 get_miniserv_config(&hash)
1805 Reads the Webmin webserver's (miniserv.pl) configuration file, usually located
1806 at /etc/webmin/miniserv.conf, and stores its names and values in the given
1810 sub get_miniserv_config
1812 return &read_file_cached(
1813 $ENV{'MINISERV_CONFIG'} || "$config_directory/miniserv.conf", $_[0]);
1816 =head2 put_miniserv_config(&hash)
1818 Writes out the Webmin webserver configuration file from the contents of
1819 the given hash ref. This should be initially populated by get_miniserv_config,
1822 get_miniserv_config(\%miniserv);
1823 $miniserv{'port'} = 10005;
1824 put_miniserv_config(\%miniserv);
1828 sub put_miniserv_config
1830 &write_file($ENV{'MINISERV_CONFIG'} || "$config_directory/miniserv.conf",
1834 =head2 restart_miniserv([nowait])
1836 Kill the old miniserv process and re-start it, then optionally waits for
1837 it to restart. This will apply all configuration settings.
1840 sub restart_miniserv
1843 return undef if (&is_readonly_mode());
1845 &get_miniserv_config(\%miniserv) || return;
1848 if ($gconfig{'os_type'} ne 'windows') {
1849 # On Unix systems, we can restart with a signal
1850 my ($pid, $addr, $i);
1851 $miniserv{'inetd'} && return;
1852 my @oldst = stat($miniserv{'pidfile'});
1853 $pid = $ENV{'MINISERV_PID'};
1855 open(PID, $miniserv{'pidfile'}) ||
1856 &error("Failed to open PID file $miniserv{'pidfile'}");
1859 $pid || &error("Invalid PID file $miniserv{'pidfile'}");
1862 # Just signal miniserv to restart
1863 &kill_logged('HUP', $pid) || &error("Incorrect Webmin PID $pid");
1865 # Wait till new PID is written, indicating a restart
1866 for($i=0; $i<60; $i++) {
1868 my @newst = stat($miniserv{'pidfile'});
1869 last if ($newst[9] != $oldst[9]);
1871 $i < 60 || &error("Webmin server did not write new PID file");
1873 ## Totally kill the process and re-run it
1874 #$SIG{'TERM'} = 'IGNORE';
1875 #&kill_logged('TERM', $pid);
1876 #&system_logged("$config_directory/start >/dev/null 2>&1 </dev/null");
1879 # On Windows, we need to use the flag file
1880 open(TOUCH, ">$miniserv{'restartflag'}");
1885 # Wait for miniserv to come back up
1886 my $addr = $miniserv{'bind'} || "127.0.0.1";
1888 for($i=0; $i<20; $i++) {
1891 &open_socket($addr, $miniserv{'port'}, STEST, \$err);
1893 last if (!$err && ++$ok >= 2);
1895 $i < 20 || &error("Failed to restart Webmin server!");
1899 =head2 reload_miniserv
1901 Sends a USR1 signal to the miniserv process, telling it to read-read it's
1902 configuration files. Not all changes will be applied though, such as the
1903 IP addresses and ports to accept connections on.
1908 return undef if (&is_readonly_mode());
1910 &get_miniserv_config(\%miniserv) || return;
1912 if ($gconfig{'os_type'} ne 'windows') {
1913 # Send a USR1 signal to re-read the config
1914 my ($pid, $addr, $i);
1915 $miniserv{'inetd'} && return;
1916 $pid = $ENV{'MINISERV_PID'};
1918 open(PID, $miniserv{'pidfile'}) ||
1919 &error("Failed to open PID file $miniserv{'pidfile'}");
1922 $pid || &error("Invalid PID file $miniserv{'pidfile'}");
1924 &kill_logged('USR1', $pid) || &error("Incorrect Webmin PID $pid");
1926 # Make sure this didn't kill Webmin!
1928 if (!kill(0, $pid)) {
1929 print STDERR "USR1 signal killed Webmin - restarting\n";
1930 &system_logged("$config_directory/start >/dev/null 2>&1 </dev/null");
1934 # On Windows, we need to use the flag file
1935 open(TOUCH, ">$miniserv{'reloadflag'}");
1940 =head2 check_os_support(&minfo, [os-type, os-version], [api-only])
1942 Returns 1 if some module is supported on the current operating system, or the
1943 OS supplies as parameters. The parameters are :
1945 =item minfo - A hash ref of module information, as returned by get_module_info
1947 =item os-type - The Webmin OS code to use instead of the system's real OS, such as redhat-linux
1949 =item os-version - The Webmin OS version to use, such as 13.0
1951 =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.
1954 sub check_os_support
1956 my $oss = $_[0]->{'os_support'};
1957 if ($_[3] && $oss && $_[0]->{'api_os_support'}) {
1958 # May provide usable API
1959 $oss .= " ".$_[0]->{'api_os_support'};
1961 if ($_[0]->{'nozone'} && &running_in_zone()) {
1962 # Not supported in a Solaris Zone
1965 if ($_[0]->{'novserver'} && &running_in_vserver()) {
1966 # Not supported in a Linux Vserver
1969 if ($_[0]->{'noopenvz'} && &running_in_openvz()) {
1970 # Not supported in an OpenVZ container
1973 return 1 if (!$oss || $oss eq '*');
1974 my $osver = $_[2] || $gconfig{'os_version'};
1975 my $ostype = $_[1] || $gconfig{'os_type'};
1978 my ($os, $ver, $codes);
1979 my ($neg) = ($oss =~ s/^!//); # starts with !
1980 $anyneg++ if ($neg);
1981 if ($oss =~ /^([^\/\s]+)\/([^\{\s]+)\{([^\}]*)\}\s*(.*)$/) {
1983 $os = $1; $ver = $2; $codes = $3; $oss = $4;
1985 elsif ($oss =~ /^([^\/\s]+)\/([^\/\s]+)\s*(.*)$/) {
1987 $os = $1; $ver = $2; $oss = $3;
1989 elsif ($oss =~ /^([^\{\s]+)\{([^\}]*)\}\s*(.*)$/) {
1991 $os = $1; $codes = $2; $oss = $3;
1993 elsif ($oss =~ /^\{([^\}]*)\}\s*(.*)$/) {
1995 $codes = $1; $oss = $2;
1997 elsif ($oss =~ /^(\S+)\s*(.*)$/) {
1999 $os = $1; $oss = $2;
2002 next if ($os && !($os eq $ostype ||
2003 $ostype =~ /^(\S+)-(\S+)$/ && $os eq "*-$2"));
2004 if ($ver =~ /^([0-9\.]+)\-([0-9\.]+)$/) {
2005 next if ($osver < $1 || $osver > $2);
2007 elsif ($ver =~ /^([0-9\.]+)\-\*$/) {
2008 next if ($osver < $1);
2010 elsif ($ver =~ /^\*\-([0-9\.]+)$/) {
2011 next if ($osver > $1);
2014 next if ($ver ne $osver);
2016 next if ($codes && !eval $codes);
2022 =head2 http_download(host, port, page, destfile, [&error], [&callback], [sslmode], [user, pass], [timeout], [osdn-convert], [no-cache], [&headers])
2024 Downloads data from a HTTP url to a local file or string. The parameters are :
2026 =item host - The hostname part of the URL, such as www.google.com
2028 =item port - The HTTP port number, such as 80
2030 =item page - The filename part of the URL, like /index.html
2032 =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.
2034 =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.
2036 =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.
2038 =item sslmode - If set to 1, an HTTPS connection is used instead of HTTP.
2040 =item user - If set, HTTP authentication is done with this username.
2042 =item pass - The HTTP password to use with the username above.
2044 =item timeout - A timeout in seconds to wait for the TCP connection to be established before failing.
2046 =item osdn-convert - If set to 1, URL for downloads from sourceforge are converted to use an appropriate mirror site.
2048 =item no-cache - If set to 1, Webmin's internal caching for this URL is disabled.
2050 =item headers - If set to a hash ref of additional HTTP headers, they will be added to the request.
2055 my ($host, $port, $page, $dest, $error, $cbfunc, $ssl, $user, $pass,
2056 $timeout, $osdn, $nocache, $headers) = @_;
2057 if ($gconfig{'debug_what_net'}) {
2058 &webmin_debug_log('HTTP', "host=$host port=$port page=$page ssl=$ssl".
2059 ($user ? " user=$user pass=$pass" : "").
2060 (ref($dest) ? "" : " dest=$dest"));
2063 # Convert OSDN URL first
2064 my $prot = $ssl ? "https://" : "http://";
2065 my $portstr = $ssl && $port == 443 ||
2066 !$ssl && $port == 80 ? "" : ":$port";
2067 ($host, $port, $page, $ssl) = &parse_http_url(
2068 &convert_osdn_url($prot.$host.$portstr.$page));
2071 # Check if we already have cached the URL
2072 my $url = ($ssl ? "https://" : "http://").$host.":".$port.$page;
2073 my $cfile = &check_in_http_cache($url);
2074 if ($cfile && !$nocache) {
2075 # Yes! Copy to dest file or variable
2076 &$cbfunc(6, $url) if ($cbfunc);
2078 &open_readfile(CACHEFILE, $cfile);
2080 $$dest = <CACHEFILE>;
2084 ©_source_dest($cfile, $dest);
2091 push(@headers, [ "Host", $host ]);
2092 push(@headers, [ "User-agent", "Webmin" ]);
2093 push(@headers, [ "Accept-language", "en" ]);
2095 my $auth = &encode_base64("$user:$pass");
2096 $auth =~ tr/\r\n//d;
2097 push(@headers, [ "Authorization", "Basic $auth" ]);
2099 foreach my $hname (keys %$headers) {
2100 push(@headers, [ $hname, $headers->{$hname} ]);
2103 # Actually download it
2104 $main::download_timed_out = undef;
2105 local $SIG{ALRM} = \&download_timeout;
2106 alarm($timeout || 60);
2107 my $h = &make_http_connection($host, $port, $ssl, "GET", $page, \@headers);
2109 $h = $main::download_timed_out if ($main::download_timed_out);
2111 if ($error) { $$error = $h; return; }
2112 else { &error($h); }
2114 &complete_http_download($h, $dest, $error, $cbfunc, $osdn, $host, $port,
2115 $headers, $ssl, $nocache);
2116 if ((!$error || !$$error) && !$nocache) {
2117 &write_to_http_cache($url, $dest);
2121 =head2 complete_http_download(handle, destfile, [&error], [&callback], [osdn], [oldhost], [oldport], [&send-headers], [old-ssl], [no-cache])
2123 Do a HTTP download, after the headers have been sent. For internal use only,
2124 typically called by http_download.
2127 sub complete_http_download
2129 local ($line, %header, @headers, $s); # Kept local so that callback funcs
2135 ($line = &read_http_connection($_[0])) =~ tr/\r\n//d;
2136 if ($line !~ /^HTTP\/1\..\s+(200|30[0-9])(\s+|$)/) {
2138 if ($_[2]) { ${$_[2]} = $line; return; }
2139 else { &error("Download failed : $line"); }
2142 &$cbfunc(1, $rcode >= 300 && $rcode < 400 ? 1 : 0)
2145 $line = &read_http_connection($_[0]);
2146 $line =~ tr/\r\n//d;
2147 $line =~ /^(\S+):\s+(.*)$/ || last;
2148 $header{lc($1)} = $2;
2149 push(@headers, [ lc($1), $2 ]);
2152 if ($main::download_timed_out) {
2153 if ($_[2]) { ${$_[2]} = $main::download_timed_out; return 0; }
2154 else { &error($main::download_timed_out); }
2156 &$cbfunc(2, $header{'content-length'}) if ($cbfunc);
2157 if ($rcode >= 300 && $rcode < 400) {
2158 # follow the redirect
2159 &$cbfunc(5, $header{'location'}) if ($cbfunc);
2160 my ($host, $port, $page, $ssl);
2161 if ($header{'location'} =~ /^(http|https):\/\/([^:]+):(\d+)(\/.*)?$/) {
2162 $ssl = $1 eq 'https' ? 1 : 0;
2163 $host = $2; $port = $3; $page = $4 || "/";
2165 elsif ($header{'location'} =~ /^(http|https):\/\/([^:\/]+)(\/.*)?$/) {
2166 $ssl = $1 eq 'https' ? 1 : 0;
2167 $host = $2; $port = 80; $page = $3 || "/";
2169 elsif ($header{'location'} =~ /^\// && $_[5]) {
2170 # Relative to same server
2174 $page = $header{'location'};
2176 elsif ($header{'location'}) {
2177 # Assume relative to same dir .. not handled
2178 if ($_[2]) { ${$_[2]} = "Invalid Location header $header{'location'}"; return; }
2179 else { &error("Invalid Location header $header{'location'}"); }
2182 if ($_[2]) { ${$_[2]} = "Missing Location header"; return; }
2183 else { &error("Missing Location header"); }
2186 ($page, $params) = split(/\?/, $page);
2188 $page .= "?".$params if (defined($params));
2189 &http_download($host, $port, $page, $_[1], $_[2], $cbfunc, $ssl,
2190 undef, undef, undef, $_[4], $_[9], $_[7]);
2195 # Append to a variable
2196 while(defined($buf = &read_http_connection($_[0], 1024))) {
2198 &$cbfunc(3, length(${$_[1]})) if ($cbfunc);
2204 if (!&open_tempfile(PFILE, ">$_[1]", 1)) {
2205 if ($_[2]) { ${$_[2]} = "Failed to write to $_[1] : $!"; return; }
2206 else { &error("Failed to write to $_[1] : $!"); }
2208 binmode(PFILE); # For windows
2209 while(defined($buf = &read_http_connection($_[0], 1024))) {
2210 &print_tempfile(PFILE, $buf);
2211 $got += length($buf);
2212 &$cbfunc(3, $got) if ($cbfunc);
2214 &close_tempfile(PFILE);
2215 if ($header{'content-length'} &&
2216 $got != $header{'content-length'}) {
2217 if ($_[2]) { ${$_[2]} = "Download incomplete"; return; }
2218 else { &error("Download incomplete"); }
2221 &$cbfunc(4) if ($cbfunc);
2223 &close_http_connection($_[0]);
2227 =head2 ftp_download(host, file, destfile, [&error], [&callback], [user, pass], [port])
2229 Download data from an FTP site to a local file. The parameters are :
2231 =item host - FTP server hostname
2233 =item file - File on the FTP server to download
2235 =item destfile - File on the Webmin system to download data to
2237 =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.
2239 =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.
2241 =item user - Username to login to the FTP server as. If missing, Webmin will login as anonymous.
2243 =item pass - Password for the username above.
2245 =item port - FTP server port number, which defaults to 21 if not set.
2250 my ($host, $file, $dest, $error, $cbfunc, $user, $pass, $port) = @_;
2252 if ($gconfig{'debug_what_net'}) {
2253 &webmin_debug_log('FTP', "host=$host port=$port file=$file".
2254 ($user ? " user=$user pass=$pass" : "").
2255 (ref($dest) ? "" : " dest=$dest"));
2259 if (&is_readonly_mode()) {
2260 if ($_[3]) { ${$_[3]} = "FTP connections not allowed in readonly mode";
2262 else { &error("FTP connections not allowed in readonly mode"); }
2265 # Check if we already have cached the URL
2266 my $url = "ftp://".$host.$file;
2267 my $cfile = &check_in_http_cache($url);
2269 # Yes! Copy to dest file or variable
2270 &$cbfunc(6, $url) if ($cbfunc);
2272 &open_readfile(CACHEFILE, $cfile);
2274 $$dest = <CACHEFILE>;
2278 ©_source_dest($cfile, $dest);
2283 # Actually download it
2284 $main::download_timed_out = undef;
2285 local $SIG{ALRM} = \&download_timeout;
2288 if ($gconfig{'ftp_proxy'} =~ /^http:\/\/(\S+):(\d+)/ && !&no_proxy($_[0])) {
2289 # download through http-style proxy
2291 if (&open_socket($1, $2, "SOCK", \$error)) {
2293 if ($main::download_timed_out) {
2295 if ($_[3]) { ${$_[3]} = $main::download_timed_out; return 0; }
2296 else { &error($main::download_timed_out); }
2298 my $esc = $_[1]; $esc =~ s/ /%20/g;
2299 my $up = "$_[5]:$_[6]\@" if ($_[5]);
2300 my $portstr = $port == 21 ? "" : ":$port";
2301 print SOCK "GET ftp://$up$_[0]$portstr$esc HTTP/1.0\r\n";
2302 print SOCK "User-agent: Webmin\r\n";
2303 if ($gconfig{'proxy_user'}) {
2304 my $auth = &encode_base64(
2305 "$gconfig{'proxy_user'}:$gconfig{'proxy_pass'}");
2306 $auth =~ tr/\r\n//d;
2307 print SOCK "Proxy-Authorization: Basic $auth\r\n";
2310 &complete_http_download({ 'fh' => "SOCK" }, $_[2], $_[3], $_[4]);
2313 elsif (!$gconfig{'proxy_fallback'}) {
2315 if ($error) { $$error = $main::download_timed_out; return 0; }
2316 else { &error($main::download_timed_out); }
2321 # connect to host and login with real FTP protocol
2322 &open_socket($_[0], $port, "SOCK", $_[3]) || return 0;
2324 if ($main::download_timed_out) {
2325 if ($_[3]) { ${$_[3]} = $main::download_timed_out; return 0; }
2326 else { &error($main::download_timed_out); }
2328 &ftp_command("", 2, $_[3]) || return 0;
2330 # Login as supplied user
2331 my @urv = &ftp_command("USER $_[5]", [ 2, 3 ], $_[3]);
2333 if (int($urv[1]/100) == 3) {
2334 &ftp_command("PASS $_[6]", 2, $_[3]) || return 0;
2338 # Login as anonymous
2339 my @urv = &ftp_command("USER anonymous", [ 2, 3 ], $_[3]);
2341 if (int($urv[1]/100) == 3) {
2342 &ftp_command("PASS root\@".&get_system_hostname(), 2,
2346 &$cbfunc(1, 0) if ($cbfunc);
2349 # get the file size and tell the callback
2350 &ftp_command("TYPE I", 2, $_[3]) || return 0;
2351 my $size = &ftp_command("SIZE $_[1]", 2, $_[3]);
2352 defined($size) || return 0;
2354 &$cbfunc(2, int($size));
2358 my $pasv = &ftp_command("PASV", 2, $_[3]);
2359 defined($pasv) || return 0;
2360 $pasv =~ /\(([0-9,]+)\)/;
2361 @n = split(/,/ , $1);
2362 &open_socket("$n[0].$n[1].$n[2].$n[3]",
2363 $n[4]*256 + $n[5], "CON", $_[3]) || return 0;
2364 &ftp_command("RETR $_[1]", 1, $_[3]) || return 0;
2368 &open_tempfile(PFILE, ">$_[2]", 1);
2369 while(read(CON, $buf, 1024) > 0) {
2370 &print_tempfile(PFILE, $buf);
2371 $got += length($buf);
2372 &$cbfunc(3, $got) if ($cbfunc);
2374 &close_tempfile(PFILE);
2376 if ($got != $size) {
2377 if ($_[3]) { ${$_[3]} = "Download incomplete"; return 0; }
2378 else { &error("Download incomplete"); }
2380 &$cbfunc(4) if ($cbfunc);
2382 &ftp_command("", 2, $_[3]) || return 0;
2386 &ftp_command("QUIT", 2, $_[3]) || return 0;
2390 &write_to_http_cache($url, $dest);
2394 =head2 ftp_upload(host, file, srcfile, [&error], [&callback], [user, pass], [port])
2396 Upload data from a local file to an FTP site. The parameters are :
2398 =item host - FTP server hostname
2400 =item file - File on the FTP server to write to
2402 =item srcfile - File on the Webmin system to upload data from
2404 =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.
2406 =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.
2408 =item user - Username to login to the FTP server as. If missing, Webmin will login as anonymous.
2410 =item pass - Password for the username above.
2412 =item port - FTP server port number, which defaults to 21 if not set.
2419 if (&is_readonly_mode()) {
2420 if ($_[3]) { ${$_[3]} = "FTP connections not allowed in readonly mode";
2422 else { &error("FTP connections not allowed in readonly mode"); }
2425 $main::download_timed_out = undef;
2426 local $SIG{ALRM} = \&download_timeout;
2429 # connect to host and login
2430 &open_socket($_[0], $_[7] || 21, "SOCK", $_[3]) || return 0;
2432 if ($main::download_timed_out) {
2433 if ($_[3]) { ${$_[3]} = $main::download_timed_out; return 0; }
2434 else { &error($main::download_timed_out); }
2436 &ftp_command("", 2, $_[3]) || return 0;
2438 # Login as supplied user
2439 my @urv = &ftp_command("USER $_[5]", [ 2, 3 ], $_[3]);
2441 if (int($urv[1]/100) == 3) {
2442 &ftp_command("PASS $_[6]", 2, $_[3]) || return 0;
2446 # Login as anonymous
2447 my @urv = &ftp_command("USER anonymous", [ 2, 3 ], $_[3]);
2449 if (int($urv[1]/100) == 3) {
2450 &ftp_command("PASS root\@".&get_system_hostname(), 2,
2454 &$cbfunc(1, 0) if ($cbfunc);
2456 &ftp_command("TYPE I", 2, $_[3]) || return 0;
2458 # get the file size and tell the callback
2459 my @st = stat($_[2]);
2461 &$cbfunc(2, $st[7]);
2465 my $pasv = &ftp_command("PASV", 2, $_[3]);
2466 defined($pasv) || return 0;
2467 $pasv =~ /\(([0-9,]+)\)/;
2468 @n = split(/,/ , $1);
2469 &open_socket("$n[0].$n[1].$n[2].$n[3]", $n[4]*256 + $n[5], "CON", $_[3]) || return 0;
2470 &ftp_command("STOR $_[1]", 1, $_[3]) || return 0;
2475 while(read(PFILE, $buf, 1024) > 0) {
2477 $got += length($buf);
2478 &$cbfunc(3, $got) if ($cbfunc);
2482 if ($got != $st[7]) {
2483 if ($_[3]) { ${$_[3]} = "Upload incomplete"; return 0; }
2484 else { &error("Upload incomplete"); }
2486 &$cbfunc(4) if ($cbfunc);
2489 &ftp_command("", 2, $_[3]) || return 0;
2490 &ftp_command("QUIT", 2, $_[3]) || return 0;
2496 =head2 no_proxy(host)
2498 Checks if some host is on the no proxy list. For internal use by the
2499 http_download and ftp_download functions.
2504 my $ip = &to_ipaddress($_[0]);
2505 foreach my $n (split(/\s+/, $gconfig{'noproxy'})) {
2506 return 1 if ($_[0] =~ /\Q$n\E/ ||
2512 =head2 open_socket(host, port, handle, [&error])
2514 Open a TCP connection to some host and port, using a file handle. The
2517 =item host - Hostname or IP address to connect to.
2519 =item port - TCP port number.
2521 =item handle - A file handle name to use for the connection.
2523 =item error - A string reference to write any error message into. If not set, the error function is called on failure.
2528 my ($host, $port, $fh, $err) = @_;
2529 $fh = &callers_package($fh);
2531 if ($gconfig{'debug_what_net'}) {
2532 &webmin_debug_log('TCP', "host=$host port=$port");
2535 # Lookup IP address for the host. Try v4 first, and failing that v6
2537 my $proto = getprotobyname("tcp");
2538 if ($ip = &to_ipaddress($host)) {
2539 # Create IPv4 socket and connection
2540 if (!socket($fh, PF_INET, SOCK_STREAM, $proto)) {
2541 my $msg = "Failed to create socket : $!";
2542 if ($err) { $$err = $msg; return 0; }
2543 else { &error($msg); }
2545 my $addr = inet_aton($ip);
2546 if ($gconfig{'bind_proxy'}) {
2547 # BIND to outgoing IP
2548 if (!bind($fh,pack_sockaddr_in(0, inet_aton($gconfig{'bind_proxy'})))) {
2549 my $msg = "Failed to bind to source address : $!";
2550 if ($err) { $$err = $msg; return 0; }
2551 else { &error($msg); }
2554 if (!connect($fh, pack_sockaddr_in($port, $addr))) {
2555 my $msg = "Failed to connect to $host:$port : $!";
2556 if ($err) { $$err = $msg; return 0; }
2557 else { &error($msg); }
2560 elsif ($ip = &to_ip6address($host)) {
2561 # Create IPv6 socket and connection
2562 if (!socket($fh, Socket6::PF_INET6(), SOCK_STREAM, $proto)) {
2563 my $msg = "Failed to create IPv6 socket : $!";
2564 if ($err) { $$err = $msg; return 0; }
2565 else { &error($msg); }
2567 my $addr = inet_pton(Socket6::AF_INET6(), $ip);
2568 if (!connect($fh, pack_sockaddr_in6($port, $addr))) {
2569 my $msg = "Failed to IPv6 connect to $host:$port : $!";
2570 if ($err) { $$err = $msg; return 0; }
2571 else { &error($msg); }
2576 my $msg = "Failed to lookup IP address for $host";
2577 if ($err) { $$err = $msg; return 0; }
2578 else { &error($msg); }
2582 my $old = select($fh);
2588 =head2 download_timeout
2590 Called when a download times out. For internal use only.
2593 sub download_timeout
2595 $main::download_timed_out = "Download timed out";
2598 =head2 ftp_command(command, expected, [&error], [filehandle])
2600 Send an FTP command, and die if the reply is not what was expected. Mainly
2601 for internal use by the ftp_download and ftp_upload functions.
2606 my ($cmd, $expect, $err, $fh) = @_;
2608 $fh = &callers_package($fh);
2611 my $what = $cmd ne "" ? "<i>$cmd</i>" : "initial connection";
2613 print $fh "$cmd\r\n";
2616 if (!($line = <$fh>)) {
2618 if ($err) { $$err = "Failed to read reply to $what"; return undef; }
2619 else { &error("Failed to read reply to $what"); }
2621 $line =~ /^(...)(.)(.*)$/;
2624 foreach my $c (@$expect) {
2625 $found++ if (int($1/100) == $c);
2629 $found++ if (int($1/100) == $_[1]);
2633 if ($err) { $$err = "$what failed : $3"; return undef; }
2634 else { &error("$what failed : $3"); }
2639 # Need to skip extra stuff..
2641 if (!($line = <$fh>)) {
2643 if ($$err) { $$err = "Failed to read reply to $what";
2645 else { &error("Failed to read reply to $what"); }
2647 $line =~ /^(....)(.*)$/; $reply .= $2;
2648 if ($1 eq "$rcode ") { last; }
2652 return wantarray ? ($reply, $rcode) : $reply;
2655 =head2 to_ipaddress(hostname)
2657 Converts a hostname to an a.b.c.d format IP address, or returns undef if
2658 it cannot be resolved.
2663 if (&check_ipaddress($_[0])) {
2664 return $_[0]; # Already in v4 format
2666 elsif (&check_ip6address($_[0])) {
2667 return undef; # A v6 address cannot be converted to v4
2670 my $hn = gethostbyname($_[0]);
2671 return undef if (!$hn);
2672 local @ip = unpack("CCCC", $hn);
2673 return join("." , @ip);
2677 =head2 to_ip6address(hostname)
2679 Converts a hostname to IPv6 address, or returns undef if it cannot be resolved.
2684 if (&check_ip6address($_[0])) {
2685 return $_[0]; # Already in v6 format
2687 elsif (&check_ipaddress($_[0])) {
2688 return undef; # A v4 address cannot be v6
2690 elsif (!&supports_ipv6()) {
2691 return undef; # Cannot lookup
2694 # Perform IPv6 DNS lookup
2696 (undef, undef, undef, $inaddr) =
2697 getaddrinfo($_[0], undef, Socket6::AF_INET6(), SOCK_STREAM);
2698 return undef if (!$inaddr);
2700 (undef, $addr) = unpack_sockaddr_in6($inaddr);
2701 return inet_ntop(Socket6::AF_INET6(), $addr);
2705 =head2 to_hostname(ipv4|ipv6-address)
2707 Reverse-resolves an IPv4 or 6 address to a hostname
2713 if (&check_ip6address($addr) && &supports_ipv6()) {
2714 return gethostbyaddr(inet_pton(Socket6::AF_INET6(), $addr),
2715 Socket6::AF_INET6());
2718 return gethostbyaddr(inet_aton($addr), AF_INET);
2722 =head2 icons_table(&links, &titles, &icons, [columns], [href], [width], [height], &befores, &afters)
2724 Renders a 4-column table of icons. The useful parameters are :
2726 =item links - An array ref of link destination URLs for the icons.
2728 =item titles - An array ref of titles to appear under the icons.
2730 =item icons - An array ref of URLs for icon images.
2732 =item columns - Number of columns to layout the icons with. Defaults to 4.
2737 &load_theme_library();
2738 if (defined(&theme_icons_table)) {
2739 &theme_icons_table(@_);
2743 my $cols = $_[3] ? $_[3] : 4;
2744 my $per = int(100.0 / $cols);
2745 print "<table class='icons_table' width=100% cellpadding=5>\n";
2746 for(my $i=0; $i<@{$_[0]}; $i++) {
2747 if ($i%$cols == 0) { print "<tr>\n"; }
2748 print "<td width=$per% align=center valign=top>\n";
2749 &generate_icon($_[2]->[$i], $_[1]->[$i], $_[0]->[$i],
2750 ref($_[4]) ? $_[4]->[$i] : $_[4], $_[5], $_[6],
2751 $_[7]->[$i], $_[8]->[$i]);
2753 if ($i%$cols == $cols-1) { print "</tr>\n"; }
2755 while($i++%$cols) { print "<td width=$per%></td>\n"; $need_tr++; }
2756 print "</tr>\n" if ($need_tr);
2760 =head2 replace_file_line(file, line, [newline]*)
2762 Replaces one line in some file with 0 or more new lines. The parameters are :
2764 =item file - Full path to some file, like /etc/hosts.
2766 =item line - Line number to replace, starting from 0.
2768 =item newline - Zero or more lines to put into the file at the given line number. These must be newline-terminated strings.
2771 sub replace_file_line
2774 my $realfile = &translate_filename($_[0]);
2775 open(FILE, $realfile);
2778 if (@_ > 2) { splice(@lines, $_[1], 1, @_[2..$#_]); }
2779 else { splice(@lines, $_[1], 1); }
2780 &open_tempfile(FILE, ">$realfile");
2781 &print_tempfile(FILE, @lines);
2782 &close_tempfile(FILE);
2785 =head2 read_file_lines(file, [readonly])
2787 Returns a reference to an array containing the lines from some file. This
2788 array can be modified, and will be written out when flush_file_lines()
2789 is called. The parameters are :
2791 =item file - Full path to the file to read.
2793 =item readonly - Should be set 1 if the caller is only going to read the lines, and never write it out.
2797 $lref = read_file_lines("/etc/hosts");
2798 push(@$lref, "127.0.0.1 localhost");
2799 flush_file_lines("/etc/hosts");
2805 my ($package, $filename, $line) = caller;
2806 print STDERR "Missing file to read at ${package}::${filename} line $line\n";
2808 my $realfile = &translate_filename($_[0]);
2809 if (!$main::file_cache{$realfile}) {
2812 &webmin_debug_log('READ', $_[0]) if ($gconfig{'debug_what_read'});
2813 open(READFILE, $realfile);
2816 $eol = /\r\n$/ ? "\r\n" : "\n";
2822 $main::file_cache{$realfile} = \@lines;
2823 $main::file_cache_noflush{$realfile} = $_[1];
2824 $main::file_cache_eol{$realfile} = $eol || "\n";
2827 # Make read-write if currently readonly
2829 $main::file_cache_noflush{$realfile} = 0;
2832 return $main::file_cache{$realfile};
2835 =head2 flush_file_lines([file], [eol])
2837 Write out to a file previously read by read_file_lines to disk (except
2838 for those marked readonly). The parameters are :
2840 =item file - The file to flush out.
2842 =item eof - End-of-line character for each line. Defaults to \n.
2845 sub flush_file_lines
2849 local $trans = &translate_filename($_[0]);
2850 $main::file_cache{$trans} ||
2851 &error("flush_file_lines called on non-loaded file $trans");
2852 push(@files, $trans);
2855 @files = ( keys %main::file_cache );
2857 foreach my $f (@files) {
2858 my $eol = $_[1] || $main::file_cache_eol{$f} || "\n";
2859 if (!$main::file_cache_noflush{$f}) {
2860 no warnings; # XXX Bareword file handles should go away
2861 &open_tempfile(FLUSHFILE, ">$f");
2862 foreach my $line (@{$main::file_cache{$f}}) {
2863 (print FLUSHFILE $line,$eol) ||
2864 &error(&text("efilewrite", $f, $!));
2866 &close_tempfile(FLUSHFILE);
2868 delete($main::file_cache{$f});
2869 delete($main::file_cache_noflush{$f});
2873 =head2 unflush_file_lines(file)
2875 Clear the internal cache of some given file, previously read by read_file_lines.
2878 sub unflush_file_lines
2880 my $realfile = &translate_filename($_[0]);
2881 delete($main::file_cache{$realfile});
2882 delete($main::file_cache_noflush{$realfile});
2885 =head2 unix_user_input(fieldname, user, [form])
2887 Returns HTML for an input to select a Unix user. By default this is a text
2888 box with a user popup button next to it.
2893 if (defined(&theme_unix_user_input)) {
2894 return &theme_unix_user_input(@_);
2896 return "<input name=$_[0] size=13 value=\"$_[1]\"> ".
2897 &user_chooser_button($_[0], 0, $_[2] || 0)."\n";
2900 =head2 unix_group_input(fieldname, user, [form])
2902 Returns HTML for an input to select a Unix group. By default this is a text
2903 box with a group popup button next to it.
2906 sub unix_group_input
2908 if (defined(&theme_unix_group_input)) {
2909 return &theme_unix_group_input(@_);
2911 return "<input name=$_[0] size=13 value=\"$_[1]\"> ".
2912 &group_chooser_button($_[0], 0, $_[2] || 0)."\n";
2915 =head2 hlink(text, page, [module], [width], [height])
2917 Returns HTML for a link that when clicked on pops up a window for a Webmin
2918 help page. The parameters are :
2920 =item text - Text for the link.
2922 =item page - Help page code, such as 'intro'.
2924 =item module - Module the help page is in. Defaults to the current module.
2926 =item width - Width of the help popup window. Defaults to 600 pixels.
2928 =item height - Height of the help popup window. Defaults to 400 pixels.
2930 The actual help pages are in each module's help sub-directory, in files with
2936 if (defined(&theme_hlink)) {
2937 return &theme_hlink(@_);
2939 my $mod = $_[2] ? $_[2] : &get_module_name();
2940 my $width = $_[3] || $tconfig{'help_width'} || $gconfig{'help_width'} || 600;
2941 my $height = $_[4] || $tconfig{'help_height'} || $gconfig{'help_height'} || 400;
2942 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>";
2945 =head2 user_chooser_button(field, multiple, [form])
2947 Returns HTML for a javascript button for choosing a Unix user or users.
2948 The parameters are :
2950 =item field - Name of the HTML field to place the username into.
2952 =item multiple - Set to 1 if multiple users can be selected.
2954 =item form - Index of the form on the page.
2957 sub user_chooser_button
2959 return undef if (!&supports_users());
2960 return &theme_user_chooser_button(@_)
2961 if (defined(&theme_user_chooser_button));
2962 my $form = defined($_[2]) ? $_[2] : 0;
2963 my $w = $_[1] ? 500 : 300;
2965 if ($_[1] && $gconfig{'db_sizeusers'}) {
2966 ($w, $h) = split(/x/, $gconfig{'db_sizeusers'});
2968 elsif (!$_[1] && $gconfig{'db_sizeuser'}) {
2969 ($w, $h) = split(/x/, $gconfig{'db_sizeuser'});
2971 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";
2974 =head2 group_chooser_button(field, multiple, [form])
2976 Returns HTML for a javascript button for choosing a Unix group or groups
2977 The parameters are :
2979 =item field - Name of the HTML field to place the group name into.
2981 =item multiple - Set to 1 if multiple groups can be selected.
2983 =item form - Index of the form on the page.
2986 sub group_chooser_button
2988 return undef if (!&supports_users());
2989 return &theme_group_chooser_button(@_)
2990 if (defined(&theme_group_chooser_button));
2991 my $form = defined($_[2]) ? $_[2] : 0;
2992 my $w = $_[1] ? 500 : 300;
2994 if ($_[1] && $gconfig{'db_sizeusers'}) {
2995 ($w, $h) = split(/x/, $gconfig{'db_sizeusers'});
2997 elsif (!$_[1] && $gconfig{'db_sizeuser'}) {
2998 ($w, $h) = split(/x/, $gconfig{'db_sizeuser'});
3000 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";
3003 =head2 foreign_check(module, [api-only])
3005 Checks if some other module exists and is supported on this OS. The parameters
3008 =item module - Name of the module to check.
3010 =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.
3015 my ($mod, $api) = @_;
3017 my $mdir = &module_root_directory($mod);
3018 &read_file_cached("$mdir/module.info", \%minfo) || return 0;
3019 return &check_os_support(\%minfo, undef, undef, $api);
3022 =head2 foreign_exists(module)
3024 Checks if some other module exists. The module parameter is the short module
3030 my $mdir = &module_root_directory($_[0]);
3031 return -r "$mdir/module.info";
3034 =head2 foreign_available(module)
3036 Returns 1 if some module is installed, and acessible to the current user. The
3037 module parameter is the module directory name.
3040 sub foreign_available
3042 return 0 if (!&foreign_check($_[0]) &&
3043 !$gconfig{'available_even_if_no_support'});
3044 my %foreign_module_info = &get_module_info($_[0]);
3046 # Check list of allowed modules
3048 &read_acl(\%acl, undef, [ $base_remote_user ]);
3049 return 0 if (!$acl{$base_remote_user,$_[0]} &&
3050 !$acl{$base_remote_user,'*'});
3052 # Check for usermod restrictions
3053 my @usermods = &list_usermods();
3054 return 0 if (!&available_usermods( [ \%foreign_module_info ], \@usermods));
3056 if (&get_product_name() eq "webmin") {
3057 # Check if the user has any RBAC privileges in this module
3058 if (&supports_rbac($_[0]) &&
3059 &use_rbac_module_acl(undef, $_[0])) {
3060 # RBAC is enabled for this user and module - check if he
3062 my $rbacs = &get_rbac_module_acl($remote_user, $_[0]);
3063 return 0 if (!$rbacs);
3065 elsif ($gconfig{'rbacdeny_'.$base_remote_user}) {
3066 # If denying access to modules not specifically allowed by
3067 # RBAC, then prevent access
3072 # Check readonly support
3073 if (&is_readonly_mode()) {
3074 return 0 if (!$foreign_module_info{'readonly'});
3077 # Check if theme vetos
3078 if (defined(&theme_foreign_available)) {
3079 return 0 if (!&theme_foreign_available($_[0]));
3082 # Check if licence module vetos
3083 if ($main::licence_module) {
3084 return 0 if (!&foreign_call($main::licence_module,
3085 "check_module_licence", $_[0]));
3091 =head2 foreign_require(module, [file], [package])
3093 Brings in functions from another module, and places them in the Perl namespace
3094 with the same name as the module. The parameters are :
3096 =item module - The source module's directory name, like sendmail.
3098 =item file - The API file in that module, like sendmail-lib.pl. If missing, all API files are loaded.
3100 =item package - Perl package to place the module's functions and global variables in.
3102 If the original module name contains dashes, they will be replaced with _ in
3108 my ($mod, $file, $pkg) = @_;
3109 $pkg ||= $mod || "global";
3110 $pkg =~ s/[^A-Za-z0-9]/_/g;
3113 push(@files, $file);
3117 my %minfo = &get_module_info($mod);
3118 if ($minfo{'library'}) {
3119 @files = split(/\s+/, $minfo{'library'});
3122 @files = ( $mod."-lib.pl" );
3125 @files = grep { !$main::done_foreign_require{$pkg,$_} } @files;
3126 return 1 if (!@files);
3127 foreach my $f (@files) {
3128 $main::done_foreign_require{$pkg,$f}++;
3131 my $mdir = &module_root_directory($mod);
3132 @INC = &unique($mdir, @INC);
3133 -d $mdir || &error("Module $mod does not exist");
3134 if (!&get_module_name() && $mod) {
3137 my $old_fmn = $ENV{'FOREIGN_MODULE_NAME'};
3138 my $old_frd = $ENV{'FOREIGN_ROOT_DIRECTORY'};
3139 my $code = "package $pkg; ".
3140 "\$ENV{'FOREIGN_MODULE_NAME'} = '$mod'; ".
3141 "\$ENV{'FOREIGN_ROOT_DIRECTORY'} = '$root_directory'; ";
3142 foreach my $f (@files) {
3143 $code .= "do '$mdir/$f' || die \$@; ";
3146 if (defined($old_fmn)) {
3147 $ENV{'FOREIGN_MODULE_NAME'} = $old_fmn;
3150 delete($ENV{'FOREIGN_MODULE_NAME'});
3152 if (defined($old_frd)) {
3153 $ENV{'FOREIGN_ROOT_DIRECTORY'} = $old_frd;
3156 delete($ENV{'FOREIGN_ROOT_DIRECTORY'});
3159 if ($@) { &error("Require $mod/$files[0] failed : <pre>$@</pre>"); }
3163 =head2 foreign_call(module, function, [arg]*)
3165 Call a function in another module. The module parameter is the target module
3166 directory name, function is the perl sub to call, and the remaining parameters
3167 are the arguments. However, unless you need to call a function whose name
3168 is dynamic, it is better to use Perl's cross-module function call syntax
3169 like module::function(args).
3174 my $pkg = $_[0] || "global";
3175 $pkg =~ s/[^A-Za-z0-9]/_/g;
3176 my @args = @_[2 .. @_-1];
3177 $main::foreign_args = \@args;
3178 my @rv = eval <<EOF;
3180 &$_[1](\@{\$main::foreign_args});
3182 if ($@) { &error("$_[0]::$_[1] failed : $@"); }
3183 return wantarray ? @rv : $rv[0];
3186 =head2 foreign_config(module, [user-config])
3188 Get the configuration from another module, and return it as a hash. If the
3189 user-config parameter is set to 1, returns the Usermin user-level preferences
3190 for the current user instead.
3195 my ($mod, $uc) = @_;
3198 &read_file_cached("$root_directory/$mod/defaultuconfig", \%fconfig);
3199 &read_file_cached("$config_directory/$mod/uconfig", \%fconfig);
3200 &read_file_cached("$user_config_directory/$mod/config", \%fconfig);
3203 &read_file_cached("$config_directory/$mod/config", \%fconfig);
3208 =head2 foreign_installed(module, mode)
3210 Checks if the server for some module is installed, and possibly also checks
3211 if the module has been configured by Webmin.
3212 For mode 1, returns 2 if the server is installed and configured for use by
3213 Webmin, 1 if installed but not configured, or 0 otherwise.
3214 For mode 0, returns 1 if installed, 0 if not.
3215 If the module does not provide an install_check.pl script, assumes that
3216 the server is installed.
3219 sub foreign_installed
3221 my ($mod, $configured) = @_;
3222 if (defined($main::foreign_installed_cache{$mod,$configured})) {
3224 return $main::foreign_installed_cache{$mod,$configured};
3228 if (!&foreign_check($mod)) {
3233 my $mdir = &module_root_directory($mod);
3234 if (!-r "$mdir/install_check.pl") {
3235 # Not known, assume OK
3236 $rv = $configured ? 2 : 1;
3239 # Call function to check
3240 &foreign_require($mod, "install_check.pl");
3241 $rv = &foreign_call($mod, "is_installed", $configured);
3244 $main::foreign_installed_cache{$mod,$configured} = $rv;
3249 =head2 foreign_defined(module, function)
3251 Returns 1 if some function is defined in another module. In general, it is
3252 simpler to use the syntax &defined(module::function) instead.
3258 $pkg =~ s/[^A-Za-z0-9]/_/g;
3259 my $func = "${pkg}::$_[1]";
3260 return defined(&$func);
3263 =head2 get_system_hostname([short])
3265 Returns the hostname of this system. If the short parameter is set to 1,
3266 then the domain name is not prepended - otherwise, Webmin will attempt to get
3267 the fully qualified hostname, like foo.example.com.
3270 sub get_system_hostname
3273 if (!$main::get_system_hostname[$m]) {
3274 if ($gconfig{'os_type'} ne 'windows') {
3275 # Try some common Linux hostname files first
3277 if ($gconfig{'os_type'} eq 'redhat-linux') {
3279 &read_env_file("/etc/sysconfig/network", \%nc);
3280 if ($nc{'HOSTNAME'}) {
3281 $fromfile = $nc{'HOSTNAME'};
3284 elsif ($gconfig{'os_type'} eq 'debian-linux') {
3285 my $hn = &read_file_contents("/etc/hostname");
3291 elsif ($gconfig{'os_type'} eq 'open-linux') {
3292 my $hn = &read_file_contents("/etc/HOSTNAME");
3298 elsif ($gconfig{'os_type'} eq 'solaris') {
3299 my $hn = &read_file_contents("/etc/nodename");
3306 # If we found a hostname, use it if value
3307 if ($fromfile && ($m || $fromfile =~ /\./)) {
3309 $fromfile =~ s/\..*$//;
3311 $main::get_system_hostname[$m] = $fromfile;
3315 # Can use hostname command on Unix
3316 &execute_command("hostname", undef,
3317 \$main::get_system_hostname[$m], undef, 0, 1);
3318 chop($main::get_system_hostname[$m]);
3320 eval "use Sys::Hostname";
3322 $main::get_system_hostname[$m] = eval "hostname()";
3324 if ($@ || !$main::get_system_hostname[$m]) {
3325 $main::get_system_hostname[$m] = "UNKNOWN";
3328 elsif ($main::get_system_hostname[$m] !~ /\./ &&
3329 $gconfig{'os_type'} =~ /linux$/ &&
3330 !$gconfig{'no_hostname_f'} && !$_[0]) {
3331 # Try with -f flag to get fully qualified name
3333 my $ex = &execute_command("hostname -f", undef, \$flag,
3336 if ($ex || $flag eq "") {
3337 # -f not supported! We have probably set the
3338 # hostname to just '-f'. Fix the problem
3341 &execute_command("hostname ".
3342 quotemeta($main::get_system_hostname[$m]),
3343 undef, undef, undef, 0, 1);
3347 $main::get_system_hostname[$m] = $flag;
3352 # On Windows, try computername environment variable
3353 return $ENV{'computername'} if ($ENV{'computername'});
3354 return $ENV{'COMPUTERNAME'} if ($ENV{'COMPUTERNAME'});
3356 # Fall back to net name command
3357 my $out = `net name 2>&1`;
3358 if ($out =~ /\-+\r?\n(\S+)/) {
3359 $main::get_system_hostname[$m] = $1;
3362 $main::get_system_hostname[$m] = "windows";
3366 return $main::get_system_hostname[$m];
3369 =head2 get_webmin_version
3371 Returns the version of Webmin currently being run, such as 1.450.
3374 sub get_webmin_version
3376 if (!$get_webmin_version) {
3377 open(VERSION, "$root_directory/version") || return 0;
3378 ($get_webmin_version = <VERSION>) =~ tr/\r|\n//d;
3381 return $get_webmin_version;
3384 =head2 get_module_acl([user], [module], [no-rbac], [no-default])
3386 Returns a hash containing access control options for the given user and module.
3387 By default the current username and module name are used. If the no-rbac flag
3388 is given, the permissions will not be updated based on the user's RBAC role
3389 (as seen on Solaris). If the no-default flag is given, default permissions for
3390 the module will not be included.
3395 my $u = defined($_[0]) ? $_[0] : $base_remote_user;
3396 my $m = defined($_[1]) ? $_[1] : &get_module_name();
3397 my $mdir = &module_root_directory($m);
3400 # Read default ACL first, to be overridden by per-user settings
3401 &read_file_cached("$mdir/defaultacl", \%rv);
3403 # If this isn't a master admin user, apply the negative permissions
3404 # so that he doesn't un-expectedly gain access to new features
3406 &read_file_cached("$config_directory/$u.acl", \%gaccess);
3407 if ($gaccess{'negative'}) {
3408 &read_file_cached("$mdir/negativeacl", \%rv);
3412 if (!$_[2] && &supports_rbac($m) && &use_rbac_module_acl($u, $m)) {
3413 # RBAC overrides exist for this user in this module
3414 my $rbac = &get_rbac_module_acl(
3415 defined($_[0]) ? $_[0] : $remote_user, $m);
3416 foreach my $r (keys %$rbac) {
3417 $rv{$r} = $rbac->{$r};
3420 elsif ($gconfig{"risk_$u"} && $m) {
3421 # ACL is defined by user's risk level
3422 my $rf = $gconfig{"risk_$u"}.'.risk';
3423 &read_file_cached("$mdir/$rf", \%rv);
3425 my $sf = $gconfig{"skill_$u"}.'.skill';
3426 &read_file_cached("$mdir/$sf", \%rv);
3429 # Use normal Webmin ACL, if a user is set
3430 my $userdb = &get_userdb_string();
3432 if ($userdb && ($u ne $base_remote_user || $remote_user_proto)) {
3433 # Look for this user in the user/group DB, if one is defined
3434 # and if the user might be in the DB
3435 my ($dbh, $proto, $prefix, $args) = &connect_userdb($userdb);
3436 ref($dbh) || &error(&text('euserdbacl', $dbh));
3437 if ($proto eq "mysql" || $proto eq "postgresql") {
3438 # Find the user in the SQL DB
3439 my $cmd = $dbh->prepare(
3440 "select id from webmin_user where name = ?");
3441 $cmd && $cmd->execute($u) ||
3442 &error(&text('euserdbacl', $dbh->errstr));
3443 my ($id) = $cmd->fetchrow();
3444 $foundindb = 1 if (defined($id));
3447 # Fetch ACLs with SQL
3449 my $cmd = $dbh->prepare(
3450 "select attr,value from webmin_user_acl ".
3451 "where id = ? and module = ?");
3452 $cmd && $cmd->execute($id, $m) ||
3453 &error(&text('euserdbacl', $dbh->errstr));
3454 while(my ($a, $v) = $cmd->fetchrow()) {
3460 elsif ($proto eq "ldap") {
3462 my $rv = $dbh->search(
3464 filter => '(&(cn='.$u.')(objectClass='.
3465 $args->{'userclass'}.'))',
3467 if (!$rv || $rv->code) {
3468 &error(&text('euserdbacl',
3469 $rv ? $rv->error : "Unknown error"));
3471 my ($user) = $rv->all_entries;
3473 # Find ACL sub-object for the module
3474 my $ldapm = $m || "global";
3476 my $rv = $dbh->search(
3477 base => $user->dn(),
3478 filter => '(cn='.$ldapm.')',
3480 if (!$rv || $rv->code) {
3481 &error(&text('euserdbacl',
3482 $rv ? $rv->error : "Unknown error"));
3484 my ($acl) = $rv->all_entries;
3486 foreach my $av ($acl->get_value(
3487 'webminAclEntry')) {
3488 my ($a, $v) = split(/=/, $av,2);
3494 &disconnect_userdb($userdb, $dbh);
3498 # Read from local files
3499 &read_file_cached("$config_directory/$m/$u.acl", \%rv);
3500 if ($remote_user ne $base_remote_user && !defined($_[0])) {
3502 "$config_directory/$m/$remote_user.acl",\%rv);
3506 if ($tconfig{'preload_functions'}) {
3507 &load_theme_library();
3509 if (defined(&theme_get_module_acl)) {
3510 %rv = &theme_get_module_acl($u, $m, \%rv);
3515 =head2 get_group_module_acl(group, [module], [no-default])
3517 Returns the ACL for a Webmin group, in an optional module (which defaults to
3518 the current module).
3521 sub get_group_module_acl
3524 my $m = defined($_[1]) ? $_[1] : &get_module_name();
3525 my $mdir = &module_root_directory($m);
3528 &read_file_cached("$mdir/defaultacl", \%rv);
3531 my $userdb = &get_userdb_string();
3534 # Look for this group in the user/group DB
3535 my ($dbh, $proto, $prefix, $args) = &connect_userdb($userdb);
3536 ref($dbh) || &error(&text('egroupdbacl', $dbh));
3537 if ($proto eq "mysql" || $proto eq "postgresql") {
3538 # Find the group in the SQL DB
3539 my $cmd = $dbh->prepare(
3540 "select id from webmin_group where name = ?");
3541 $cmd && $cmd->execute($g) ||
3542 &error(&text('egroupdbacl', $dbh->errstr));
3543 my ($id) = $cmd->fetchrow();
3544 $foundindb = 1 if (defined($id));
3547 # Fetch ACLs with SQL
3549 my $cmd = $dbh->prepare(
3550 "select attr,value from webmin_group_acl ".
3551 "where id = ? and module = ?");
3552 $cmd && $cmd->execute($id, $m) ||
3553 &error(&text('egroupdbacl', $dbh->errstr));
3554 while(my ($a, $v) = $cmd->fetchrow()) {
3560 elsif ($proto eq "ldap") {
3561 # Find group in LDAP
3562 my $rv = $dbh->search(
3564 filter => '(&(cn='.$g.')(objectClass='.
3565 $args->{'groupclass'}.'))',
3567 if (!$rv || $rv->code) {
3568 &error(&text('egroupdbacl',
3569 $rv ? $rv->error : "Unknown error"));
3571 my ($group) = $rv->all_entries;
3573 # Find ACL sub-object for the module
3574 my $ldapm = $m || "global";
3576 my $rv = $dbh->search(
3577 base => $group->dn(),
3578 filter => '(cn='.$ldapm.')',
3580 if (!$rv || $rv->code) {
3581 &error(&text('egroupdbacl',
3582 $rv ? $rv->error : "Unknown error"));
3584 my ($acl) = $rv->all_entries;
3586 foreach my $av ($acl->get_value(
3587 'webminAclEntry')) {
3588 my ($a, $v) = split(/=/, $av, 2);
3594 &disconnect_userdb($userdb, $dbh);
3597 # Read from local files
3598 &read_file_cached("$config_directory/$m/$g.gacl", \%rv);
3600 if (defined(&theme_get_module_acl)) {
3601 %rv = &theme_get_module_acl($g, $m, \%rv);
3606 =head2 save_module_acl(&acl, [user], [module], [never-update-group])
3608 Updates the acl hash for some user and module. The parameters are :
3610 =item acl - Hash reference for the new access control options, or undef to clear
3612 =item user - User to update, defaulting to the current user.
3614 =item module - Module to update, defaulting to the caller.
3616 =item never-update-group - Never update the user's group's ACL
3621 my $u = defined($_[1]) ? $_[1] : $base_remote_user;
3622 my $m = defined($_[2]) ? $_[2] : &get_module_name();
3623 if (!$_[3] && &foreign_check("acl")) {
3624 # Check if this user is a member of a group, and if he gets the
3625 # module from a group. If so, update its ACL as well
3626 &foreign_require("acl", "acl-lib.pl");
3628 foreach my $g (&acl::list_groups()) {
3629 if (&indexof($u, @{$g->{'members'}}) >= 0 &&
3630 &indexof($m, @{$g->{'modules'}}) >= 0) {
3636 &save_group_module_acl($_[0], $group->{'name'}, $m);
3640 my $userdb = &get_userdb_string();
3642 if ($userdb && ($u ne $base_remote_user || $remote_user_proto)) {
3643 # Look for this user in the user/group DB
3644 my ($dbh, $proto, $prefix, $args) = &connect_userdb($userdb);
3645 ref($dbh) || &error(&text('euserdbacl', $dbh));
3646 if ($proto eq "mysql" || $proto eq "postgresql") {
3647 # Find the user in the SQL DB
3648 my $cmd = $dbh->prepare(
3649 "select id from webmin_user where name = ?");
3650 $cmd && $cmd->execute($u) ||
3651 &error(&text('euserdbacl2', $dbh->errstr));
3652 my ($id) = $cmd->fetchrow();
3653 $foundindb = 1 if (defined($id));
3656 # Replace ACLs for user
3658 my $cmd = $dbh->prepare("delete from webmin_user_acl ".
3659 "where id = ? and module = ?");
3660 $cmd && $cmd->execute($id, $m) ||
3661 &error(&text('euserdbacl', $dbh->errstr));
3664 my $cmd = $dbh->prepare(
3665 "insert into webmin_user_acl ".
3666 "(id,module,attr,value) values (?,?,?,?)");
3667 $cmd || &error(&text('euserdbacl2',
3669 foreach my $a (keys %{$_[0]}) {
3670 $cmd->execute($id,$m,$a,$_[0]->{$a}) ||
3671 &error(&text('euserdbacl2',
3678 elsif ($proto eq "ldap") {
3679 # Find the user in LDAP
3680 my $rv = $dbh->search(
3682 filter => '(&(cn='.$u.')(objectClass='.
3683 $args->{'userclass'}.'))',
3685 if (!$rv || $rv->code) {
3686 &error(&text('euserdbacl',
3687 $rv ? $rv->error : "Unknown error"));
3689 my ($user) = $rv->all_entries;
3692 # Find the ACL sub-object for the module
3694 my $ldapm = $m || "global";
3695 my $rv = $dbh->search(
3696 base => $user->dn(),
3697 filter => '(cn='.$ldapm.')',
3699 if (!$rv || $rv->code) {
3700 &error(&text('euserdbacl',
3701 $rv ? $rv->error : "Unknown error"));
3703 my ($acl) = $rv->all_entries;
3706 foreach my $a (keys %{$_[0]}) {
3707 push(@al, $a."=".$_[0]->{$a});
3711 $rv = $dbh->modify($acl->dn(),
3712 replace => { "webminAclEntry", \@al });
3716 my @attrs = ( "cn", $ldapm,
3717 "objectClass", "webminAcl",
3718 "webminAclEntry", \@al );
3719 $rv = $dbh->add("cn=".$ldapm.",".$user->dn(),
3722 if (!$rv || $rv->code) {
3723 &error(&text('euserdbacl2',
3724 $rv ? $rv->error : "Unknown error"));
3728 &disconnect_userdb($userdb, $dbh);
3732 # Save ACL to local file
3733 if (!-d "$config_directory/$m") {
3734 mkdir("$config_directory/$m", 0755);
3737 &write_file("$config_directory/$m/$u.acl", $_[0]);
3740 &unlink_file("$config_directory/$m/$u.acl");
3745 =head2 save_group_module_acl(&acl, group, [module], [never-update-group])
3747 Updates the acl hash for some group and module. The parameters are :
3749 =item acl - Hash reference for the new access control options.
3751 =item group - Group name to update.
3753 =item module - Module to update, defaulting to the caller.
3755 =item never-update-group - Never update the parent group's ACL
3758 sub save_group_module_acl
3761 my $m = defined($_[2]) ? $_[2] : &get_module_name();
3762 if (!$_[3] && &foreign_check("acl")) {
3763 # Check if this group is a member of a group, and if it gets the
3764 # module from a group. If so, update the parent ACL as well
3765 &foreign_require("acl", "acl-lib.pl");
3767 foreach my $pg (&acl::list_groups()) {
3768 if (&indexof('@'.$g, @{$pg->{'members'}}) >= 0 &&
3769 &indexof($m, @{$pg->{'modules'}}) >= 0) {
3775 &save_group_module_acl($_[0], $group->{'name'}, $m);
3779 my $userdb = &get_userdb_string();
3782 # Look for this group in the user/group DB
3783 my ($dbh, $proto, $prefix, $args) = &connect_userdb($userdb);
3784 ref($dbh) || &error(&text('egroupdbacl', $dbh));
3785 if ($proto eq "mysql" || $proto eq "postgresql") {
3786 # Find the group in the SQL DB
3787 my $cmd = $dbh->prepare(
3788 "select id from webmin_group where name = ?");
3789 $cmd && $cmd->execute($g) ||
3790 &error(&text('egroupdbacl2', $dbh->errstr));
3791 my ($id) = $cmd->fetchrow();
3792 $foundindb = 1 if (defined($id));
3795 # Replace ACLs for group
3797 my $cmd = $dbh->prepare("delete from webmin_group_acl ".
3798 "where id = ? and module = ?");
3799 $cmd && $cmd->execute($id, $m) ||
3800 &error(&text('egroupdbacl', $dbh->errstr));
3803 my $cmd = $dbh->prepare(
3804 "insert into webmin_group_acl ".
3805 "(id,module,attr,value) values (?,?,?,?)");
3806 $cmd || &error(&text('egroupdbacl2',
3808 foreach my $a (keys %{$_[0]}) {
3809 $cmd->execute($id,$m,$a,$_[0]->{$a}) ||
3810 &error(&text('egroupdbacl2',
3817 elsif ($proto eq "ldap") {
3818 # Find the group in LDAP
3819 my $rv = $dbh->search(
3821 filter => '(&(cn='.$g.')(objectClass='.
3822 $args->{'groupclass'}.'))',
3824 if (!$rv || $rv->code) {
3825 &error(&text('egroupdbacl',
3826 $rv ? $rv->error : "Unknown error"));
3828 my ($group) = $rv->all_entries;
3830 my $ldapm = $m || "global";
3832 # Find the ACL sub-object for the module
3834 my $rv = $dbh->search(
3835 base => $group->dn(),
3836 filter => '(cn='.$ldapm.')',
3838 if (!$rv || $rv->code) {
3839 &error(&text('egroupdbacl',
3840 $rv ? $rv->error : "Unknown error"));
3842 my ($acl) = $rv->all_entries;
3845 foreach my $a (keys %{$_[0]}) {
3846 push(@al, $a."=".$_[0]->{$a});
3850 $rv = $dbh->modify($acl->dn(),
3851 replace => { "webminAclEntry", \@al });
3855 my @attrs = ( "cn", $ldapm,
3856 "objectClass", "webminAcl",
3857 "webminAclEntry", \@al );
3858 $rv = $dbh->add("cn=".$ldapm.",".$group->dn(),
3861 if (!$rv || $rv->code) {
3862 &error(&text('egroupdbacl2',
3863 $rv ? $rv->error : "Unknown error"));
3867 &disconnect_userdb($userdb, $dbh);
3871 # Save ACL to local file
3872 if (!-d "$config_directory/$m") {
3873 mkdir("$config_directory/$m", 0755);
3876 &write_file("$config_directory/$m/$g.gacl", $_[0]);
3879 &unlink_file("$config_directory/$m/$g.gacl");
3886 This function must be called by all Webmin CGI scripts, either directly or
3887 indirectly via a per-module lib.pl file. It performs a number of initialization
3888 and housekeeping tasks, such as working out the module name, checking that the
3889 current user has access to the module, and populating global variables. Some
3890 of the variables set include :
3892 =item $config_directory - Base Webmin config directory, typically /etc/webmin
3894 =item $var_directory - Base logs directory, typically /var/webmin
3896 =item %config - Per-module configuration.
3898 =item %gconfig - Global configuration.
3900 =item $scriptname - Base name of the current perl script.
3902 =item $module_name - The name of the current module.
3904 =item $module_config_directory - The config directory for this module.
3906 =item $module_config_file - The config file for this module.
3908 =item $module_root_directory - This module's code directory.
3910 =item $webmin_logfile - The detailed logfile for webmin.
3912 =item $remote_user - The actual username used to login to webmin.
3914 =item $base_remote_user - The username whose permissions are in effect.
3916 =item $current_theme - The theme currently in use.
3918 =item $root_directory - The first root directory of this webmin install.
3920 =item @root_directories - All root directories for this webmin install.
3925 # Record first process ID that called this, so we know when it exited to clean
3927 $main::initial_process_id ||= $$;
3929 # Configuration and spool directories
3930 if (!defined($ENV{'WEBMIN_CONFIG'})) {
3931 die "WEBMIN_CONFIG not set";
3933 $config_directory = $ENV{'WEBMIN_CONFIG'};
3934 if (!defined($ENV{'WEBMIN_VAR'})) {
3935 open(VARPATH, "$config_directory/var-path");
3936 chop($var_directory = <VARPATH>);
3940 $var_directory = $ENV{'WEBMIN_VAR'};
3942 $main::http_cache_directory = $ENV{'WEBMIN_VAR'}."/cache";
3943 $main::default_debug_log_file = $ENV{'WEBMIN_VAR'}."/webmin.debug";
3945 if ($ENV{'SESSION_ID'}) {
3946 # Hide this variable from called programs, but keep it for internal use
3947 $main::session_id = $ENV{'SESSION_ID'};
3948 delete($ENV{'SESSION_ID'});
3950 if ($ENV{'REMOTE_PASS'}) {
3951 # Hide the password too
3952 $main::remote_pass = $ENV{'REMOTE_PASS'};
3953 delete($ENV{'REMOTE_PASS'});
3956 if ($> == 0 && $< != 0 && !$ENV{'FOREIGN_MODULE_NAME'}) {
3957 # Looks like we are running setuid, but the real UID hasn't been set.
3958 # Do so now, so that executed programs don't get confused
3963 # Read the webmin global config file. This contains the OS type and version,
3964 # OS specific configuration and global options such as proxy servers
3965 $config_file = "$config_directory/config";
3967 &read_file_cached($config_file, \%gconfig);
3968 $null_file = $gconfig{'os_type'} eq 'windows' ? "NUL" : "/dev/null";
3969 $path_separator = $gconfig{'os_type'} eq 'windows' ? ';' : ':';
3971 # If debugging is enabled, open the debug log
3972 if ($gconfig{'debug_enabled'} && !$main::opened_debug_log++) {
3973 my $dlog = $gconfig{'debug_file'} || $main::default_debug_log_file;
3974 if ($gconfig{'debug_size'}) {
3975 my @st = stat($dlog);
3976 if ($st[7] > $gconfig{'debug_size'}) {
3977 rename($dlog, $dlog.".0");
3980 open(main::DEBUGLOG, ">>$dlog");
3981 $main::opened_debug_log = 1;
3983 if ($gconfig{'debug_what_start'}) {
3984 my $script_name = $0 =~ /([^\/]+)$/ ? $1 : '-';
3985 $main::debug_log_start_time = time();
3986 &webmin_debug_log("START", "script=$script_name");
3987 $main::debug_log_start_module = $module_name;
3991 # Set PATH and LD_LIBRARY_PATH
3992 if ($gconfig{'path'}) {
3993 if ($gconfig{'syspath'}) {
3995 $ENV{'PATH'} = $gconfig{'path'};
3999 $ENV{'PATH'} = $gconfig{'path'}.$path_separator.$ENV{'PATH'};
4002 $ENV{$gconfig{'ld_env'}} = $gconfig{'ld_path'} if ($gconfig{'ld_env'});
4004 # Set http_proxy and ftp_proxy environment variables, based on Webmin settings
4005 if ($gconfig{'http_proxy'}) {
4006 $ENV{'http_proxy'} = $gconfig{'http_proxy'};
4008 if ($gconfig{'ftp_proxy'}) {
4009 $ENV{'ftp_proxy'} = $gconfig{'ftp_proxy'};
4011 if ($gconfig{'noproxy'}) {
4012 $ENV{'no_proxy'} = $gconfig{'noproxy'};
4015 # Find all root directories
4017 if (&get_miniserv_config(\%miniserv)) {
4018 @root_directories = ( $miniserv{'root'} );
4019 for($i=0; defined($miniserv{"extraroot_$i"}); $i++) {
4020 push(@root_directories, $miniserv{"extraroot_$i"});
4024 # Work out which module we are in, and read the per-module config file
4025 $0 =~ s/\\/\//g; # Force consistent path on Windows
4026 if (defined($ENV{'FOREIGN_MODULE_NAME'})) {
4027 # In a foreign call - use the module name given
4028 $root_directory = $ENV{'FOREIGN_ROOT_DIRECTORY'};
4029 $module_name = $ENV{'FOREIGN_MODULE_NAME'};
4030 @root_directories = ( $root_directory ) if (!@root_directories);
4032 elsif ($ENV{'SCRIPT_NAME'}) {
4033 my $sn = $ENV{'SCRIPT_NAME'};
4034 $sn =~ s/^$gconfig{'webprefix'}//
4035 if (!$gconfig{'webprefixnoredir'});
4036 if ($sn =~ /^\/([^\/]+)\//) {
4037 # Get module name from CGI path
4040 if ($ENV{'SERVER_ROOT'}) {
4041 $root_directory = $ENV{'SERVER_ROOT'};
4043 elsif ($ENV{'SCRIPT_FILENAME'}) {
4044 $root_directory = $ENV{'SCRIPT_FILENAME'};
4045 $root_directory =~ s/$sn$//;
4047 @root_directories = ( $root_directory ) if (!@root_directories);
4050 # Get root directory from miniserv.conf, and deduce module name from $0
4051 $root_directory = $root_directories[0];
4053 foreach my $r (@root_directories) {
4054 if ($0 =~ /^$r\/([^\/]+)\/[^\/]+$/i) {
4055 # Under a module directory
4060 elsif ($0 =~ /^$root_directory\/[^\/]+$/i) {
4066 &error("Script was not run with full path (failed to find $0 under $root_directory)") if (!$rok);
4069 # Work out of this is a web, command line or cron job
4070 if (!$main::webmin_script_type) {
4071 if ($ENV{'SCRIPT_NAME'}) {
4073 $main::webmin_script_type = 'web';
4076 # Cron jobs have no TTY
4077 if ($gconfig{'os_type'} eq 'windows' ||
4078 open(DEVTTY, ">/dev/tty")) {
4079 $main::webmin_script_type = 'cmd';
4083 $main::webmin_script_type = 'cron';
4088 # Set the umask based on config
4089 if ($gconfig{'umask'} && !$main::umask_already++) {
4090 umask(oct($gconfig{'umask'}));
4093 # If this is a cron job or other background task, set the nice level
4094 if (!$main::nice_already && $main::webmin_script_type eq 'cron') {
4096 if ($gconfig{'nice'}) {
4097 eval 'POSIX::nice($gconfig{\'nice\'});';
4100 # Set IO scheduling class and priority
4101 if ($gconfig{'sclass'} ne '' || $gconfig{'sprio'} ne '') {
4103 $cmd .= " -c ".quotemeta($gconfig{'sclass'})
4104 if ($gconfig{'sclass'} ne '');
4105 $cmd .= " -n ".quotemeta($gconfig{'sprio'})
4106 if ($gconfig{'sprio'} ne '');
4108 &execute_command("$cmd >/dev/null 2>&1");
4111 $main::nice_already++;
4114 my $u = $ENV{'BASE_REMOTE_USER'} || $ENV{'REMOTE_USER'};
4115 $base_remote_user = $u;
4116 $remote_user = $ENV{'REMOTE_USER'};
4118 # Work out if user is definitely in the DB, and if so get his attrs
4119 $remote_user_proto = $ENV{"REMOTE_USER_PROTO"};
4120 %remote_user_attrs = ( );
4121 if ($remote_user_proto) {
4122 my $userdb = &get_userdb_string();
4123 my ($dbh, $proto, $prefix, $args) =
4124 $userdb ? &connect_userdb($userdb) : ( );
4126 if ($proto eq "mysql" || $proto eq "postgresql") {
4127 # Read attrs from SQL
4128 my $cmd = $dbh->prepare("select webmin_user_attr.attr,webmin_user_attr.value from webmin_user_attr,webmin_user where webmin_user_attr.id = webmin_user.id and webmin_user.name = ?");
4129 if ($cmd && $cmd->execute($base_remote_user)) {
4130 while(my ($attr, $value) = $cmd->fetchrow()) {
4131 $remote_user_attrs{$attr} = $value;
4136 elsif ($proto eq "ldap") {
4137 # Read attrs from LDAP
4138 my $rv = $dbh->search(
4140 filter => '(&(cn='.$base_remote_user.')'.
4142 $args->{'userclass'}.'))',
4144 my ($u) = $rv && !$rv->code ? $rv->all_entries : ( );
4146 foreach $la ($u->get_value('webminAttr')) {
4147 my ($attr, $value) = split(/=/, $la, 2);
4148 $remote_user_attrs{$attr} = $value;
4152 &disconnect_userdb($userdb, $dbh);
4157 # Find and load the configuration file for this module
4158 my (@ruinfo, $rgroup);
4159 $module_config_directory = "$config_directory/$module_name";
4160 if (&get_product_name() eq "usermin" &&
4161 -r "$module_config_directory/config.$remote_user") {
4163 $module_config_file = "$module_config_directory/config.$remote_user";
4165 elsif (&get_product_name() eq "usermin" &&
4166 (@ruinfo = getpwnam($remote_user)) &&
4167 ($rgroup = getgrgid($ruinfo[3])) &&
4168 -r "$module_config_directory/config.\@$rgroup") {
4169 # Based on group name
4170 $module_config_file = "$module_config_directory/config.\@$rgroup";
4174 $module_config_file = "$module_config_directory/config";
4177 &read_file_cached($module_config_file, \%config);
4179 # Fix up windows-specific substitutions in values
4180 foreach my $k (keys %config) {
4181 if ($config{$k} =~ /\$\{systemroot\}/) {
4182 my $root = &get_windows_root();
4183 $config{$k} =~ s/\$\{systemroot\}/$root/g;
4188 # Record the initial module
4189 $main::initial_module_name ||= $module_name;
4191 # Set some useful variables
4193 $current_themes = $ENV{'MOBILE_DEVICE'} && defined($gconfig{'mobile_theme'}) ?
4194 $gconfig{'mobile_theme'} :
4195 defined($remote_user_attrs{'theme'}) ?
4196 $remote_user_attrs{'theme'} :
4197 defined($gconfig{'theme_'.$remote_user}) ?
4198 $gconfig{'theme_'.$remote_user} :
4199 defined($gconfig{'theme_'.$base_remote_user}) ?
4200 $gconfig{'theme_'.$base_remote_user} :
4202 @current_themes = split(/\s+/, $current_themes);
4203 $current_theme = $current_themes[0];
4204 @theme_root_directories = map { "$root_directory/$_" } @current_themes;
4205 $theme_root_directory = $theme_root_directories[0];
4206 @theme_configs = ( );
4207 foreach my $troot (@theme_root_directories) {
4209 &read_file_cached("$troot/config", \%onetconfig);
4210 &read_file_cached("$troot/config", \%tconfig);
4211 push(@theme_configs, \%onetconfig);
4213 $tb = defined($tconfig{'cs_header'}) ? "bgcolor=#$tconfig{'cs_header'}" :
4214 defined($gconfig{'cs_header'}) ? "bgcolor=#$gconfig{'cs_header'}" :
4216 $cb = defined($tconfig{'cs_table'}) ? "bgcolor=#$tconfig{'cs_table'}" :
4217 defined($gconfig{'cs_table'}) ? "bgcolor=#$gconfig{'cs_table'}" :
4219 $tb .= ' '.$tconfig{'tb'} if ($tconfig{'tb'});
4220 $cb .= ' '.$tconfig{'cb'} if ($tconfig{'cb'});
4221 if ($tconfig{'preload_functions'}) {
4222 # Force load of theme functions right now, if requested
4223 &load_theme_library();
4225 if ($tconfig{'oofunctions'} && !$main::loaded_theme_oo_library++) {
4226 # Load the theme's Webmin:: package classes
4227 do "$theme_root_directory/$tconfig{'oofunctions'}";
4232 $webmin_logfile = $gconfig{'webmin_log'} ? $gconfig{'webmin_log'}
4233 : "$var_directory/webmin.log";
4235 # Load language strings into %text
4236 my @langs = &list_languages();
4238 if ($gconfig{'acceptlang'}) {
4239 foreach my $a (split(/,/, $ENV{'HTTP_ACCEPT_LANGUAGE'})) {
4240 my ($al) = grep { $_->{'lang'} eq $a } @langs;
4242 $accepted_lang = $al->{'lang'};
4247 $current_lang = $force_lang ? $force_lang :
4248 $accepted_lang ? $accepted_lang :
4249 $remote_user_attrs{'lang'} ? $remote_user_attrs{'lang'} :
4250 $gconfig{"lang_$remote_user"} ? $gconfig{"lang_$remote_user"} :
4251 $gconfig{"lang_$base_remote_user"} ? $gconfig{"lang_$base_remote_user"} :
4252 $gconfig{"lang"} ? $gconfig{"lang"} : $default_lang;
4253 foreach my $l (@langs) {
4254 $current_lang_info = $l if ($l->{'lang'} eq $current_lang);
4256 @lang_order_list = &unique($default_lang,
4257 split(/:/, $current_lang_info->{'fallback'}),
4259 %text = &load_language($module_name);
4260 %text || &error("Failed to determine Webmin root from SERVER_ROOT, SCRIPT_FILENAME or the full command line");
4262 # Get the %module_info for this module
4264 my ($mi) = grep { $_->{'dir'} eq $module_name }
4265 &get_all_module_infos(2);
4266 %module_info = %$mi;
4267 $module_root_directory = &module_root_directory($module_name);
4270 if ($module_name && !$main::no_acl_check &&
4271 !defined($ENV{'FOREIGN_MODULE_NAME'})) {
4272 # Check if the HTTP user can access this module
4273 if (!&foreign_available($module_name)) {
4274 if (!&foreign_check($module_name)) {
4275 &error(&text('emodulecheck',
4276 "<i>$module_info{'desc'}</i>"));
4279 &error(&text('emodule', "<i>$u</i>",
4280 "<i>$module_info{'desc'}</i>"));
4283 $main::no_acl_check++;
4286 # Check the Referer: header for nasty redirects
4287 my @referers = split(/\s+/, $gconfig{'referers'});
4289 if ($ENV{'HTTP_REFERER'} =~/^(http|https|ftp):\/\/([^:\/]+:[^@\/]+@)?([^\/:@]+)/) {
4292 my $http_host = $ENV{'HTTP_HOST'};
4293 $http_host =~ s/:\d+$//;
4294 my $unsafe_index = $unsafe_index_cgi ||
4295 &get_module_variable('$unsafe_index_cgi');
4297 ($ENV{'SCRIPT_NAME'} !~ /^\/(index.cgi)?$/ || $unsafe_index) &&
4298 ($ENV{'SCRIPT_NAME'} !~ /^\/([a-z0-9\_\-]+)\/(index.cgi)?$/i ||
4300 $0 !~ /(session_login|pam_login)\.cgi$/ && !$gconfig{'referer'} &&
4301 $ENV{'MINISERV_CONFIG'} && !$main::no_referers_check &&
4302 $ENV{'HTTP_USER_AGENT'} !~ /^Webmin/i &&
4303 ($referer_site && $referer_site ne $http_host &&
4304 &indexof($referer_site, @referers) < 0 ||
4305 !$referer_site && $gconfig{'referers_none'}) &&
4306 !$trust_unknown_referers &&
4307 !&get_module_variable('$trust_unknown_referers')) {
4308 # Looks like a link from elsewhere .. show an error
4309 &header($text{'referer_title'}, "", undef, 0, 1, 1);
4311 $prot = lc($ENV{'HTTPS'}) eq 'on' ? "https" : "http";
4312 my $url = "<tt>".&html_escape("$prot://$ENV{'HTTP_HOST'}$ENV{'REQUEST_URI'}")."</tt>";
4313 if ($referer_site) {
4315 print &text('referer_warn',
4316 "<tt>".&html_escape($ENV{'HTTP_REFERER'})."</tt>", $url);
4318 print &text('referer_fix1', &html_escape($http_host)),"<p>\n";
4319 print &text('referer_fix2', &html_escape($http_host)),"<p>\n";
4322 # No referer info given
4323 print &text('referer_warn_unknown', $url),"<p>\n";
4324 print &text('referer_fix1u'),"<p>\n";
4325 print &text('referer_fix2u'),"<p>\n";
4329 &footer("/", $text{'index'});
4332 $main::no_referers_check++;
4333 $main::completed_referers_check++;
4335 # Call theme post-init
4336 if (defined(&theme_post_init_config)) {
4337 &theme_post_init_config(@_);
4340 # Record that we have done the calling library in this package
4341 my ($callpkg, $lib) = caller();
4343 $main::done_foreign_require{$callpkg,$lib} = 1;
4345 # If a licence checking is enabled, do it now
4346 if ($gconfig{'licence_module'} && !$main::done_licence_module_check &&
4347 &foreign_check($gconfig{'licence_module'}) &&
4348 -r "$root_directory/$gconfig{'licence_module'}/licence_check.pl") {
4349 my $oldpwd = &get_current_dir();
4350 $main::done_licence_module_check++;
4351 $main::licence_module = $gconfig{'licence_module'};
4352 &foreign_require($main::licence_module, "licence_check.pl");
4353 ($main::licence_status, $main::licence_message) =
4354 &foreign_call($main::licence_module, "check_licence");
4358 # Export global variables to caller
4359 if ($main::export_to_caller) {
4360 foreach my $v ('$config_file', '%gconfig', '$null_file',
4361 '$path_separator', '@root_directories',
4362 '$root_directory', '$module_name',
4363 '$base_remote_user', '$remote_user',
4364 '$remote_user_proto', '%remote_user_attrs',
4365 '$module_config_directory', '$module_config_file',
4366 '%config', '@current_themes', '$current_theme',
4367 '@theme_root_directories', '$theme_root_directory',
4368 '%tconfig','@theme_configs', '$tb', '$cb', '$scriptname',
4369 '$webmin_logfile', '$current_lang',
4370 '$current_lang_info', '@lang_order_list', '%text',
4371 '%module_info', '$module_root_directory') {
4372 my ($vt, $vn) = split('', $v, 2);
4373 eval "${vt}${callpkg}::${vn} = ${vt}${vn}";
4380 =head2 load_language([module], [directory])
4382 Returns a hashtable mapping text codes to strings in the appropriate language,
4383 based on the $current_lang global variable, which is in turn set based on
4384 the Webmin user's selection. The optional module parameter tells the function
4385 which module to load strings for, and defaults to the calling module. The
4386 optional directory parameter can be used to load strings from a directory
4389 In regular module development you will never need to call this function
4390 directly, as init_config calls it for you, and places the module's strings
4391 into the %text hash.
4397 my $root = $root_directory;
4398 my $ol = $gconfig{'overlang'};
4399 my ($dir) = ($_[1] || "lang");
4401 # Read global lang files
4402 foreach my $o (@lang_order_list) {
4403 my $ok = &read_file_cached("$root/$dir/$o", \%text);
4404 return () if (!$ok && $o eq $default_lang);
4407 foreach my $o (@lang_order_list) {
4408 &read_file_cached("$root/$ol/$o", \%text);
4411 &read_file_cached("$config_directory/custom-lang", \%text);
4414 # Read module's lang files
4415 my $mdir = &module_root_directory($_[0]);
4416 foreach my $o (@lang_order_list) {
4417 &read_file_cached("$mdir/$dir/$o", \%text);
4420 foreach $o (@lang_order_list) {
4421 &read_file_cached("$mdir/$ol/$o", \%text);
4424 &read_file_cached("$config_directory/$_[0]/custom-lang", \%text);
4426 foreach $k (keys %text) {
4427 $text{$k} =~ s/\$(\{([^\}]+)\}|([A-Za-z0-9\.\-\_]+))/text_subs($2 || $3,\%text)/ge;
4430 if (defined(&theme_load_language)) {
4431 &theme_load_language(\%text, $_[0]);
4436 =head2 text_subs(string)
4438 Used internally by load_language to expand $code substitutions in language
4444 if (substr($_[0], 0, 8) eq "include:") {
4447 open(INCLUDE, substr($_[0], 8));
4455 my $t = $_[1]->{$_[0]};
4456 return defined($t) ? $t : '$'.$_[0];
4460 =head2 text(message, [substitute]+)
4462 Returns a translated message from %text, but with $1, $2, etc.. replaced with
4463 the substitute parameters. This makes it easy to use strings with placeholders
4464 that get replaced with programmatically generated text. For example :
4466 print &text('index_hello', $remote_user),"<p>\n";
4471 my $t = &get_module_variable('%text', 1);
4472 my $rv = exists($t->{$_[0]}) ? $t->{$_[0]} : $text{$_[0]};
4473 for(my $i=1; $i<@_; $i++) {
4474 $rv =~ s/\$$i/$_[$i]/g;
4479 =head2 encode_base64(string)
4481 Encodes a string into base64 format, for use in MIME email or HTTP
4482 authorization headers.
4488 pos($_[0]) = 0; # ensure start at the beginning
4489 while ($_[0] =~ /(.{1,57})/gs) {
4490 $res .= substr(pack('u57', $1), 1)."\n";
4493 $res =~ tr|\` -_|AA-Za-z0-9+/|;
4494 my $padding = (3 - length($_[0]) % 3) % 3;
4495 $res =~ s/.{$padding}$/'=' x $padding/e if ($padding);
4499 =head2 decode_base64(string)
4501 Converts a base64-encoded string into plain text. The opposite of encode_base64.
4508 $str =~ tr|A-Za-z0-9+=/||cd; # remove non-base64 chars
4509 if (length($str) % 4) {
4512 $str =~ s/=+$//; # remove padding
4513 $str =~ tr|A-Za-z0-9+/| -_|; # convert to uuencoded format
4514 while ($str =~ /(.{1,60})/gs) {
4515 my $len = chr(32 + length($1)*3/4); # compute length byte
4516 $res .= unpack("u", $len . $1 ); # uudecode
4521 =head2 get_module_info(module, [noclone], [forcache])
4523 Returns a hash containg details of the given module. Some useful keys are :
4525 =item dir - The module directory, like sendmail.
4527 =item desc - Human-readable description, in the current users' language.
4529 =item version - Optional module version number.
4531 =item os_support - List of supported operating systems and versions.
4533 =item category - Category on Webmin's left menu, like net.
4538 return () if ($_[0] =~ /^\./);
4539 my (%rv, $clone, $o);
4540 my $mdir = &module_root_directory($_[0]);
4541 &read_file_cached("$mdir/module.info", \%rv) || return ();
4543 # A clone is a module that links to another directory under the root
4544 foreach my $r (@root_directories) {
4545 if (&is_under_directory($r, $mdir)) {
4551 foreach $o (@lang_order_list) {
4552 $rv{"desc"} = $rv{"desc_$o"} if ($rv{"desc_$o"});
4553 $rv{"longdesc"} = $rv{"longdesc_$o"} if ($rv{"longdesc_$o"});
4555 if ($clone && !$_[1] && $config_directory) {
4556 $rv{'clone'} = $rv{'desc'};
4557 &read_file("$config_directory/$_[0]/clone", \%rv);
4560 my %module_categories;
4561 &read_file_cached("$config_directory/webmin.cats", \%module_categories);
4562 my $pn = &get_product_name();
4563 if (defined($rv{'category_'.$pn})) {
4564 # Can override category for webmin/usermin
4565 $rv{'category'} = $rv{'category_'.$pn};
4567 $rv{'realcategory'} = $rv{'category'};
4568 $rv{'category'} = $module_categories{$_[0]}
4569 if (defined($module_categories{$_[0]}));
4571 # Apply description overrides
4572 $rv{'realdesc'} = $rv{'desc'};
4574 &read_file_cached("$config_directory/webmin.descs", \%descs);
4575 if ($descs{$_[0]." ".$current_lang}) {
4576 $rv{'desc'} = $descs{$_[0]." ".$current_lang};
4578 elsif ($descs{$_[0]}) {
4579 $rv{'desc'} = $descs{$_[0]};
4583 # Apply per-user description overridde
4584 my %gaccess = &get_module_acl(undef, "");
4585 if ($gaccess{'desc_'.$_[0]}) {
4586 $rv{'desc'} = $gaccess{'desc_'.$_[0]};
4590 if ($rv{'longdesc'}) {
4591 # All standard modules have an index.cgi
4592 $rv{'index_link'} = 'index.cgi';
4595 # Call theme-specific override function
4596 if (defined(&theme_get_module_info)) {
4597 %rv = &theme_get_module_info(\%rv, $_[0], $_[1], $_[2]);
4603 =head2 get_all_module_infos(cachemode)
4605 Returns a list contains the information on all modules in this webmin
4606 install, including clones. Uses caching to reduce the number of module.info
4607 files that need to be read. Each element of the array is a hash reference
4608 in the same format as returned by get_module_info. The cache mode flag can be :
4609 0 = read and write, 1 = don't read or write, 2 = read only
4612 sub get_all_module_infos
4616 # Is the cache out of date? (ie. have any of the root's changed?)
4617 my $cache_file = "$config_directory/module.infos.cache";
4619 if (&read_file_cached($cache_file, \%cache)) {
4620 foreach my $r (@root_directories) {
4622 if ($st[9] != $cache{'mtime_'.$r}) {
4632 if ($_[0] != 1 && !$changed && $cache{'lang'} eq $current_lang) {
4633 # Can use existing module.info cache
4635 foreach my $k (keys %cache) {
4636 if ($k =~ /^(\S+) (\S+)$/) {
4637 $mods{$1}->{$2} = $cache{$k};
4640 @rv = map { $mods{$_} } (keys %mods) if (%mods);
4643 # Need to rebuild cache
4645 foreach my $r (@root_directories) {
4647 foreach my $m (readdir(DIR)) {
4648 next if ($m =~ /^(config-|\.)/ || $m =~ /\.(cgi|pl)$/);
4649 my %minfo = &get_module_info($m, 0, 1);
4650 next if (!%minfo || !$minfo{'dir'});
4652 foreach $k (keys %minfo) {
4653 $cache{"${m} ${k}"} = $minfo{$k};
4658 $cache{'mtime_'.$r} = $st[9];
4660 $cache{'lang'} = $current_lang;
4661 &write_file($cache_file, \%cache) if (!$_[0] && $< == 0 && $> == 0);
4664 # Override descriptions for modules for current user
4665 my %gaccess = &get_module_acl(undef, "");
4666 foreach my $m (@rv) {
4667 if ($gaccess{"desc_".$m->{'dir'}}) {
4668 $m->{'desc'} = $gaccess{"desc_".$m->{'dir'}};
4672 # Apply installed flags
4674 &read_file_cached("$config_directory/installed.cache", \%installed);
4675 foreach my $m (@rv) {
4676 $m->{'installed'} = $installed{$m->{'dir'}};
4682 =head2 get_theme_info(theme)
4684 Returns a hash containing a theme's details, taken from it's theme.info file.
4685 Some useful keys are :
4687 =item dir - The theme directory, like blue-theme.
4689 =item desc - Human-readable description, in the current users' language.
4691 =item version - Optional module version number.
4693 =item os_support - List of supported operating systems and versions.
4698 return () if ($_[0] =~ /^\./);
4700 my $tdir = &module_root_directory($_[0]);
4701 &read_file("$tdir/theme.info", \%rv) || return ();
4702 foreach my $o (@lang_order_list) {
4703 $rv{"desc"} = $rv{"desc_$o"} if ($rv{"desc_$o"});
4709 =head2 list_languages
4711 Returns an array of supported languages, taken from Webmin's os_list.txt file.
4712 Each is a hash reference with the following keys :
4714 =item lang - The short language code, like es for Spanish.
4716 =item desc - A human-readable description, in English.
4718 =item charset - An optional character set to use when displaying the language.
4720 =item titles - Set to 1 only if Webmin has title images for the language.
4722 =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.
4727 if (!@main::list_languages_cache) {
4730 open(LANG, "$root_directory/lang_list.txt");
4732 if (/^(\S+)\s+(.*)/) {
4733 my $l = { 'desc' => $2 };
4734 foreach $o (split(/,/, $1)) {
4735 if ($o =~ /^([^=]+)=(.*)$/) {
4739 $l->{'index'} = scalar(@rv);
4740 push(@main::list_languages_cache, $l);
4744 @main::list_languages_cache = sort { $a->{'desc'} cmp $b->{'desc'} }
4745 @main::list_languages_cache;
4747 return @main::list_languages_cache;
4750 =head2 read_env_file(file, &hash)
4752 Similar to Webmin's read_file function, but handles files containing shell
4753 environment variables formatted like :
4758 The file parameter is the full path to the file to read, and hash a Perl hash
4759 ref to read names and values into.
4765 &open_readfile(FILE, $_[0]) || return 0;
4768 if (/^\s*(export\s*)?([A-Za-z0-9_\.]+)\s*=\s*"(.*)"/i ||
4769 /^\s*(export\s*)?([A-Za-z0-9_\.]+)\s*=\s*'(.*)'/i ||
4770 /^\s*(export\s*)?([A-Za-z0-9_\.]+)\s*=\s*(.*)/i) {
4778 =head2 write_env_file(file, &hash, [export])
4780 Writes out a hash to a file in name='value' format, suitable for use in a shell
4781 script. The parameters are :
4783 =item file - Full path for a file to write to
4785 =item hash - Hash reference of names and values to write.
4787 =item export - If set to 1, preceed each variable setting with the word 'export'.
4792 my $exp = $_[2] ? "export " : "";
4793 &open_tempfile(FILE, ">$_[0]");
4794 foreach my $k (keys %{$_[1]}) {
4795 my $v = $_[1]->{$k};
4796 if ($v =~ /^\S+$/) {
4797 &print_tempfile(FILE, "$exp$k=$v\n");
4800 &print_tempfile(FILE, "$exp$k=\"$v\"\n");
4803 &close_tempfile(FILE);
4806 =head2 lock_file(filename, [readonly], [forcefile])
4808 Lock a file for exclusive access. If the file is already locked, spin
4809 until it is freed. Uses a .lock file, which is not 100% reliable, but seems
4810 to work OK. The parameters are :
4812 =item filename - File or directory to lock.
4814 =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.
4816 =item forcefile - Force the file to be considered as a real file and not a symlink for Webmin actions logging purposes.
4821 my $realfile = &translate_filename($_[0]);
4822 return 0 if (!$_[0] || defined($main::locked_file_list{$realfile}));
4823 my $no_lock = !&can_lock_file($realfile);
4824 my $lock_tries_count = 0;
4827 if (!$no_lock && open(LOCKING, "$realfile.lock")) {
4832 if ($no_lock || !$pid || !kill(0, $pid) || $pid == $$) {
4835 # Create the .lock file
4836 open(LOCKING, ">$realfile.lock") || return 0;
4837 my $lck = eval "flock(LOCKING, 2+4)";
4839 # Lock of lock file failed! Wait till later
4842 print LOCKING $$,"\n";
4843 eval "flock(LOCKING, 8)";
4846 $main::locked_file_list{$realfile} = int($_[1]);
4847 push(@main::temporary_files, "$realfile.lock");
4848 if (($gconfig{'logfiles'} || $gconfig{'logfullfiles'}) &&
4849 !&get_module_variable('$no_log_file_changes') &&
4851 # Grab a copy of this file for later diffing
4853 $main::locked_file_data{$realfile} = undef;
4855 $main::locked_file_type{$realfile} = 1;
4856 $main::locked_file_data{$realfile} = '';
4858 elsif (!$_[2] && ($lnk = readlink($realfile))) {
4859 $main::locked_file_type{$realfile} = 2;
4860 $main::locked_file_data{$realfile} = $lnk;
4862 elsif (open(ORIGFILE, $realfile)) {
4863 $main::locked_file_type{$realfile} = 0;
4864 $main::locked_file_data{$realfile} = '';
4867 $main::locked_file_data{$realfile} .=$_;
4876 if ($lock_tries_count++ > 5*60) {
4877 # Give up after 5 minutes
4878 &error(&text('elock_tries', "<tt>$realfile</tt>", 5));
4884 =head2 unlock_file(filename)
4886 Release a lock on a file taken out by lock_file. If Webmin actions logging of
4887 file changes is enabled, then at unlock file a diff will be taken between the
4888 old and new contents, and stored under /var/webmin/diffs when webmin_log is
4889 called. This can then be viewed in the Webmin Actions Log module.
4894 my $realfile = &translate_filename($_[0]);
4895 return if (!$_[0] || !defined($main::locked_file_list{$realfile}));
4896 unlink("$realfile.lock") if (&can_lock_file($realfile));
4897 delete($main::locked_file_list{$realfile});
4898 if (exists($main::locked_file_data{$realfile})) {
4899 # Diff the new file with the old
4901 my $lnk = readlink($realfile);
4902 my $type = -d _ ? 1 : $lnk ? 2 : 0;
4903 my $oldtype = $main::locked_file_type{$realfile};
4904 my $new = !defined($main::locked_file_data{$realfile});
4905 if ($new && !-e _) {
4906 # file doesn't exist, and never did! do nothing ..
4908 elsif ($new && $type == 1 || !$new && $oldtype == 1) {
4909 # is (or was) a directory ..
4910 if (-d _ && !defined($main::locked_file_data{$realfile})) {
4911 push(@main::locked_file_diff,
4912 { 'type' => 'mkdir', 'object' => $realfile });
4914 elsif (!-d _ && defined($main::locked_file_data{$realfile})) {
4915 push(@main::locked_file_diff,
4916 { 'type' => 'rmdir', 'object' => $realfile });
4919 elsif ($new && $type == 2 || !$new && $oldtype == 2) {
4920 # is (or was) a symlink ..
4921 if ($lnk && !defined($main::locked_file_data{$realfile})) {
4922 push(@main::locked_file_diff,
4923 { 'type' => 'symlink', 'object' => $realfile,
4926 elsif (!$lnk && defined($main::locked_file_data{$realfile})) {
4927 push(@main::locked_file_diff,
4928 { 'type' => 'unsymlink', 'object' => $realfile,
4929 'data' => $main::locked_file_data{$realfile} });
4931 elsif ($lnk ne $main::locked_file_data{$realfile}) {
4932 push(@main::locked_file_diff,
4933 { 'type' => 'resymlink', 'object' => $realfile,
4938 # is a file, or has changed type?!
4939 my ($diff, $delete_file);
4940 my $type = "modify";
4942 open(NEWFILE, ">$realfile");
4947 if (!defined($main::locked_file_data{$realfile})) {
4950 open(ORIGFILE, ">$realfile.webminorig");
4951 print ORIGFILE $main::locked_file_data{$realfile};
4953 $diff = &backquote_command(
4954 "diff ".quotemeta("$realfile.webminorig")." ".
4955 quotemeta($realfile)." 2>/dev/null");
4956 push(@main::locked_file_diff,
4957 { 'type' => $type, 'object' => $realfile,
4958 'data' => $diff } ) if ($diff);
4959 unlink("$realfile.webminorig");
4960 unlink($realfile) if ($delete_file);
4963 if ($gconfig{'logfullfiles'}) {
4964 # Add file details to list of those to fully log
4965 $main::orig_file_data{$realfile} ||=
4966 $main::locked_file_data{$realfile};
4967 $main::orig_file_type{$realfile} ||=
4968 $main::locked_file_type{$realfile};
4971 delete($main::locked_file_data{$realfile});
4972 delete($main::locked_file_type{$realfile});
4976 =head2 test_lock(file)
4978 Returns 1 if some file is currently locked, 0 if not.
4983 my $realfile = &translate_filename($_[0]);
4984 return 0 if (!$_[0]);
4985 return 1 if (defined($main::locked_file_list{$realfile}));
4986 return 0 if (!&can_lock_file($realfile));
4988 if (open(LOCKING, "$realfile.lock")) {
4993 return $pid && kill(0, $pid);
4996 =head2 unlock_all_files
4998 Unlocks all files locked by the current script.
5001 sub unlock_all_files
5003 foreach $f (keys %main::locked_file_list) {
5008 =head2 can_lock_file(file)
5010 Returns 1 if some file should be locked, based on the settings in the
5011 Webmin Configuration module. For internal use by lock_file only.
5016 if (&is_readonly_mode()) {
5017 return 0; # never lock in read-only mode
5019 elsif ($gconfig{'lockmode'} == 0) {
5022 elsif ($gconfig{'lockmode'} == 1) {
5026 # Check if under any of the directories
5028 foreach my $d (split(/\t+/, $gconfig{'lockdirs'})) {
5029 if (&same_file($d, $_[0]) ||
5030 &is_under_directory($d, $_[0])) {
5034 return $gconfig{'lockmode'} == 2 ? $match : !$match;
5038 =head2 webmin_log(action, type, object, ¶ms, [module], [host, script-on-host, client-ip])
5040 Log some action taken by a user. This is typically called at the end of a
5041 script, once all file changes are complete and all commands run. The
5044 =item action - A short code for the action being performed, like 'create'.
5046 =item type - A code for the type of object the action is performed to, like 'user'.
5048 =item object - A short name for the object, like 'joe' if the Unix user 'joe' was just created.
5050 =item params - A hash ref of additional information about the action.
5052 =item module - Name of the module in which the action was performed, which defaults to the current module.
5054 =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.
5056 =item script-on-host - Script name like create_user.cgi on the host the action was performed on.
5058 =item client-ip - IP address of the browser that performed the action.
5063 return if (!$gconfig{'log'} || &is_readonly_mode());
5064 my $m = $_[4] ? $_[4] : &get_module_name();
5066 if ($gconfig{'logclear'}) {
5067 # check if it is time to clear the log
5068 my @st = stat("$webmin_logfile.time");
5069 my $write_logtime = 0;
5071 if ($st[9]+$gconfig{'logtime'}*60*60 < time()) {
5072 # clear logfile and all diff files
5073 &unlink_file("$ENV{'WEBMIN_VAR'}/diffs");
5074 &unlink_file("$ENV{'WEBMIN_VAR'}/files");
5075 &unlink_file("$ENV{'WEBMIN_VAR'}/annotations");
5076 unlink($webmin_logfile);
5083 if ($write_logtime) {
5084 open(LOGTIME, ">$webmin_logfile.time");
5085 print LOGTIME time(),"\n";
5090 # If an action script directory is defined, call the appropriate scripts
5091 if ($gconfig{'action_script_dir'}) {
5092 my ($action, $type, $object) = ($_[0], $_[1], $_[2]);
5093 my ($basedir) = $gconfig{'action_script_dir'};
5095 for my $dir ($basedir/$type/$action, $basedir/$type, $basedir) {
5098 opendir(DIR, $dir) or die "Can't open $dir: $!";
5099 while (defined($file = readdir(DIR))) {
5100 next if ($file =~ /^\.\.?$/); # skip '.' and '..'
5101 if (-x "$dir/$file") {
5102 # Call a script notifying it of the action
5104 $ENV{'ACTION_MODULE'} = &get_module_name();
5105 $ENV{'ACTION_ACTION'} = $_[0];
5106 $ENV{'ACTION_TYPE'} = $_[1];
5107 $ENV{'ACTION_OBJECT'} = $_[2];
5108 $ENV{'ACTION_SCRIPT'} = $script_name;
5109 foreach my $p (keys %param) {
5110 $ENV{'ACTION_PARAM_'.uc($p)} = $param{$p};
5112 system("$dir/$file", @_,
5113 "<$null_file", ">$null_file", "2>&1");
5121 # should logging be done at all?
5122 return if ($gconfig{'logusers'} && &indexof($base_remote_user,
5123 split(/\s+/, $gconfig{'logusers'})) < 0);
5124 return if ($gconfig{'logmodules'} && &indexof($m,
5125 split(/\s+/, $gconfig{'logmodules'})) < 0);
5129 my @tm = localtime($now);
5130 my $script_name = $0 =~ /([^\/]+)$/ ? $1 : '-';
5131 my $id = sprintf "%d.%d.%d", $now, $$, $main::action_id_count;
5132 $main::action_id_count++;
5133 my $line = sprintf "%s [%2.2d/%s/%4.4d %2.2d:%2.2d:%2.2d] %s %s %s %s %s \"%s\" \"%s\" \"%s\"",
5134 $id, $tm[3], $text{"smonth_".($tm[4]+1)}, $tm[5]+1900,
5135 $tm[2], $tm[1], $tm[0],
5136 $remote_user || '-',
5137 $main::session_id || '-',
5138 $_[7] || $ENV{'REMOTE_HOST'} || '-',
5139 $m, $_[5] ? "$_[5]:$_[6]" : $script_name,
5140 $_[0], $_[1] ne '' ? $_[1] : '-', $_[2] ne '' ? $_[2] : '-';
5142 foreach my $k (sort { $a cmp $b } keys %{$_[3]}) {
5143 my $v = $_[3]->{$k};
5149 elsif (ref($v) eq 'ARRAY') {
5153 $vv =~ s/(['"\\\r\n\t\%])/sprintf("%%%2.2X",ord($1))/ge;
5154 $line .= " $k='$vv'";
5158 foreach $vv (split(/\0/, $v)) {
5160 $vv =~ s/(['"\\\r\n\t\%])/sprintf("%%%2.2X",ord($1))/ge;
5161 $line .= " $k='$vv'";
5164 $param{$k} = join(" ", @pv);
5166 open(WEBMINLOG, ">>$webmin_logfile");
5167 print WEBMINLOG $line,"\n";
5169 if ($gconfig{'logperms'}) {
5170 chmod(oct($gconfig{'logperms'}), $webmin_logfile);
5173 chmod(0600, $webmin_logfile);
5176 if ($gconfig{'logfiles'} && !&get_module_variable('$no_log_file_changes')) {
5177 # Find and record the changes made to any locked files, or commands run
5179 mkdir("$ENV{'WEBMIN_VAR'}/diffs", 0700);
5180 foreach my $d (@main::locked_file_diff) {
5181 mkdir("$ENV{'WEBMIN_VAR'}/diffs/$id", 0700);
5182 open(DIFFLOG, ">$ENV{'WEBMIN_VAR'}/diffs/$id/$i");
5183 print DIFFLOG "$d->{'type'} $d->{'object'}\n";
5184 print DIFFLOG $d->{'data'};
5186 if ($d->{'input'}) {
5187 open(DIFFLOG, ">$ENV{'WEBMIN_VAR'}/diffs/$id/$i.input");
5188 print DIFFLOG $d->{'input'};
5191 if ($gconfig{'logperms'}) {
5192 chmod(oct($gconfig{'logperms'}),
5193 "$ENV{'WEBMIN_VAR'}/diffs/$id/$i",
5194 "$ENV{'WEBMIN_VAR'}/diffs/$id/$i.input");
5198 @main::locked_file_diff = undef;
5200 if ($gconfig{'logfullfiles'}) {
5201 # Save the original contents of any modified files
5203 mkdir("$ENV{'WEBMIN_VAR'}/files", 0700);
5204 foreach my $f (keys %main::orig_file_data) {
5205 mkdir("$ENV{'WEBMIN_VAR'}/files/$id", 0700);
5206 open(ORIGLOG, ">$ENV{'WEBMIN_VAR'}/files/$id/$i");
5207 if (!defined($main::orig_file_type{$f})) {
5208 print ORIGLOG -1," ",$f,"\n";
5211 print ORIGLOG $main::orig_file_type{$f}," ",$f,"\n";
5213 print ORIGLOG $main::orig_file_data{$f};
5215 if ($gconfig{'logperms'}) {
5216 chmod(oct($gconfig{'logperms'}),
5217 "$ENV{'WEBMIN_VAR'}/files/$id.$i");
5221 %main::orig_file_data = undef;
5222 %main::orig_file_type = undef;
5226 if ($gconfig{'logsyslog'}) {
5227 eval 'use Sys::Syslog qw(:DEFAULT setlogsock);
5228 openlog(&get_product_name(), "cons,pid,ndelay", "daemon");
5229 setlogsock("inet");';
5231 # Syslog module is installed .. try to convert to a
5232 # human-readable form
5234 my $mod = &get_module_name();
5235 my $mdir = module_root_directory($mod);
5236 if (-r "$mdir/log_parser.pl") {
5237 &foreign_require($mod, "log_parser.pl");
5239 foreach my $k (keys %{$_[3]}) {
5240 my $v = $_[3]->{$k};
5241 if (ref($v) eq 'ARRAY') {
5242 $params{$k} = join("\0", @$v);
5248 $msg = &foreign_call($mod, "parse_webmin_log",
5249 $remote_user, $script_name,
5250 $_[0], $_[1], $_[2], \%params);
5251 $msg =~ s/<[^>]*>//g; # Remove tags
5253 elsif ($_[0] eq "_config_") {
5254 my %wtext = &load_language("webminlog");
5255 $msg = $wtext{'search_config'};
5257 $msg ||= "$_[0] $_[1] $_[2]";
5258 my %info = &get_module_info($m);
5259 eval { syslog("info", "%s", "[$info{'desc'}] $msg"); };
5264 =head2 additional_log(type, object, data, [input])
5266 Records additional log data for an upcoming call to webmin_log, such
5267 as a command that was run or SQL that was executed. Typically you will never
5268 need to call this function directory.
5273 if ($gconfig{'logfiles'} && !&get_module_variable('$no_log_file_changes')) {
5274 push(@main::locked_file_diff,
5275 { 'type' => $_[0], 'object' => $_[1], 'data' => $_[2],
5276 'input' => $_[3] } );
5280 =head2 webmin_debug_log(type, message)
5282 Write something to the Webmin debug log. For internal use only.
5285 sub webmin_debug_log
5287 my ($type, $msg) = @_;
5288 return 0 if (!$main::opened_debug_log);
5289 return 0 if ($gconfig{'debug_no'.$main::webmin_script_type});
5290 if ($gconfig{'debug_modules'}) {
5291 my @dmods = split(/\s+/, $gconfig{'debug_modules'});
5292 return 0 if (&indexof($main::initial_module_name, @dmods) < 0);
5295 my @tm = localtime($now);
5297 "%s [%2.2d/%s/%4.4d %2.2d:%2.2d:%2.2d] %s %s %s %s \"%s\"",
5298 $$, $tm[3], $text{"smonth_".($tm[4]+1)}, $tm[5]+1900,
5299 $tm[2], $tm[1], $tm[0],
5300 $remote_user || "-",
5301 $ENV{'REMOTE_HOST'} || "-",
5302 &get_module_name() || "-",
5305 seek(main::DEBUGLOG, 0, 2);
5306 print main::DEBUGLOG $line."\n";
5310 =head2 system_logged(command)
5312 Just calls the Perl system() function, but also logs the command run.
5317 if (&is_readonly_mode()) {
5318 print STDERR "Vetoing command $_[0]\n";
5321 my @realcmd = ( &translate_command($_[0]), @_[1..$#_] );
5322 my $cmd = join(" ", @realcmd);
5324 if ($cmd =~ s/(\s*&\s*)$//) {
5327 while($cmd =~ s/(\d*)(<|>)((\/(tmp|dev)\S+)|&\d+)\s*$//) { }
5328 $cmd =~ s/^\((.*)\)\s*$/$1/;
5330 &additional_log('exec', undef, $cmd);
5331 return system(@realcmd);
5334 =head2 backquote_logged(command)
5336 Executes a command and returns the output (like `command`), but also logs it.
5339 sub backquote_logged
5341 if (&is_readonly_mode()) {
5343 print STDERR "Vetoing command $_[0]\n";
5346 my $realcmd = &translate_command($_[0]);
5349 if ($cmd =~ s/(\s*&\s*)$//) {
5352 while($cmd =~ s/(\d*)(<|>)((\/(tmp\/.webmin|dev)\S+)|&\d+)\s*$//) { }
5353 $cmd =~ s/^\((.*)\)\s*$/$1/;
5355 &additional_log('exec', undef, $cmd);
5356 &webmin_debug_log('CMD', "cmd=$cmd") if ($gconfig{'debug_what_cmd'});
5360 =head2 backquote_with_timeout(command, timeout, safe?, [maxlines])
5362 Runs some command, waiting at most the given number of seconds for it to
5363 complete, and returns the output. The maxlines parameter sets the number
5364 of lines of output to capture. The safe parameter should be set to 1 if the
5365 command is safe for read-only mode users to run.
5368 sub backquote_with_timeout
5370 my $realcmd = &translate_command($_[0]);
5371 &webmin_debug_log('CMD', "cmd=$realcmd timeout=$_[1]")
5372 if ($gconfig{'debug_what_cmd'});
5374 my $pid = &open_execute_command(OUT, "($realcmd) <$null_file", 1, $_[2]);
5379 my $elapsed = time() - $start;
5380 last if ($elapsed > $_[1]);
5382 vec($rmask, fileno(OUT), 1) = 1;
5383 my $sel = select($rmask, undef, undef, $_[1] - $elapsed);
5384 last if (!$sel || $sel < 0);
5386 last if (!defined($line));
5389 if ($_[3] && $linecount >= $_[3]) {
5394 if (kill('TERM', $pid) && time() - $start >= $_[1]) {
5398 return wantarray ? ($out, $timed_out) : $out;
5401 =head2 backquote_command(command, safe?)
5403 Executes a command and returns the output (like `command`), subject to
5404 command translation. The safe parameter should be set to 1 if the command
5405 is safe for read-only mode users to run.
5408 sub backquote_command
5410 if (&is_readonly_mode() && !$_[1]) {
5411 print STDERR "Vetoing command $_[0]\n";
5415 my $realcmd = &translate_command($_[0]);
5416 &webmin_debug_log('CMD', "cmd=$realcmd") if ($gconfig{'debug_what_cmd'});
5420 =head2 kill_logged(signal, pid, ...)
5422 Like Perl's built-in kill function, but also logs the fact that some process
5423 was killed. On Windows, falls back to calling process.exe to terminate a
5429 return scalar(@_)-1 if (&is_readonly_mode());
5430 &webmin_debug_log('KILL', "signal=$_[0] pids=".join(" ", @_[1..@_-1]))
5431 if ($gconfig{'debug_what_procs'});
5432 &additional_log('kill', $_[0], join(" ", @_[1..@_-1])) if (@_ > 1);
5433 if ($gconfig{'os_type'} eq 'windows') {
5434 # Emulate some kills with process.exe
5435 my $arg = $_[0] eq "KILL" ? "-k" :
5436 $_[0] eq "TERM" ? "-q" :
5437 $_[0] eq "STOP" ? "-s" :
5438 $_[0] eq "CONT" ? "-r" : undef;
5440 foreach my $p (@_[1..@_-1]) {
5442 $ok ||= kill($_[0], $p);
5445 &execute_command("process $arg $p");
5457 =head2 rename_logged(old, new)
5459 Re-names a file and logs the rename. If the old and new files are on different
5460 filesystems, calls mv or the Windows rename function to do the job.
5465 &additional_log('rename', $_[0], $_[1]) if ($_[0] ne $_[1]);
5466 return &rename_file($_[0], $_[1]);
5469 =head2 rename_file(old, new)
5471 Renames a file or directory. If the old and new files are on different
5472 filesystems, calls mv or the Windows rename function to do the job.
5477 if (&is_readonly_mode()) {
5478 print STDERR "Vetoing rename from $_[0] to $_[1]\n";
5481 my $src = &translate_filename($_[0]);
5482 my $dst = &translate_filename($_[1]);
5483 &webmin_debug_log('RENAME', "src=$src dst=$dst")
5484 if ($gconfig{'debug_what_ops'});
5485 my $ok = rename($src, $dst);
5486 if (!$ok && $! !~ /permission/i) {
5487 # Try the mv command, in case this is a cross-filesystem rename
5488 if ($gconfig{'os_type'} eq 'windows') {
5489 # Need to use rename
5490 my $out = &backquote_command("rename ".quotemeta($_[0]).
5491 " ".quotemeta($_[1])." 2>&1");
5493 $! = $out if (!$ok);
5497 my $out = &backquote_command("mv ".quotemeta($_[0]).
5498 " ".quotemeta($_[1])." 2>&1");
5500 $! = $out if (!$ok);
5506 =head2 symlink_logged(src, dest)
5508 Create a symlink, and logs it. Effectively does the same thing as the Perl
5515 my $rv = &symlink_file($_[0], $_[1]);
5516 &unlock_file($_[1]);
5520 =head2 symlink_file(src, dest)
5522 Creates a soft link, unless in read-only mode. Effectively does the same thing
5523 as the Perl symlink function.
5528 if (&is_readonly_mode()) {
5529 print STDERR "Vetoing symlink from $_[0] to $_[1]\n";
5532 my $src = &translate_filename($_[0]);
5533 my $dst = &translate_filename($_[1]);
5534 &webmin_debug_log('SYMLINK', "src=$src dst=$dst")
5535 if ($gconfig{'debug_what_ops'});
5536 return symlink($src, $dst);
5539 =head2 link_file(src, dest)
5541 Creates a hard link, unless in read-only mode. The existing new link file
5542 will be deleted if necessary. Effectively the same as Perl's link function.
5547 if (&is_readonly_mode()) {
5548 print STDERR "Vetoing link from $_[0] to $_[1]\n";
5551 my $src = &translate_filename($_[0]);
5552 my $dst = &translate_filename($_[1]);
5553 &webmin_debug_log('LINK', "src=$src dst=$dst")
5554 if ($gconfig{'debug_what_ops'});
5555 unlink($dst); # make sure link works
5556 return link($src, $dst);
5559 =head2 make_dir(dir, perms, recursive)
5561 Creates a directory and sets permissions on it, unless in read-only mode.
5562 The perms parameter sets the octal permissions to apply, which unlike Perl's
5563 mkdir will really get set. The recursive flag can be set to 1 to have the
5564 function create parent directories too.
5569 my ($dir, $perms, $recur) = @_;
5570 if (&is_readonly_mode()) {
5571 print STDERR "Vetoing directory $dir\n";
5574 $dir = &translate_filename($dir);
5575 my $exists = -d $dir ? 1 : 0;
5576 return 1 if ($exists && $recur); # already exists
5577 &webmin_debug_log('MKDIR', $dir) if ($gconfig{'debug_what_ops'});
5578 my $rv = mkdir($dir, $perms);
5579 if (!$rv && $recur) {
5580 # Failed .. try mkdir -p
5581 my $param = $gconfig{'os_type'} eq 'windows' ? "" : "-p";
5582 my $ex = &execute_command("mkdir $param "."e_path($dir));
5588 chmod($perms, $dir);
5593 =head2 set_ownership_permissions(user, group, perms, file, ...)
5595 Sets the user, group owner and permissions on some files. The parameters are :
5597 =item user - UID or username to change the file owner to. If undef, then the owner is not changed.
5599 =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.
5601 =item perms - Octal permissions set to set on the file. If undef, they are left alone.
5603 =item file - One or more files or directories to modify.
5606 sub set_ownership_permissions
5608 my ($user, $group, $perms, @files) = @_;
5609 if (&is_readonly_mode()) {
5610 print STDERR "Vetoing permission changes on ",join(" ", @files),"\n";
5613 @files = map { &translate_filename($_) } @files;
5614 if ($gconfig{'debug_what_ops'}) {
5615 foreach my $f (@files) {
5616 &webmin_debug_log('PERMS',
5617 "file=$f user=$user group=$group perms=$perms");
5621 if (defined($user)) {
5622 my $uid = $user !~ /^\d+$/ ? getpwnam($user) : $user;
5624 if (defined($group)) {
5625 $gid = $group !~ /^\d+$/ ? getgrnam($group) : $group;
5628 my @uinfo = getpwuid($uid);
5631 $rv = chown($uid, $gid, @files);
5633 if ($rv && defined($perms)) {
5634 $rv = chmod($perms, @files);
5639 =head2 unlink_logged(file, ...)
5641 Like Perl's unlink function, but locks the files beforehand and un-locks them
5642 after so that the deletion is logged by Webmin.
5648 foreach my $f (@_) {
5649 if (!&test_lock($f)) {
5654 my @rv = &unlink_file(@_);
5655 foreach my $f (@_) {
5660 return wantarray ? @rv : $rv[0];
5663 =head2 unlink_file(file, ...)
5665 Deletes some files or directories. Like Perl's unlink function, but also
5666 recursively deletes directories with the rm command if needed.
5671 return 1 if (&is_readonly_mode());
5674 foreach my $f (@_) {
5675 &unflush_file_lines($f);
5676 my $realf = &translate_filename($f);
5677 &webmin_debug_log('UNLINK', $realf) if ($gconfig{'debug_what_ops'});
5679 if (!rmdir($realf)) {
5681 if ($gconfig{'os_type'} eq 'windows') {
5682 # Call del and rmdir commands
5685 my $out = `del /q "$qm" 2>&1`;
5687 $out = `rmdir "$qm" 2>&1`;
5692 my $qm = quotemeta($realf);
5693 $out = `rm -rf $qm 2>&1`;
5702 if (!unlink($realf)) {
5708 return wantarray ? ($rv, $err) : $rv;
5711 =head2 copy_source_dest(source, dest)
5713 Copy some file or directory to a new location. Returns 1 on success, or 0
5714 on failure - also sets $! on failure. If the source is a directory, uses
5715 piped tar commands to copy a whole directory structure including permissions
5719 sub copy_source_dest
5721 return (1, undef) if (&is_readonly_mode());
5722 my ($src, $dst) = @_;
5725 &webmin_debug_log('COPY', "src=$src dst=$dst")
5726 if ($gconfig{'debug_what_ops'});
5727 if ($gconfig{'os_type'} eq 'windows') {
5728 # No tar or cp on windows, so need to use copy command
5732 $out = &backquote_logged("xcopy \"$src\" \"$dst\" /Y /E /I 2>&1");
5735 $out = &backquote_logged("copy /Y \"$src\" \"$dst\" 2>&1");
5743 # A directory .. need to copy with tar command
5744 my @st = stat($src);
5747 &set_ownership_permissions($st[4], $st[5], $st[2], $dst);
5748 $out = &backquote_logged("(cd ".quotemeta($src)." ; tar cf - . | (cd ".quotemeta($dst)." ; tar xf -)) 2>&1");
5755 # Can just copy with cp
5756 my $out = &backquote_logged("cp -p ".quotemeta($src).
5757 " ".quotemeta($dst)." 2>&1");
5763 return wantarray ? ($ok, $err) : $ok;
5766 =head2 remote_session_name(host|&server)
5768 Generates a session ID for some server. For this server, this will always
5769 be an empty string. For a server object it will include the hostname and
5770 port and PID. For a server name, it will include the hostname and PID. For
5774 sub remote_session_name
5776 return ref($_[0]) && $_[0]->{'host'} && $_[0]->{'port'} ?
5777 "$_[0]->{'host'}:$_[0]->{'port'}.$$" :
5778 $_[0] eq "" || ref($_[0]) && $_[0]->{'id'} == 0 ? "" :
5779 ref($_[0]) ? "" : "$_[0].$$";
5782 =head2 remote_foreign_require(server, module, file)
5784 Connects to rpc.cgi on a remote webmin server and have it open a session
5785 to a process that will actually do the require and run functions. This is the
5786 equivalent for foreign_require, but for a remote Webmin system. The server
5787 parameter can either be a hostname of a system registered in the Webmin
5788 Servers Index module, or a hash reference for a system from that module.
5791 sub remote_foreign_require
5793 my $call = { 'action' => 'require',
5796 my $sn = &remote_session_name($_[0]);
5797 if ($remote_session{$sn}) {
5798 $call->{'session'} = $remote_session{$sn};
5801 $call->{'newsession'} = 1;
5803 my $rv = &remote_rpc_call($_[0], $call);
5804 if ($rv->{'session'}) {
5805 $remote_session{$sn} = $rv->{'session'};
5806 $remote_session_server{$sn} = $_[0];
5810 =head2 remote_foreign_call(server, module, function, [arg]*)
5812 Call a function on a remote server. Must have been setup first with
5813 remote_foreign_require for the same server and module. Equivalent to
5814 foreign_call, but with the extra server parameter to specify the remote
5818 sub remote_foreign_call
5820 return undef if (&is_readonly_mode());
5821 my $sn = &remote_session_name($_[0]);
5822 return &remote_rpc_call($_[0], { 'action' => 'call',
5825 'session' => $remote_session{$sn},
5826 'args' => [ @_[3 .. $#_] ] } );
5829 =head2 remote_foreign_check(server, module, [api-only])
5831 Checks if some module is installed and supported on a remote server. Equivilant
5832 to foreign_check, but for the remote Webmin system specified by the server
5836 sub remote_foreign_check
5838 return &remote_rpc_call($_[0], { 'action' => 'check',
5843 =head2 remote_foreign_config(server, module)
5845 Gets the configuration for some module from a remote server, as a hash.
5846 Equivalent to foreign_config, but for a remote system.
5849 sub remote_foreign_config
5851 return &remote_rpc_call($_[0], { 'action' => 'config',
5852 'module' => $_[1] });
5855 =head2 remote_eval(server, module, code)
5857 Evaluates some perl code in the context of a module on a remote webmin server.
5858 The server parameter must be the hostname of a remote system, module must
5859 be a module directory name, and code a string of Perl code to run. This can
5860 only be called after remote_foreign_require for the same server and module.
5865 return undef if (&is_readonly_mode());
5866 my $sn = &remote_session_name($_[0]);
5867 return &remote_rpc_call($_[0], { 'action' => 'eval',
5870 'session' => $remote_session{$sn} });
5873 =head2 remote_write(server, localfile, [remotefile], [remotebasename])
5875 Transfers some local file to another server via Webmin's RPC protocol, and
5876 returns the resulting remote filename. If the remotefile parameter is given,
5877 that is the destination filename which will be used. Otherwise a randomly
5878 selected temporary filename will be used, and returned by the function.
5883 return undef if (&is_readonly_mode());
5885 my $sn = &remote_session_name($_[0]);
5886 if (!$_[0] || $remote_server_version{$sn} >= 0.966) {
5887 # Copy data over TCP connection
5888 my $rv = &remote_rpc_call($_[0], { 'action' => 'tcpwrite',
5890 'name' => $_[3] } );
5892 my $serv = ref($_[0]) ? $_[0]->{'host'} : $_[0];
5893 &open_socket($serv || "localhost", $rv->[1], TWRITE, \$error);
5894 return &$main::remote_error_handler("Failed to transfer file : $error")
5897 while(read(FILE, $got, 1024) > 0) {
5901 shutdown(TWRITE, 1);
5903 if ($error && $error !~ /^OK/) {
5904 # Got back an error!
5905 return &$main::remote_error_handler("Failed to transfer file : $error");
5911 # Just pass file contents as parameters
5913 while(read(FILE, $got, 1024) > 0) {
5917 return &remote_rpc_call($_[0], { 'action' => 'write',
5920 'session' => $remote_session{$sn} });
5924 =head2 remote_read(server, localfile, remotefile)
5926 Transfers a file from a remote server to this system, using Webmin's RPC
5927 protocol. The server parameter must be the hostname of a system registered
5928 in the Webmin Servers Index module, localfile is the destination path on this
5929 system, and remotefile is the file to fetch from the remote server.
5934 my $sn = &remote_session_name($_[0]);
5935 if (!$_[0] || $remote_server_version{$sn} >= 0.966) {
5936 # Copy data over TCP connection
5937 my $rv = &remote_rpc_call($_[0], { 'action' => 'tcpread',
5938 'file' => $_[2] } );
5940 return &$main::remote_error_handler("Failed to transfer file : $rv->[1]");
5943 my $serv = ref($_[0]) ? $_[0]->{'host'} : $_[0];
5944 &open_socket($serv || "localhost", $rv->[1], TREAD, \$error);
5945 return &$main::remote_error_handler("Failed to transfer file : $error")
5948 open(FILE, ">$_[1]");
5949 while(read(TREAD, $got, 1024) > 0) {
5956 # Just get data as return value
5957 my $d = &remote_rpc_call($_[0], { 'action' => 'read',
5959 'session' => $remote_session{$sn} });
5960 open(FILE, ">$_[1]");
5966 =head2 remote_finished
5968 Close all remote sessions. This happens automatically after a while
5969 anyway, but this function should be called to clean things up faster.
5974 foreach my $sn (keys %remote_session) {
5975 my $server = $remote_session_server{$sn};
5976 &remote_rpc_call($server, { 'action' => 'quit',
5977 'session' => $remote_session{$sn} } );
5978 delete($remote_session{$sn});
5979 delete($remote_session_server{$sn});
5981 foreach $fh (keys %fast_fh_cache) {
5983 delete($fast_fh_cache{$fh});
5987 =head2 remote_error_setup(&function)
5989 Sets a function to be called instead of &error when a remote RPC operation
5990 fails. Useful if you want to have more control over your remote operations.
5993 sub remote_error_setup
5995 $main::remote_error_handler = $_[0] || \&error;
5998 =head2 remote_rpc_call(server, structure)
6000 Calls rpc.cgi on some server and passes it a perl structure (hash,array,etc)
6001 and then reads back a reply structure. This is mainly for internal use only,
6002 and is called by the other remote_* functions.
6008 my $sn = &remote_session_name($_[0]); # Will be undef for local connection
6010 # Server structure was given
6012 $serv->{'user'} || $serv->{'id'} == 0 ||
6013 return &$main::remote_error_handler(
6014 "No Webmin login set for server");
6017 # lookup the server in the webmin servers module if needed
6018 if (!%main::remote_servers_cache) {
6019 &foreign_require("servers", "servers-lib.pl");
6020 foreach $s (&foreign_call("servers", "list_servers")) {
6021 $main::remote_servers_cache{$s->{'host'}} = $s;
6022 $main::remote_servers_cache{$s->{'host'}.":".$s->{'port'}} = $s;
6025 $serv = $main::remote_servers_cache{$_[0]};
6026 $serv || return &$main::remote_error_handler(
6027 "No Webmin Servers entry for $_[0]");
6028 $serv->{'user'} || return &$main::remote_error_handler(
6029 "No login set for server $_[0]");
6031 my $ip = $serv->{'ip'} || $serv->{'host'};
6033 # Work out the username and password
6035 if ($serv->{'sameuser'}) {
6036 $user = $remote_user;
6037 defined($main::remote_pass) || return &$main::remote_error_handler(
6038 "Password for this server is not available");
6039 $pass = $main::remote_pass;
6042 $user = $serv->{'user'};
6043 $pass = $serv->{'pass'};
6046 if ($serv->{'fast'} || !$sn) {
6047 # Make TCP connection call to fastrpc.cgi
6048 if (!$fast_fh_cache{$sn} && $sn) {
6049 # Need to open the connection
6050 my $con = &make_http_connection(
6051 $ip, $serv->{'port'}, $serv->{'ssl'},
6052 "POST", "/fastrpc.cgi");
6053 return &$main::remote_error_handler(
6054 "Failed to connect to $serv->{'host'} : $con")
6056 &write_http_connection($con, "Host: $serv->{'host'}\r\n");
6057 &write_http_connection($con, "User-agent: Webmin\r\n");
6058 my $auth = &encode_base64("$user:$pass");
6060 &write_http_connection($con, "Authorization: basic $auth\r\n");
6061 &write_http_connection($con, "Content-length: ",
6062 length($tostr),"\r\n");
6063 &write_http_connection($con, "\r\n");
6064 &write_http_connection($con, $tostr);
6066 # read back the response
6067 my $line = &read_http_connection($con);
6068 $line =~ tr/\r\n//d;
6069 if ($line =~ /^HTTP\/1\..\s+401\s+/) {
6070 return &$main::remote_error_handler("Login to RPC server as $user rejected");
6072 $line =~ /^HTTP\/1\..\s+200\s+/ ||
6073 return &$main::remote_error_handler("HTTP error : $line");
6075 $line = &read_http_connection($con);
6076 $line =~ tr/\r\n//d;
6078 $line = &read_http_connection($con);
6079 if ($line =~ /^0\s+(.*)/) {
6080 return &$main::remote_error_handler("RPC error : $1");
6082 elsif ($line =~ /^1\s+(\S+)\s+(\S+)\s+(\S+)/ ||
6083 $line =~ /^1\s+(\S+)\s+(\S+)/) {
6084 # Started ok .. connect and save SID
6085 &close_http_connection($con);
6086 my ($port, $sid, $version, $error) = ($1, $2, $3);
6087 &open_socket($ip, $port, $sid, \$error);
6088 return &$main::remote_error_handler("Failed to connect to fastrpc.cgi : $error")
6090 $fast_fh_cache{$sn} = $sid;
6091 $remote_server_version{$sn} = $version;
6094 while($stuff = &read_http_connection($con)) {
6097 return &$main::remote_error_handler("Bad response from fastrpc.cgi : $line");
6100 elsif (!$fast_fh_cache{$sn}) {
6101 # Open the connection by running fastrpc.cgi locally
6102 pipe(RPCOUTr, RPCOUTw);
6106 open(STDOUT, ">&RPCOUTw");
6110 $ENV{'REQUEST_METHOD'} = 'GET';
6111 $ENV{'SCRIPT_NAME'} = '/fastrpc.cgi';
6112 $ENV{'SERVER_ROOT'} ||= $root_directory;
6114 if ($base_remote_user ne 'root' &&
6115 $base_remote_user ne 'admin') {
6116 # Need to fake up a login for the CGI!
6117 &read_acl(undef, \%acl, [ 'root' ]);
6118 $ENV{'BASE_REMOTE_USER'} =
6119 $ENV{'REMOTE_USER'} =
6120 $acl{'root'} ? 'root' : 'admin';
6122 delete($ENV{'FOREIGN_MODULE_NAME'});
6123 delete($ENV{'FOREIGN_ROOT_DIRECTORY'});
6124 chdir($root_directory);
6125 if (!exec("$root_directory/fastrpc.cgi")) {
6126 print "exec failed : $!\n";
6133 ($line = <RPCOUTr>) =~ tr/\r\n//d;
6137 if ($line =~ /^0\s+(.*)/) {
6138 return &$main::remote_error_handler("RPC error : $2");
6140 elsif ($line =~ /^1\s+(\S+)\s+(\S+)/) {
6141 # Started ok .. connect and save SID
6143 my ($port, $sid, $error) = ($1, $2, undef);
6144 &open_socket("localhost", $port, $sid, \$error);
6145 return &$main::remote_error_handler("Failed to connect to fastrpc.cgi : $error") if ($error);
6146 $fast_fh_cache{$sn} = $sid;
6153 &error("Bad response from fastrpc.cgi : $line");
6156 # Got a connection .. send off the request
6157 my $fh = $fast_fh_cache{$sn};
6158 my $tostr = &serialise_variable($_[1]);
6159 print $fh length($tostr)," $fh\n";
6161 my $rlen = int(<$fh>);
6162 my ($fromstr, $got);
6163 while(length($fromstr) < $rlen) {
6164 return &$main::remote_error_handler("Failed to read from fastrpc.cgi")
6165 if (read($fh, $got, $rlen - length($fromstr)) <= 0);
6168 my $from = &unserialise_variable($fromstr);
6170 return &$main::remote_error_handler("Remote Webmin error");
6172 if (defined($from->{'arv'})) {
6173 return @{$from->{'arv'}};
6176 return $from->{'rv'};
6180 # Call rpc.cgi on remote server
6181 my $tostr = &serialise_variable($_[1]);
6183 my $con = &make_http_connection($ip, $serv->{'port'},
6184 $serv->{'ssl'}, "POST", "/rpc.cgi");
6185 return &$main::remote_error_handler("Failed to connect to $serv->{'host'} : $con") if (!ref($con));
6187 &write_http_connection($con, "Host: $serv->{'host'}\r\n");
6188 &write_http_connection($con, "User-agent: Webmin\r\n");
6189 my $auth = &encode_base64("$user:$pass");
6191 &write_http_connection($con, "Authorization: basic $auth\r\n");
6192 &write_http_connection($con, "Content-length: ",length($tostr),"\r\n");
6193 &write_http_connection($con, "\r\n");
6194 &write_http_connection($con, $tostr);
6196 # read back the response
6197 my $line = &read_http_connection($con);
6198 $line =~ tr/\r\n//d;
6199 if ($line =~ /^HTTP\/1\..\s+401\s+/) {
6200 return &$main::remote_error_handler("Login to RPC server as $user rejected");
6202 $line =~ /^HTTP\/1\..\s+200\s+/ || return &$main::remote_error_handler("RPC HTTP error : $line");
6204 $line = &read_http_connection($con);
6205 $line =~ tr/\r\n//d;
6208 while($line = &read_http_connection($con)) {
6212 my $from = &unserialise_variable($fromstr);
6213 return &$main::remote_error_handler("Invalid RPC login to $serv->{'host'}") if (!$from->{'status'});
6214 if (defined($from->{'arv'})) {
6215 return @{$from->{'arv'}};
6218 return $from->{'rv'};
6223 =head2 remote_multi_callback(&servers, parallel, &function, arg|&args, &returns, &errors, [module, library])
6225 Executes some function in parallel on multiple servers at once. Fills in
6226 the returns and errors arrays respectively. If the module and library
6227 parameters are given, that module is remotely required on the server first,
6228 to check if it is connectable. The parameters are :
6230 =item servers - A list of Webmin system hash references.
6232 =item parallel - Number of parallel operations to perform.
6234 =item function - Reference to function to call for each system.
6236 =item args - Additional parameters to the function.
6238 =item returns - Array ref to place return values into, in same order as servers.
6240 =item errors - Array ref to place error messages into.
6242 =item module - Optional module to require on the remote system first.
6244 =item library - Optional library to require in the module.
6247 sub remote_multi_callback
6249 my ($servs, $parallel, $func, $args, $rets, $errs, $mod, $lib) = @_;
6250 &remote_error_setup(\&remote_multi_callback_error);
6252 # Call the functions
6254 foreach my $g (@$servs) {
6260 $remote_multi_callback_err = undef;
6262 # Require the remote lib
6263 &remote_foreign_require($g->{'host'}, $mod, $lib);
6264 if ($remote_multi_callback_err) {
6265 # Failed .. return error
6266 print $wh &serialise_variable(
6267 [ undef, $remote_multi_callback_err ]);
6273 my $a = ref($args) ? $args->[$p] : $args;
6274 my $rv = &$func($g, $a);
6277 print $wh &serialise_variable(
6278 [ $rv, $remote_multi_callback_err ]);
6286 # Read back the results
6288 foreach my $g (@$servs) {
6292 $errs->[$p] = "Failed to read response from $g->{'host'}";
6295 my $rv = &unserialise_variable($line);
6297 $rets->[$p] = $rv->[0];
6298 $errs->[$p] = $rv->[1];
6303 &remote_error_setup(undef);
6306 sub remote_multi_callback_error
6308 $remote_multi_callback_err = $_[0];
6311 =head2 serialise_variable(variable)
6313 Converts some variable (maybe a scalar, hash ref, array ref or scalar ref)
6314 into a url-encoded string. In the cases of arrays and hashes, it is recursively
6315 called on each member to serialize the entire object.
6318 sub serialise_variable
6320 if (!defined($_[0])) {
6326 $rv = &urlize($_[0]);
6328 elsif ($r eq 'SCALAR') {
6329 $rv = &urlize(${$_[0]});
6331 elsif ($r eq 'ARRAY') {
6332 $rv = join(",", map { &urlize(&serialise_variable($_)) } @{$_[0]});
6334 elsif ($r eq 'HASH') {
6335 $rv = join(",", map { &urlize(&serialise_variable($_)).",".
6336 &urlize(&serialise_variable($_[0]->{$_})) }
6339 elsif ($r eq 'REF') {
6340 $rv = &serialise_variable(${$_[0]});
6342 elsif ($r eq 'CODE') {
6347 # An object - treat as a hash
6348 $r = "OBJECT ".&urlize($r);
6349 $rv = join(",", map { &urlize(&serialise_variable($_)).",".
6350 &urlize(&serialise_variable($_[0]->{$_})) }
6353 return ($r ? $r : 'VAL').",".$rv;
6356 =head2 unserialise_variable(string)
6358 Converts a string created by serialise_variable() back into the original
6359 scalar, hash ref, array ref or scalar ref. If the original variable was a Perl
6360 object, the same class is used on this system, if available.
6363 sub unserialise_variable
6365 my @v = split(/,/, $_[0]);
6367 if ($v[0] eq 'VAL') {
6368 @v = split(/,/, $_[0], -1);
6369 $rv = &un_urlize($v[1]);
6371 elsif ($v[0] eq 'SCALAR') {
6372 local $r = &un_urlize($v[1]);
6375 elsif ($v[0] eq 'ARRAY') {
6377 for(my $i=1; $i<@v; $i++) {
6378 push(@$rv, &unserialise_variable(&un_urlize($v[$i])));
6381 elsif ($v[0] eq 'HASH') {
6383 for(my $i=1; $i<@v; $i+=2) {
6384 $rv->{&unserialise_variable(&un_urlize($v[$i]))} =
6385 &unserialise_variable(&un_urlize($v[$i+1]));
6388 elsif ($v[0] eq 'REF') {
6389 local $r = &unserialise_variable($v[1]);
6392 elsif ($v[0] eq 'UNDEF') {
6395 elsif ($v[0] =~ /^OBJECT\s+(.*)$/) {
6396 # An object hash that we have to re-bless
6399 for(my $i=1; $i<@v; $i+=2) {
6400 $rv->{&unserialise_variable(&un_urlize($v[$i]))} =
6401 &unserialise_variable(&un_urlize($v[$i+1]));
6409 =head2 other_groups(user)
6411 Returns a list of secondary groups a user is a member of, as a list of
6420 while(my @g = getgrent()) {
6421 my @m = split(/\s+/, $g[3]);
6422 push(@rv, $g[2]) if (&indexof($user, @m) >= 0);
6424 endgrent() if ($gconfig{'os_type'} ne 'hpux');
6428 =head2 date_chooser_button(dayfield, monthfield, yearfield)
6430 Returns HTML for a button that pops up a data chooser window. The parameters
6433 =item dayfield - Name of the text field to place the day of the month into.
6435 =item monthfield - Name of the select field to select the month of the year in, indexed from 1.
6437 =item yearfield - Name of the text field to place the year into.
6440 sub date_chooser_button
6442 return &theme_date_chooser_button(@_)
6443 if (defined(&theme_date_chooser_button));
6444 my ($w, $h) = (250, 225);
6445 if ($gconfig{'db_sizedate'}) {
6446 ($w, $h) = split(/x/, $gconfig{'db_sizedate'});
6448 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";
6451 =head2 help_file(module, file)
6453 Returns the path to a module's help file of some name, typically under the
6454 help directory with a .html extension.
6459 my $mdir = &module_root_directory($_[0]);
6460 my $dir = "$mdir/help";
6461 foreach my $o (@lang_order_list) {
6462 my $lang = "$dir/$_[1].$o.html";
6463 return $lang if (-r $lang);
6465 return "$dir/$_[1].html";
6470 Seeds the random number generator, if not already done in this script. On Linux
6471 this makes use of the current time, process ID and a read from /dev/urandom.
6472 On other systems, only the current time and process ID are used.
6477 if (!$main::done_seed_random) {
6478 if (open(RANDOM, "/dev/urandom")) {
6480 read(RANDOM, $buf, 4);
6482 srand(time() ^ $$ ^ $buf);
6487 $main::done_seed_random = 1;
6491 =head2 disk_usage_kb(directory)
6493 Returns the number of kB used by some directory and all subdirs. Implemented
6494 by calling the C<du -k> command.
6499 my $dir = &translate_filename($_[0]);
6501 my $ex = &execute_command("du -sk ".quotemeta($dir), undef, \$out, undef, 0, 1);
6503 &execute_command("du -s ".quotemeta($dir), undef, \$out, undef, 0, 1);
6505 return $out =~ /^([0-9]+)/ ? $1 : "???";
6508 =head2 recursive_disk_usage(directory, [skip-regexp], [only-regexp])
6510 Returns the number of bytes taken up by all files in some directory and all
6511 sub-directories, by summing up their lengths. The disk_usage_kb is more
6512 reflective of reality, as the filesystem typically pads file sizes to 1k or
6516 sub recursive_disk_usage
6518 my $dir = &translate_filename($_[0]);
6525 my @st = stat($dir);
6531 my @files = readdir(DIR);
6533 foreach my $f (@files) {
6534 next if ($f eq "." || $f eq "..");
6535 next if ($skip && $f =~ /$skip/);
6536 next if ($only && $f !~ /$only/);
6537 $rv += &recursive_disk_usage("$dir/$f", $skip, $only);
6543 =head2 help_search_link(term, [ section, ... ] )
6545 Returns HTML for a link to the man module for searching local and online
6546 docs for various search terms. The term parameter can either be a single
6547 word like 'bind', or a space-separated list of words. This function is typically
6548 used by modules that want to refer users to additional documentation in man
6549 pages or local system doc files.
6552 sub help_search_link
6554 if (&foreign_available("man") && !$tconfig{'nosearch'}) {
6555 my $for = &urlize(shift(@_));
6556 return "<a href='$gconfig{'webprefix'}/man/search.cgi?".
6557 join("&", map { "section=$_" } @_)."&".
6558 "for=$for&exact=1&check=".&get_module_name()."'>".
6559 $text{'helpsearch'}."</a>\n";
6566 =head2 make_http_connection(host, port, ssl, method, page, [&headers])
6568 Opens a connection to some HTTP server, maybe through a proxy, and returns
6569 a handle object. The handle can then be used to send additional headers
6570 and read back a response. If anything goes wrong, returns an error string.
6571 The parameters are :
6573 =item host - Hostname or IP address of the webserver to connect to.
6575 =item port - HTTP port number to connect to.
6577 =item ssl - Set to 1 to connect in SSL mode.
6579 =item method - HTTP method, like GET or POST.
6581 =item page - Page to request on the webserver, like /foo/index.html
6583 =item headers - Array ref of additional HTTP headers, each of which is a 2-element array ref.
6586 sub make_http_connection
6588 my ($host, $port, $ssl, $method, $page, $headers) = @_;
6591 foreach my $h (@$headers) {
6592 $htxt .= $h->[0].": ".$h->[1]."\r\n";
6596 if (&is_readonly_mode()) {
6597 return "HTTP connections not allowed in readonly mode";
6599 my $rv = { 'fh' => time().$$ };
6602 eval "use Net::SSLeay";
6603 $@ && return $text{'link_essl'};
6604 eval "Net::SSLeay::SSLeay_add_ssl_algorithms()";
6605 eval "Net::SSLeay::load_error_strings()";
6606 $rv->{'ssl_ctx'} = Net::SSLeay::CTX_new() ||
6607 return "Failed to create SSL context";
6608 $rv->{'ssl_con'} = Net::SSLeay::new($rv->{'ssl_ctx'}) ||
6609 return "Failed to create SSL connection";
6611 if ($gconfig{'http_proxy'} =~ /^http:\/\/(\S+):(\d+)/ &&
6612 !&no_proxy($host)) {
6615 &open_socket($1, $2, $rv->{'fh'}, \$error);
6618 my $fh = $rv->{'fh'};
6619 print $fh "CONNECT $host:$port HTTP/1.0\r\n";
6620 if ($gconfig{'proxy_user'}) {
6621 my $auth = &encode_base64(
6622 "$gconfig{'proxy_user'}:".
6623 "$gconfig{'proxy_pass'}");
6624 $auth =~ tr/\r\n//d;
6625 print $fh "Proxy-Authorization: Basic $auth\r\n";
6629 if ($line =~ /^HTTP(\S+)\s+(\d+)\s+(.*)/) {
6630 return "Proxy error : $3" if ($2 != 200);
6633 return "Proxy error : $line";
6638 elsif (!$gconfig{'proxy_fallback'}) {
6639 # Connection to proxy failed - give up
6646 &open_socket($host, $port, $rv->{'fh'}, \$error);
6647 return $error if ($error);
6649 Net::SSLeay::set_fd($rv->{'ssl_con'}, fileno($rv->{'fh'}));
6650 Net::SSLeay::connect($rv->{'ssl_con'}) ||
6651 return "SSL connect() failed";
6652 my $rtxt = "$method $page HTTP/1.0\r\n".$htxt;
6653 Net::SSLeay::write($rv->{'ssl_con'}, $rtxt);
6656 # Plain HTTP request
6658 if ($gconfig{'http_proxy'} =~ /^http:\/\/(\S+):(\d+)/ &&
6659 !&no_proxy($host)) {
6662 &open_socket($1, $2, $rv->{'fh'}, \$error);
6666 my $fh = $rv->{'fh'};
6667 my $rtxt = $method." ".
6668 "http://$host:$port$page HTTP/1.0\r\n";
6669 if ($gconfig{'proxy_user'}) {
6670 my $auth = &encode_base64(
6671 "$gconfig{'proxy_user'}:".
6672 "$gconfig{'proxy_pass'}");
6673 $auth =~ tr/\r\n//d;
6674 $rtxt .= "Proxy-Authorization: Basic $auth\r\n";
6679 elsif (!$gconfig{'proxy_fallback'}) {
6684 # Connecting directly
6686 &open_socket($host, $port, $rv->{'fh'}, \$error);
6687 return $error if ($error);
6688 my $fh = $rv->{'fh'};
6689 my $rtxt = "$method $page HTTP/1.0\r\n".$htxt;
6696 =head2 read_http_connection(&handle, [bytes])
6698 Reads either one line or up to the specified number of bytes from the handle,
6699 originally supplied by make_http_connection.
6702 sub read_http_connection
6706 if ($h->{'ssl_con'}) {
6709 while(($idx = index($h->{'buffer'}, "\n")) < 0) {
6710 # need to read more..
6711 if (!($more = Net::SSLeay::read($h->{'ssl_con'}))) {
6713 $rv = $h->{'buffer'};
6714 delete($h->{'buffer'});
6717 $h->{'buffer'} .= $more;
6719 $rv = substr($h->{'buffer'}, 0, $idx+1);
6720 $h->{'buffer'} = substr($h->{'buffer'}, $idx+1);
6723 if (length($h->{'buffer'})) {
6724 $rv = $h->{'buffer'};
6725 delete($h->{'buffer'});
6728 $rv = Net::SSLeay::read($h->{'ssl_con'}, $_[1]);
6734 read($h->{'fh'}, $rv, $_[1]) > 0 || return undef;
6737 my $fh = $h->{'fh'};
6741 $rv = undef if ($rv eq "");
6745 =head2 write_http_connection(&handle, [data+])
6747 Writes the given data to the given HTTP connection handle.
6750 sub write_http_connection
6753 my $fh = $h->{'fh'};
6755 if ($h->{'ssl_ctx'}) {
6756 foreach my $s (@_) {
6757 my $ok = Net::SSLeay::write($h->{'ssl_con'}, $s);
6758 $allok = 0 if (!$ok);
6762 my $ok = (print $fh @_);
6763 $allok = 0 if (!$ok);
6768 =head2 close_http_connection(&handle)
6770 Closes a connection to an HTTP server, identified by the given handle.
6773 sub close_http_connection
6779 =head2 clean_environment
6781 Deletes any environment variables inherited from miniserv so that they
6782 won't be passed to programs started by webmin. This is useful when calling
6783 programs that check for CGI-related environment variables and modify their
6784 behaviour, and to avoid passing sensitive variables to un-trusted programs.
6787 sub clean_environment
6789 %UNCLEAN_ENV = %ENV;
6790 foreach my $k (keys %ENV) {
6791 if ($k =~ /^(HTTP|VIRTUALSERVER|QUOTA|USERADMIN)_/) {
6795 foreach my $e ('WEBMIN_CONFIG', 'SERVER_NAME', 'CONTENT_TYPE', 'REQUEST_URI',
6796 'PATH_INFO', 'WEBMIN_VAR', 'REQUEST_METHOD', 'GATEWAY_INTERFACE',
6797 'QUERY_STRING', 'REMOTE_USER', 'SERVER_SOFTWARE', 'SERVER_PROTOCOL',
6798 'REMOTE_HOST', 'SERVER_PORT', 'DOCUMENT_ROOT', 'SERVER_ROOT',
6799 'MINISERV_CONFIG', 'SCRIPT_NAME', 'SERVER_ADMIN', 'CONTENT_LENGTH',
6800 'HTTPS', 'FOREIGN_MODULE_NAME', 'FOREIGN_ROOT_DIRECTORY',
6801 'SCRIPT_FILENAME', 'PATH_TRANSLATED', 'BASE_REMOTE_USER',
6802 'DOCUMENT_REALROOT', 'MINISERV_CONFIG', 'MYSQL_PWD',
6808 =head2 reset_environment
6810 Puts the environment back how it was before clean_environment was callled.
6813 sub reset_environment
6816 foreach my $k (keys %UNCLEAN_ENV) {
6817 $ENV{$k} = $UNCLEAN_ENV{$k};
6819 undef(%UNCLEAN_ENV);
6823 =head2 progress_callback
6825 Never called directly, but useful for passing to &http_download to print
6826 out progress of an HTTP request.
6829 sub progress_callback
6831 if (defined(&theme_progress_callback)) {
6832 # Call the theme override
6833 return &theme_progress_callback(@_);
6837 print $progress_callback_prefix;
6839 $progress_size = $_[1];
6840 $progress_step = int($_[1] / 10);
6841 print &text('progress_size2', $progress_callback_url,
6842 &nice_size($progress_size)),"<br>\n";
6845 print &text('progress_nosize', $progress_callback_url),"<br>\n";
6847 $last_progress_time = $last_progress_size = undef;
6849 elsif ($_[0] == 3) {
6851 my $sp = $progress_callback_prefix.(" " x 5);
6852 if ($progress_size) {
6853 # And we have a size to compare against
6854 my $st = int(($_[1] * 10) / $progress_size);
6855 my $time_now = time();
6856 if ($st != $progress_step ||
6857 $time_now - $last_progress_time > 60) {
6858 # Show progress every 10% or 60 seconds
6859 print $sp,&text('progress_datan', &nice_size($_[1]),
6860 int($_[1]*100/$progress_size)),"<br>\n";
6861 $last_progress_time = $time_now;
6863 $progress_step = $st;
6866 # No total size .. so only show in 100k jumps
6867 if ($_[1] > $last_progress_size+100*1024) {
6868 print $sp,&text('progress_data2n',
6869 &nice_size($_[1])),"<br>\n";
6870 $last_progress_size = $_[1];
6874 elsif ($_[0] == 4) {
6875 # All done downloading
6876 print $progress_callback_prefix,&text('progress_done'),"<br>\n";
6878 elsif ($_[0] == 5) {
6879 # Got new location after redirect
6880 $progress_callback_url = $_[1];
6882 elsif ($_[0] == 6) {
6884 $progress_callback_url = $_[1];
6885 print &text('progress_incache', $progress_callback_url),"<br>\n";
6889 =head2 switch_to_remote_user
6891 Changes the user and group of the current process to that of the unix user
6892 with the same name as the current webmin login, or fails if there is none.
6893 This should be called by Usermin module scripts that only need to run with
6894 limited permissions.
6897 sub switch_to_remote_user
6899 @remote_user_info = $remote_user ? getpwnam($remote_user) :
6901 @remote_user_info || &error(&text('switch_remote_euser', $remote_user));
6902 &create_missing_homedir(\@remote_user_info);
6904 &switch_to_unix_user(\@remote_user_info);
6905 $ENV{'USER'} = $ENV{'LOGNAME'} = $remote_user;
6906 $ENV{'HOME'} = $remote_user_info[7];
6908 # Export global variables to caller
6909 if ($main::export_to_caller) {
6910 my ($callpkg) = caller();
6911 eval "\@${callpkg}::remote_user_info = \@remote_user_info";
6915 =head2 switch_to_unix_user(&user-details)
6917 Switches the current process to the UID and group ID from the given list
6918 of user details, which must be in the format returned by getpwnam.
6921 sub switch_to_unix_user
6924 if (!defined($uinfo->[0])) {
6925 # No username given, so just use given GID
6926 ($(, $)) = ( $uinfo->[3], "$uinfo->[3] $uinfo->[3]" );
6929 # Use all groups from user
6930 ($(, $)) = ( $uinfo->[3],
6931 "$uinfo->[3] ".join(" ", $uinfo->[3],
6932 &other_groups($uinfo->[0])) );
6935 POSIX::setuid($uinfo->[2]);
6937 if ($< != $uinfo->[2] || $> != $uinfo->[2]) {
6938 ($>, $<) = ( $uinfo->[2], $uinfo->[2] );
6942 =head2 eval_as_unix_user(username, &code)
6944 Runs some code fragment with the effective UID and GID switch to that
6945 of the given Unix user, so that file IO takes place with his permissions.
6949 sub eval_as_unix_user
6951 my ($user, $code) = @_;
6952 my @uinfo = getpwnam($user);
6953 if (!scalar(@uinfo)) {
6954 &error("eval_as_unix_user called with invalid user $user");
6956 $) = $uinfo[3]." ".join(" ", $uinfo[3], &other_groups($user));
6960 local $main::error_must_die = 1;
6967 $err =~ s/\s+at\s+(\/\S+)\s+line\s+(\d+)\.?//;
6970 return wantarray ? @rv : $rv[0];
6973 =head2 create_user_config_dirs
6975 Creates per-user config directories and sets $user_config_directory and
6976 $user_module_config_directory to them. Also reads per-user module configs
6977 into %userconfig. This should be called by Usermin module scripts that need
6978 to store per-user preferences or other settings.
6981 sub create_user_config_dirs
6983 return if (!$gconfig{'userconfig'});
6984 my @uinfo = @remote_user_info ? @remote_user_info : getpwnam($remote_user);
6985 return if (!@uinfo || !$uinfo[7]);
6986 &create_missing_homedir(\@uinfo);
6987 $user_config_directory = "$uinfo[7]/$gconfig{'userconfig'}";
6988 if (!-d $user_config_directory) {
6989 mkdir($user_config_directory, 0700) ||
6990 &error("Failed to create $user_config_directory : $!");
6991 if ($< == 0 && $uinfo[2]) {
6992 chown($uinfo[2], $uinfo[3], $user_config_directory);
6995 if (&get_module_name()) {
6996 $user_module_config_directory = $user_config_directory."/".
6998 if (!-d $user_module_config_directory) {
6999 mkdir($user_module_config_directory, 0700) ||
7000 &error("Failed to create $user_module_config_directory : $!");
7001 if ($< == 0 && $uinfo[2]) {
7002 chown($uinfo[2], $uinfo[3], $user_config_directory);
7006 &read_file_cached("$module_root_directory/defaultuconfig",
7008 &read_file_cached("$module_config_directory/uconfig", \%userconfig);
7009 &read_file_cached("$user_module_config_directory/config",
7013 # Export global variables to caller
7014 if ($main::export_to_caller) {
7015 my ($callpkg) = caller();
7016 foreach my $v ('$user_config_directory',
7017 '$user_module_config_directory', '%userconfig') {
7018 my ($vt, $vn) = split('', $v, 2);
7019 eval "${vt}${callpkg}::${vn} = ${vt}${vn}";
7024 =head2 create_missing_homedir(&uinfo)
7026 If auto homedir creation is enabled, create one for this user if needed.
7027 For internal use only.
7030 sub create_missing_homedir
7033 if (!-e $uinfo->[7] && $gconfig{'create_homedir'}) {
7034 # Use has no home dir .. make one
7035 system("mkdir -p ".quotemeta($uinfo->[7]));
7036 chown($uinfo->[2], $uinfo->[3], $uinfo->[7]);
7037 if ($gconfig{'create_homedir_perms'} ne '') {
7038 chmod(oct($gconfig{'create_homedir_perms'}), $uinfo->[7]);
7043 =head2 filter_javascript(text)
7045 Disables all javascript <script>, onClick= and so on tags in the given HTML,
7046 and returns the new HTML. Useful for displaying HTML from an un-trusted source.
7049 sub filter_javascript
7052 $rv =~ s/<\s*script[^>]*>([\000-\377]*?)<\s*\/script\s*>//gi;
7053 $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;
7054 $rv =~ s/(javascript:)/x$1/gi;
7055 $rv =~ s/(vbscript:)/x$1/gi;
7059 =head2 resolve_links(path)
7061 Given a path that may contain symbolic links, returns the real path.
7067 $path =~ s/\/+/\//g;
7068 $path =~ s/\/$// if ($path ne "/");
7069 my @p = split(/\/+/, $path);
7071 for(my $i=0; $i<@p; $i++) {
7072 my $sofar = "/".join("/", @p[0..$i]);
7073 my $lnk = readlink($sofar);
7074 if ($lnk eq $sofar) {
7075 # Link to itself! Cannot do anything more really ..
7078 elsif ($lnk =~ /^\//) {
7079 # Link is absolute..
7080 return &resolve_links($lnk."/".join("/", @p[$i+1 .. $#p]));
7084 return &resolve_links("/".join("/", @p[0..$i-1])."/".$lnk."/".join("/", @p[$i+1 .. $#p]));
7090 =head2 simplify_path(path, bogus)
7092 Given a path, maybe containing elements ".." and "." , convert it to a
7093 clean, absolute form. Returns undef if this is not possible.
7101 my @bits = split(/\/+/, $dir);
7104 foreach my $b (@bits) {
7108 elsif ($b eq "..") {
7110 if (scalar(@fixedbits) == 0) {
7111 # Cannot! Already at root!
7118 push(@fixedbits, $b);
7121 return "/".join('/', @fixedbits);
7124 =head2 same_file(file1, file2)
7126 Returns 1 if two files are actually the same
7131 return 1 if ($_[0] eq $_[1]);
7132 return 0 if ($_[0] !~ /^\// || $_[1] !~ /^\//);
7133 my @stat1 = $stat_cache{$_[0]} ? @{$stat_cache{$_[0]}}
7134 : (@{$stat_cache{$_[0]}} = stat($_[0]));
7135 my @stat2 = $stat_cache{$_[1]} ? @{$stat_cache{$_[1]}}
7136 : (@{$stat_cache{$_[1]}} = stat($_[1]));
7137 return 0 if (!@stat1 || !@stat2);
7138 return $stat1[0] == $stat2[0] && $stat1[1] == $stat2[1];
7141 =head2 flush_webmin_caches
7143 Clears all in-memory and on-disk caches used by Webmin.
7146 sub flush_webmin_caches
7148 undef(%main::read_file_cache);
7149 undef(%main::acl_hash_cache);
7150 undef(%main::acl_array_cache);
7151 undef(%main::has_command_cache);
7152 undef(@main::list_languages_cache);
7153 undef($main::got_list_usermods_cache);
7154 undef(@main::list_usermods_cache);
7155 undef(%main::foreign_installed_cache);
7156 unlink("$config_directory/module.infos.cache");
7157 &get_all_module_infos();
7160 =head2 list_usermods
7162 Returns a list of additional module restrictions. For internal use in
7168 if (!$main::got_list_usermods_cache) {
7169 @main::list_usermods_cache = ( );
7171 open(USERMODS, "$config_directory/usermin.mods");
7173 if (/^([^:]+):(\+|-|):(.*)/) {
7174 push(@main::list_usermods_cache,
7175 [ $1, $2, [ split(/\s+/, $3) ] ]);
7179 $main::got_list_usermods_cache = 1;
7181 return @main::list_usermods_cache;
7184 =head2 available_usermods(&allmods, &usermods)
7186 Returns a list of modules that are available to the given user, based
7187 on usermod additional/subtractions. For internal use by Usermin only.
7190 sub available_usermods
7192 return @{$_[0]} if (!@{$_[1]});
7194 my %mods = map { $_->{'dir'}, 1 } @{$_[0]};
7195 my @uinfo = @remote_user_info;
7196 @uinfo = getpwnam($remote_user) if (!@uinfo);
7197 foreach my $u (@{$_[1]}) {
7199 if ($u->[0] eq "*" || $u->[0] eq $remote_user) {
7202 elsif ($u->[0] =~ /^\@(.*)$/) {
7203 # Check for group membership
7204 my @ginfo = getgrnam($1);
7205 $applies++ if (@ginfo && ($ginfo[2] == $uinfo[3] ||
7206 &indexof($remote_user, split(/\s+/, $ginfo[3])) >= 0));
7208 elsif ($u->[0] =~ /^\//) {
7209 # Check users and groups in file
7211 open(USERFILE, $u->[0]);
7214 if ($_ eq $remote_user) {
7217 elsif (/^\@(.*)$/) {
7218 my @ginfo = getgrnam($1);
7220 if (@ginfo && ($ginfo[2] == $uinfo[3] ||
7221 &indexof($remote_user,
7222 split(/\s+/, $ginfo[3])) >= 0));
7229 if ($u->[1] eq "+") {
7230 map { $mods{$_}++ } @{$u->[2]};
7232 elsif ($u->[1] eq "-") {
7233 map { delete($mods{$_}) } @{$u->[2]};
7237 map { $mods{$_}++ } @{$u->[2]};
7241 return grep { $mods{$_->{'dir'}} } @{$_[0]};
7244 =head2 get_available_module_infos(nocache)
7246 Returns a list of modules available to the current user, based on
7247 operating system support, access control and usermod restrictions. Useful
7248 in themes that need to display a list of modules the user can use.
7249 Each element of the returned array is a hash reference in the same format as
7250 returned by get_module_info.
7253 sub get_available_module_infos
7256 &read_acl(\%acl, \%uacl, [ $base_remote_user ]);
7257 my $risk = $gconfig{'risk_'.$base_remote_user};
7259 foreach my $minfo (&get_all_module_infos($_[0])) {
7260 next if (!&check_os_support($minfo));
7262 # Check module risk level
7263 next if ($risk ne 'high' && $minfo->{'risk'} &&
7264 $minfo->{'risk'} !~ /$risk/);
7268 next if (!$acl{$base_remote_user,$minfo->{'dir'}} &&
7269 !$acl{$base_remote_user,"*"});
7271 next if (&is_readonly_mode() && !$minfo->{'readonly'});
7275 # Check usermod restrictions
7276 my @usermods = &list_usermods();
7277 @rv = sort { $a->{'desc'} cmp $b->{'desc'} }
7278 &available_usermods(\@rv, \@usermods);
7280 # Check RBAC restrictions
7282 foreach my $m (@rv) {
7283 if (&supports_rbac($m->{'dir'}) &&
7284 &use_rbac_module_acl(undef, $m->{'dir'})) {
7285 local $rbacs = &get_rbac_module_acl($remote_user,
7293 # Module or system doesn't support RBAC
7294 push(@rbacrv, $m) if (!$gconfig{'rbacdeny_'.$base_remote_user});
7300 if (defined(&theme_foreign_available)) {
7301 foreach my $m (@rbacrv) {
7302 if (&theme_foreign_available($m->{'dir'})) {
7311 # Check licence module vetos
7313 if ($main::licence_module) {
7314 foreach my $m (@themerv) {
7315 if (&foreign_call($main::licence_module,
7316 "check_module_licence", $m->{'dir'})) {
7328 =head2 get_visible_module_infos(nocache)
7330 Like get_available_module_infos, but excludes hidden modules from the list.
7331 Each element of the returned array is a hash reference in the same format as
7332 returned by get_module_info.
7335 sub get_visible_module_infos
7338 my $pn = &get_product_name();
7339 return grep { !$_->{'hidden'} &&
7340 !$_->{$pn.'_hidden'} } &get_available_module_infos($nocache);
7343 =head2 get_visible_modules_categories(nocache)
7345 Returns a list of Webmin module categories, each of which is a hash ref
7346 with 'code', 'desc' and 'modules' keys. The modules value is an array ref
7347 of modules in the category, in the format returned by get_module_info.
7348 Un-used modules are automatically assigned to the 'unused' category, and
7349 those with no category are put into 'others'.
7352 sub get_visible_modules_categories
7355 my @mods = &get_visible_module_infos($nocache);
7357 if (&get_product_name() eq 'webmin') {
7358 @unmods = grep { $_->{'installed'} eq '0' } @mods;
7359 @mods = grep { $_->{'installed'} ne '0' } @mods;
7361 my %cats = &list_categories(\@mods);
7363 foreach my $c (keys %cats) {
7364 my $cat = { 'code' => $c || 'other',
7365 'desc' => $cats{$c} };
7366 $cat->{'modules'} = [ grep { $_->{'category'} eq $c } @mods ];
7369 @rv = sort { ($b->{'code'} eq "others" ? "" : $b->{'code'}) cmp
7370 ($a->{'code'} eq "others" ? "" : $a->{'code'}) } @rv;
7372 # Add un-installed modules in magic category
7373 my $cat = { 'code' => 'unused',
7374 'desc' => $text{'main_unused'},
7376 'modules' => \@unmods };
7382 =head2 is_under_directory(directory, file)
7384 Returns 1 if the given file is under the specified directory, 0 if not.
7385 Symlinks are taken into account in the file to find it's 'real' location.
7388 sub is_under_directory
7390 my ($dir, $file) = @_;
7391 return 1 if ($dir eq "/");
7392 return 0 if ($file =~ /\.\./);
7393 my $ld = &resolve_links($dir);
7395 return &is_under_directory($ld, $file);
7397 my $lp = &resolve_links($file);
7399 return &is_under_directory($dir, $lp);
7401 return 0 if (length($file) < length($dir));
7402 return 1 if ($dir eq $file);
7404 return substr($file, 0, length($dir)) eq $dir;
7407 =head2 parse_http_url(url, [basehost, baseport, basepage, basessl])
7409 Given an absolute URL, returns the host, port, page and ssl flag components.
7410 Relative URLs can also be parsed, if the base information is provided.
7415 if ($_[0] =~ /^(http|https):\/\/([^:\/]+)(:(\d+))?(\/\S*)?$/) {
7417 my $ssl = $1 eq 'https';
7418 return ($2, $3 ? $4 : $ssl ? 443 : 80, $5 || "/", $ssl);
7424 elsif ($_[0] =~ /^\/\S*$/) {
7425 # A relative to the server URL
7426 return ($_[1], $_[2], $_[0], $_[4]);
7429 # A relative to the directory URL
7431 $page =~ s/[^\/]+$//;
7432 return ($_[1], $_[2], $page.$_[0], $_[4]);
7436 =head2 check_clicks_function
7438 Returns HTML for a JavaScript function called check_clicks that returns
7439 true when first called, but false subsequently. Useful on onClick for
7440 critical buttons. Deprecated, as this method of preventing duplicate actions
7444 sub check_clicks_function
7449 function check_clicks(form)
7456 for(i=0; i<form.length; i++)
7457 form.elements[i].disabled = true;
7466 =head2 load_entities_map
7468 Returns a hash ref containing mappings between HTML entities (like ouml) and
7469 ascii values (like 246). Mainly for internal use.
7472 sub load_entities_map
7474 if (!%entities_map_cache) {
7476 open(EMAP, "$root_directory/entities_map.txt");
7478 if (/^(\d+)\s+(\S+)/) {
7479 $entities_map_cache{$2} = $1;
7484 return \%entities_map_cache;
7487 =head2 entities_to_ascii(string)
7489 Given a string containing HTML entities like ö and 7, replace them
7490 with their ASCII equivalents.
7493 sub entities_to_ascii
7496 my $emap = &load_entities_map();
7497 $str =~ s/&([a-z]+);/chr($emap->{$1})/ge;
7498 $str =~ s/&#(\d+);/chr($1)/ge;
7502 =head2 get_product_name
7504 Returns either 'webmin' or 'usermin', depending on which program the current
7505 module is in. Useful for modules that can be installed into either.
7508 sub get_product_name
7510 return $gconfig{'product'} if (defined($gconfig{'product'}));
7511 return defined($gconfig{'userconfig'}) ? 'usermin' : 'webmin';
7516 Returns the character set for the current language, such as iso-8859-1.
7521 my $charset = defined($gconfig{'charset'}) ? $gconfig{'charset'} :
7522 $current_lang_info->{'charset'} ?
7523 $current_lang_info->{'charset'} : $default_charset;
7527 =head2 get_display_hostname
7529 Returns the system's hostname for UI display purposes. This may be different
7530 from the actual hostname if you administrator has configured it so in the
7531 Webmin Configuration module.
7534 sub get_display_hostname
7536 if ($gconfig{'hostnamemode'} == 0) {
7537 return &get_system_hostname();
7539 elsif ($gconfig{'hostnamemode'} == 3) {
7540 return $gconfig{'hostnamedisplay'};
7543 my $h = $ENV{'HTTP_HOST'};
7545 if ($gconfig{'hostnamemode'} == 2) {
7546 $h =~ s/^(www|ftp|mail)\.//i;
7552 =head2 save_module_config([&config], [modulename])
7554 Saves the configuration for some module. The config parameter is an optional
7555 hash reference of names and values to save, which defaults to the global
7556 %config hash. The modulename parameter is the module to update the config
7557 file, which defaults to the current module.
7560 sub save_module_config
7562 my $c = $_[0] || { &get_module_variable('%config') };
7563 my $m = defined($_[1]) ? $_[1] : &get_module_name();
7564 &write_file("$config_directory/$m/config", $c);
7567 =head2 save_user_module_config([&config], [modulename])
7569 Saves the user's Usermin preferences for some module. The config parameter is
7570 an optional hash reference of names and values to save, which defaults to the
7571 global %userconfig hash. The modulename parameter is the module to update the
7572 config file, which defaults to the current module.
7575 sub save_user_module_config
7577 my $c = $_[0] || { &get_module_variable('%userconfig') };
7578 my $m = $_[1] || &get_module_name();
7579 my $ucd = $user_config_directory;
7581 my @uinfo = @remote_user_info ? @remote_user_info
7582 : getpwnam($remote_user);
7583 return if (!@uinfo || !$uinfo[7]);
7584 $ucd = "$uinfo[7]/$gconfig{'userconfig'}";
7586 &write_file("$ucd/$m/config", $c);
7589 =head2 nice_size(bytes, [min])
7591 Converts a number of bytes into a number followed by a suffix like GB, MB
7592 or kB. Rounding is to two decimal digits. The optional min parameter sets the
7593 smallest units to use - so you could pass 1024*1024 to never show bytes or kB.
7598 my ($units, $uname);
7599 if (abs($_[0]) > 1024*1024*1024*1024 || $_[1] >= 1024*1024*1024*1024) {
7600 $units = 1024*1024*1024*1024;
7603 elsif (abs($_[0]) > 1024*1024*1024 || $_[1] >= 1024*1024*1024) {
7604 $units = 1024*1024*1024;
7607 elsif (abs($_[0]) > 1024*1024 || $_[1] >= 1024*1024) {
7611 elsif (abs($_[0]) > 1024 || $_[1] >= 1024) {
7619 my $sz = sprintf("%.2f", ($_[0]*1.0 / $units));
7621 return $sz." ".$uname;
7624 =head2 get_perl_path
7626 Returns the path to Perl currently in use, such as /usr/bin/perl.
7631 if (open(PERL, "$config_directory/perl-path")) {
7637 return $^X if (-x $^X);
7638 return &has_command("perl");
7641 =head2 get_goto_module([&mods])
7643 Returns the details of a module that the current user should be re-directed
7644 to after logging in, or undef if none. Useful for themes.
7649 my @mods = $_[0] ? @{$_[0]} : &get_visible_module_infos();
7650 if ($gconfig{'gotomodule'}) {
7651 my ($goto) = grep { $_->{'dir'} eq $gconfig{'gotomodule'} } @mods;
7652 return $goto if ($goto);
7654 if (@mods == 1 && $gconfig{'gotoone'}) {
7660 =head2 select_all_link(field, form, [text])
7662 Returns HTML for a 'Select all' link that uses Javascript to select
7663 multiple checkboxes with the same name. The parameters are :
7665 =item field - Name of the checkbox inputs.
7667 =item form - Index of the form on the page.
7669 =item text - Message for the link, defaulting to 'Select all'.
7674 return &theme_select_all_link(@_) if (defined(&theme_select_all_link));
7675 my ($field, $form, $text) = @_;
7677 $text ||= $text{'ui_selall'};
7678 return "<a class='select_all' href='#' onClick='var ff = document.forms[$form].$field; ff.checked = true; for(i=0; i<ff.length; i++) { if (!ff[i].disabled) { ff[i].checked = true; } } return false'>$text</a>";
7681 =head2 select_invert_link(field, form, text)
7683 Returns HTML for an 'Invert selection' link that uses Javascript to invert the
7684 selection on multiple checkboxes with the same name. The parameters are :
7686 =item field - Name of the checkbox inputs.
7688 =item form - Index of the form on the page.
7690 =item text - Message for the link, defaulting to 'Invert selection'.
7693 sub select_invert_link
7695 return &theme_select_invert_link(@_) if (defined(&theme_select_invert_link));
7696 my ($field, $form, $text) = @_;
7698 $text ||= $text{'ui_selinv'};
7699 return "<a class='select_invert' href='#' onClick='var ff = document.forms[$form].$field; ff.checked = !ff.checked; for(i=0; i<ff.length; i++) { if (!ff[i].disabled) { ff[i].checked = !ff[i].checked; } } return false'>$text</a>";
7702 =head2 select_rows_link(field, form, text, &rows)
7704 Returns HTML for a link that uses Javascript to select rows with particular
7705 values for their checkboxes. The parameters are :
7707 =item field - Name of the checkbox inputs.
7709 =item form - Index of the form on the page.
7711 =item text - Message for the link, de
7713 =item rows - Reference to an array of 1 or 0 values, indicating which rows to check.
7716 sub select_rows_link
7718 return &theme_select_rows_link(@_) if (defined(&theme_select_rows_link));
7719 my ($field, $form, $text, $rows) = @_;
7721 my $js = "var sel = { ".join(",", map { "\""."e_escape($_)."\":1" } @$rows)." }; ";
7722 $js .= "for(var i=0; i<document.forms[$form].${field}.length; i++) { var r = document.forms[$form].${field}[i]; r.checked = sel[r.value]; } ";
7723 $js .= "return false;";
7724 return "<a href='#' onClick='$js'>$text</a>";
7727 =head2 check_pid_file(file)
7729 Given a pid file, returns the PID it contains if the process is running.
7734 open(PIDFILE, $_[0]) || return undef;
7735 my $pid = <PIDFILE>;
7737 $pid =~ /^\s*(\d+)/ || return undef;
7738 kill(0, $1) || return undef;
7744 Return the local os-specific library name to this module. For internal use only.
7749 my $mn = &get_module_name();
7750 my $md = &module_root_directory($mn);
7751 if (-r "$md/$mn-$gconfig{'os_type'}-$gconfig{'os_version'}-lib.pl") {
7752 return "$mn-$gconfig{'os_type'}-$gconfig{'os_version'}-lib.pl";
7754 elsif (-r "$md/$mn-$gconfig{'os_type'}-lib.pl") {
7755 return "$mn-$gconfig{'os_type'}-lib.pl";
7757 elsif (-r "$md/$mn-generic-lib.pl") {
7758 return "$mn-generic-lib.pl";
7765 =head2 module_root_directory(module)
7767 Given a module name, returns its root directory. On a typical Webmin install,
7768 all modules are under the same directory - but it is theoretically possible to
7772 sub module_root_directory
7774 my $d = ref($_[0]) ? $_[0]->{'dir'} : $_[0];
7775 if (@root_directories > 1) {
7776 foreach my $r (@root_directories) {
7782 return "$root_directories[0]/$d";
7785 =head2 list_mime_types
7787 Returns a list of all known MIME types and their extensions, as a list of hash
7788 references with keys :
7790 =item type - The MIME type, like text/plain.
7792 =item exts - A list of extensions, like .doc and .avi.
7794 =item desc - A human-readable description for the MIME type.
7799 if (!@list_mime_types_cache) {
7801 open(MIME, "$root_directory/mime.types");
7805 if (s/#\s*(.*)$//g) {
7808 my ($type, @exts) = split(/\s+/);
7810 push(@list_mime_types_cache, { 'type' => $type,
7817 return @list_mime_types_cache;
7820 =head2 guess_mime_type(filename, [default])
7822 Given a file name like xxx.gif or foo.html, returns a guessed MIME type.
7823 The optional default parameter sets a default type of use if none is found,
7824 which defaults to application/octet-stream.
7829 if ($_[0] =~ /\.([A-Za-z0-9\-]+)$/) {
7831 foreach my $t (&list_mime_types()) {
7832 foreach my $e (@{$t->{'exts'}}) {
7833 return $t->{'type'} if (lc($e) eq lc($ext));
7837 return @_ > 1 ? $_[1] : "application/octet-stream";
7840 =head2 open_tempfile([handle], file, [no-error], [no-tempfile], [safe?])
7842 Opens a file handle for writing to a temporary file, which will only be
7843 renamed over the real file when the handle is closed. This allows critical
7844 files like /etc/shadow to be updated safely, even if writing fails part way
7845 through due to lack of disk space. The parameters are :
7847 =item handle - File handle to open, as you would use in Perl's open function.
7849 =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.
7851 =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.
7853 =item no-tempfile - If set to 1, writing will be direct to the file instead of using a temporary file.
7855 =item safe - Indicates to users in read-only mode that this write is safe and non-destructive.
7861 # Just getting a temp file
7862 if (!defined($main::open_tempfiles{$_[0]})) {
7863 $_[0] =~ /^(.*)\/(.*)$/ || return $_[0];
7864 my $dir = $1 || "/";
7865 my $tmp = "$dir/$2.webmintmp.$$";
7866 $main::open_tempfiles{$_[0]} = $tmp;
7867 push(@main::temporary_files, $tmp);
7869 return $main::open_tempfiles{$_[0]};
7873 my ($fh, $file, $noerror, $notemp, $safe) = @_;
7874 $fh = &callers_package($fh);
7876 my %gaccess = &get_module_acl(undef, "");
7877 my $db = $gconfig{'debug_what_write'};
7878 if ($file =~ /\r|\n|\0/) {
7879 if ($noerror) { return 0; }
7880 else { &error("Filename contains invalid characters"); }
7882 if (&is_readonly_mode() && $file =~ />/ && !$safe) {
7883 # Read-only mode .. veto all writes
7884 print STDERR "vetoing write to $file\n";
7885 return open($fh, ">$null_file");
7887 elsif ($file =~ /^(>|>>|)nul$/i) {
7888 # Write to Windows null device
7889 &webmin_debug_log($1 eq ">" ? "WRITE" :
7890 $1 eq ">>" ? "APPEND" : "READ", "nul") if ($db);
7892 elsif ($file =~ /^(>|>>)(\/dev\/.*)/ || lc($file) eq "nul") {
7893 # Writes to /dev/null or TTYs don't need to be handled
7894 &webmin_debug_log($1 eq ">" ? "WRITE" : "APPEND", $2) if ($db);
7895 return open($fh, $file);
7897 elsif ($file =~ /^>\s*(([a-zA-Z]:)?\/.*)$/ && !$notemp) {
7898 &webmin_debug_log("WRITE", $1) if ($db);
7899 # Over-writing a file, via a temp file
7901 $file = &translate_filename($file);
7903 # Open the link target instead
7904 $file = &resolve_links($file);
7907 # Cannot open a directory!
7908 if ($noerror) { return 0; }
7909 else { &error("Cannot write to directory $file"); }
7911 my $tmp = &open_tempfile($file);
7912 my $ex = open($fh, ">$tmp");
7913 if (!$ex && $! =~ /permission/i) {
7914 # Could not open temp file .. try opening actual file
7916 $ex = open($fh, ">$file");
7917 delete($main::open_tempfiles{$file});
7920 $main::open_temphandles{$fh} = $file;
7923 if (!$ex && !$noerror) {
7924 &error(&text("efileopen", $file, $!));
7928 elsif ($file =~ /^>\s*(([a-zA-Z]:)?\/.*)$/ && $notemp) {
7929 # Just writing direct to a file
7930 &webmin_debug_log("WRITE", $1) if ($db);
7932 $file = &translate_filename($file);
7933 my @old_attributes = &get_clear_file_attributes($file);
7934 my $ex = open($fh, ">$file");
7935 &reset_file_attributes($file, \@old_attributes);
7936 $main::open_temphandles{$fh} = $file;
7937 if (!$ex && !$noerror) {
7938 &error(&text("efileopen", $file, $!));
7943 elsif ($file =~ /^>>\s*(([a-zA-Z]:)?\/.*)$/) {
7944 # Appending to a file .. nothing special to do
7945 &webmin_debug_log("APPEND", $1) if ($db);
7947 $file = &translate_filename($file);
7948 my @old_attributes = &get_clear_file_attributes($file);
7949 my $ex = open($fh, ">>$file");
7950 &reset_file_attributes($file, \@old_attributes);
7951 $main::open_temphandles{$fh} = $file;
7952 if (!$ex && !$noerror) {
7953 &error(&text("efileopen", $file, $!));
7958 elsif ($file =~ /^([a-zA-Z]:)?\//) {
7959 # Read mode .. nothing to do here
7960 &webmin_debug_log("READ", $file) if ($db);
7961 $file = &translate_filename($file);
7962 return open($fh, $file);
7964 elsif ($file eq ">" || $file eq ">>") {
7965 my ($package, $filename, $line) = caller;
7966 if ($noerror) { return 0; }
7967 else { &error("Missing file to open at ${package}::${filename} line $line"); }
7970 my ($package, $filename, $line) = caller;
7971 &error("Unsupported file or mode $file at ${package}::${filename} line $line");
7976 =head2 close_tempfile(file|handle)
7978 Copies a temp file to the actual file, assuming that all writes were
7979 successful. The handle must have been one passed to open_tempfile.
7985 my $fh = &callers_package($_[0]);
7987 if (defined($file = $main::open_temphandles{$fh})) {
7989 close($fh) || &error(&text("efileclose", $file, $!));
7990 delete($main::open_temphandles{$fh});
7991 return &close_tempfile($file);
7993 elsif (defined($main::open_tempfiles{$_[0]})) {
7995 &webmin_debug_log("CLOSE", $_[0]) if ($gconfig{'debug_what_write'});
7996 my @st = stat($_[0]);
7997 if (&is_selinux_enabled() && &has_command("chcon")) {
7998 # Set original security context
7999 system("chcon --reference=".quotemeta($_[0]).
8000 " ".quotemeta($main::open_tempfiles{$_[0]}).
8001 " >/dev/null 2>&1");
8003 my @old_attributes = &get_clear_file_attributes($_[0]);
8004 rename($main::open_tempfiles{$_[0]}, $_[0]) || &error("Failed to replace $_[0] with $main::open_tempfiles{$_[0]} : $!");
8006 # Set original permissions and ownership
8007 chmod($st[2], $_[0]);
8008 chown($st[4], $st[5], $_[0]);
8010 &reset_file_attributes($_[0], \@old_attributes);
8011 delete($main::open_tempfiles{$_[0]});
8012 @main::temporary_files = grep { $_ ne $main::open_tempfiles{$_[0]} } @main::temporary_files;
8013 if ($main::open_templocks{$_[0]}) {
8014 &unlock_file($_[0]);
8015 delete($main::open_templocks{$_[0]});
8020 # Must be closing a handle not associated with a file
8026 =head2 print_tempfile(handle, text, ...)
8028 Like the normal print function, but calls &error on failure. Useful when
8029 combined with open_tempfile, to ensure that a criticial file is never
8030 only partially written.
8035 my ($fh, @args) = @_;
8036 $fh = &callers_package($fh);
8037 (print $fh @args) || &error(&text("efilewrite",
8038 $main::open_temphandles{$fh} || $fh, $!));
8041 =head2 is_selinux_enabled
8043 Returns 1 if SElinux is supported on this system and enabled, 0 if not.
8046 sub is_selinux_enabled
8048 if (!defined($main::selinux_enabled_cache)) {
8050 if ($gconfig{'os_type'} !~ /-linux$/) {
8051 # Not on linux, so no way
8052 $main::selinux_enabled_cache = 0;
8054 elsif (&read_env_file("/etc/selinux/config", \%seconfig)) {
8055 # Use global config file
8056 $main::selinux_enabled_cache =
8057 $seconfig{'SELINUX'} eq 'disabled' ||
8058 !$seconfig{'SELINUX'} ? 0 : 1;
8061 # Use selinuxenabled command
8062 #$selinux_enabled_cache =
8063 # system("selinuxenabled >/dev/null 2>&1") ? 0 : 1;
8064 $main::selinux_enabled_cache = 0;
8067 return $main::selinux_enabled_cache;
8070 =head2 get_clear_file_attributes(file)
8072 Finds file attributes that may prevent writing, clears them and returns them
8073 as a list. May call error. Mainly for internal use by open_tempfile and
8077 sub get_clear_file_attributes
8081 if ($gconfig{'chattr'}) {
8082 # Get original immutable bit
8083 my $out = &backquote_command(
8084 "lsattr ".quotemeta($file)." 2>/dev/null");
8086 $out =~ s/\s\S+\n//;
8087 @old_attributes = grep { $_ ne '-' } split(//, $out);
8089 if (&indexof("i", @old_attributes) >= 0) {
8090 my $err = &backquote_logged(
8091 "chattr -i ".quotemeta($file)." 2>&1");
8093 &error("Failed to remove immutable bit on ".
8098 return @old_attributes;
8101 =head2 reset_file_attributes(file, &attributes)
8103 Put back cleared attributes on some file. May call error. Mainly for internal
8104 use by close_tempfile.
8107 sub reset_file_attributes
8109 my ($file, $old_attributes) = @_;
8110 if (&indexof("i", @$old_attributes) >= 0) {
8111 my $err = &backquote_logged(
8112 "chattr +i ".quotemeta($file)." 2>&1");
8114 &error("Failed to restore immutable bit on ".
8120 =head2 cleanup_tempnames
8122 Remove all temporary files generated using transname. Typically only called
8123 internally when a Webmin script exits.
8126 sub cleanup_tempnames
8128 foreach my $t (@main::temporary_files) {
8131 @main::temporary_files = ( );
8134 =head2 open_lock_tempfile([handle], file, [no-error])
8136 Returns a temporary file for writing to some actual file, and also locks it.
8137 Effectively the same as calling lock_file and open_tempfile on the same file,
8138 but calls the unlock for you automatically when it is closed.
8141 sub open_lock_tempfile
8143 my ($fh, $file, $noerror, $notemp, $safe) = @_;
8144 $fh = &callers_package($fh);
8145 my $lockfile = $file;
8146 $lockfile =~ s/^[^\/]*//;
8147 if ($lockfile =~ /^\//) {
8148 $main::open_templocks{$lockfile} = &lock_file($lockfile);
8150 return &open_tempfile($fh, $file, $noerror, $notemp, $safe);
8155 $main::end_exit_status ||= $?;
8156 if ($$ == $main::initial_process_id) {
8157 # Exiting from initial process
8158 &cleanup_tempnames();
8159 if ($gconfig{'debug_what_start'} && $main::debug_log_start_time &&
8160 $main::debug_log_start_module eq &get_module_name()) {
8161 my $len = time() - $main::debug_log_start_time;
8162 &webmin_debug_log("STOP", "runtime=$len");
8163 $main::debug_log_start_time = 0;
8165 if (!$ENV{'SCRIPT_NAME'} &&
8166 $main::initial_module_name eq &get_module_name()) {
8167 # In a command-line script - call the real exit, so that the
8168 # exit status gets properly propogated. In some cases this
8169 # was not happening.
8170 exit($main::end_exit_status);
8175 =head2 month_to_number(month)
8177 Converts a month name like feb to a number like 1.
8182 return $month_to_number_map{lc(substr($_[0], 0, 3))};
8185 =head2 number_to_month(number)
8187 Converts a number like 1 to a month name like Feb.
8192 return ucfirst($number_to_month_map{$_[0]});
8195 =head2 get_rbac_module_acl(user, module)
8197 Returns a hash reference of RBAC overrides ACLs for some user and module.
8198 May return undef if none exist (indicating access denied), or the string *
8199 if full access is granted.
8202 sub get_rbac_module_acl
8204 my ($user, $mod) = @_;
8205 eval "use Authen::SolarisRBAC";
8206 return undef if ($@);
8209 if (Authen::SolarisRBAC::chkauth("webmin.$mod.admin", $user)) {
8210 # Automagic webmin.modulename.admin authorization exists .. allow access
8212 if (!Authen::SolarisRBAC::chkauth("webmin.$mod.config", $user)) {
8213 %rv = ( 'noconfig' => 1 );
8220 open(RBAC, &module_root_directory($mod)."/rbac-mapping");
8224 my ($auths, $acls) = split(/\s+/, $_);
8225 my @auths = split(/,/, $auths);
8227 my ($merge) = ($acls =~ s/^\+//);
8229 if ($auths eq "*") {
8230 # These ACLs apply to all RBAC users.
8231 # Only if there is some that match a specific authorization
8232 # later will they be used though.
8235 # Check each of the RBAC authorizations
8236 foreach my $a (@auths) {
8237 if (!Authen::SolarisRBAC::chkauth($a, $user)) {
8242 $foundany++ if ($gotall);
8245 # Found an RBAC authorization - return the ACLs
8246 return "*" if ($acls eq "*");
8247 my %acl = map { split(/=/, $_, 2) } split(/,/, $acls);
8249 # Just add to current set
8250 foreach my $a (keys %acl) {
8261 return !$foundany ? undef : %rv ? \%rv : undef;
8264 =head2 supports_rbac([module])
8266 Returns 1 if RBAC client support is available, such as on Solaris.
8271 return 0 if ($gconfig{'os_type'} ne 'solaris');
8272 eval "use Authen::SolarisRBAC";
8275 #return 0 if (!-r &module_root_directory($_[0])."/rbac-mapping");
8280 =head2 supports_ipv6()
8282 Returns 1 if outgoing IPv6 connections can be made
8287 return $ipv6_module_error ? 0 : 1;
8290 =head2 use_rbac_module_acl(user, module)
8292 Returns 1 if some user should use RBAC to get permissions for a module
8295 sub use_rbac_module_acl
8297 my $u = defined($_[0]) ? $_[0] : $base_remote_user;
8298 my $m = defined($_[1]) ? $_[1] : &get_module_name();
8299 return 1 if ($gconfig{'rbacdeny_'.$u}); # RBAC forced for user
8300 my %access = &get_module_acl($u, $m, 1);
8301 return $access{'rbac'} ? 1 : 0;
8304 =head2 execute_command(command, stdin, stdout, stderr, translate-files?, safe?)
8306 Runs some command, possibly feeding it input and capturing output to the
8307 give files or scalar references. The parameters are :
8309 =item command - Full command to run, possibly including shell meta-characters.
8311 =item stdin - File to read input from, or a scalar ref containing input, or undef if no input should be given.
8313 =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.
8315 =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.
8317 =item translate-files - Set to 1 to apply filename translation to any filenames. Usually has no effect.
8319 =item safe - Set to 1 if this command is safe and does not modify the state of the system.
8324 my ($cmd, $stdin, $stdout, $stderr, $trans, $safe) = @_;
8325 if (&is_readonly_mode() && !$safe) {
8326 print STDERR "Vetoing command $_[0]\n";
8330 $cmd = &translate_command($cmd);
8332 # Use ` operator where possible
8333 &webmin_debug_log('CMD', "cmd=$cmd") if ($gconfig{'debug_what_cmd'});
8334 if (!$stdin && ref($stdout) && !$stderr) {
8335 $cmd = "($cmd)" if ($gconfig{'os_type'} ne 'windows');
8336 $$stdout = `$cmd 2>$null_file`;
8339 elsif (!$stdin && ref($stdout) && $stdout eq $stderr) {
8340 $cmd = "($cmd)" if ($gconfig{'os_type'} ne 'windows');
8341 $$stdout = `$cmd 2>&1`;
8344 elsif (!$stdin && !$stdout && !$stderr) {
8345 $cmd = "($cmd)" if ($gconfig{'os_type'} ne 'windows');
8346 return system("$cmd >$null_file 2>$null_file <$null_file");
8350 $| = 1; # needed on some systems to flush before forking
8351 pipe(EXECSTDINr, EXECSTDINw);
8352 pipe(EXECSTDOUTr, EXECSTDOUTw);
8353 pipe(EXECSTDERRr, EXECSTDERRw);
8355 if (!($pid = fork())) {
8359 open(STDIN, "<&EXECSTDINr");
8360 open(STDOUT, ">&EXECSTDOUTw");
8361 if (ref($stderr) && $stderr eq $stdout) {
8362 open(STDERR, ">&EXECSTDOUTw");
8365 open(STDERR, ">&EXECSTDERRw");
8372 my $fullcmd = "($cmd)";
8373 if ($stdin && !ref($stdin)) {
8374 $fullcmd .= " <$stdin";
8376 if ($stdout && !ref($stdout)) {
8377 $fullcmd .= " >$stdout";
8379 if ($stderr && !ref($stderr)) {
8380 if ($stderr eq $stdout) {
8381 $fullcmd .= " 2>&1";
8384 $fullcmd .= " 2>$stderr";
8387 if ($gconfig{'os_type'} eq 'windows') {
8391 exec("/bin/sh", "-c", $fullcmd);
8393 print "Exec failed : $!\n";
8400 # Feed input and capture output
8402 if ($stdin && ref($stdin)) {
8403 print EXECSTDINw $$stdin;
8406 if ($stdout && ref($stdout)) {
8408 while(<EXECSTDOUTr>) {
8413 if ($stderr && ref($stderr) && $stderr ne $stdout) {
8415 while(<EXECSTDERRr>) {
8426 =head2 open_readfile(handle, file)
8428 Opens some file for reading. Returns 1 on success, 0 on failure. Pretty much
8429 exactly the same as Perl's open function.
8434 my ($fh, $file) = @_;
8435 $fh = &callers_package($fh);
8436 my $realfile = &translate_filename($file);
8437 &webmin_debug_log('READ', $file) if ($gconfig{'debug_what_read'});
8438 return open($fh, "<".$realfile);
8441 =head2 open_execute_command(handle, command, output?, safe?)
8443 Runs some command, with the specified file handle set to either write to it if
8444 in-or-out is set to 0, or read to it if output is set to 1. The safe flag
8445 indicates if the command modifies the state of the system or not.
8448 sub open_execute_command
8450 my ($fh, $cmd, $mode, $safe) = @_;
8451 $fh = &callers_package($fh);
8452 my $realcmd = &translate_command($cmd);
8453 if (&is_readonly_mode() && !$safe) {
8454 # Don't actually run it
8455 print STDERR "vetoing command $cmd\n";
8458 return open($fh, ">$null_file");
8461 return open($fh, $null_file);
8465 &webmin_debug_log('CMD', "cmd=$realcmd mode=$mode")
8466 if ($gconfig{'debug_what_cmd'});
8468 return open($fh, "| $cmd");
8470 elsif ($mode == 1) {
8471 return open($fh, "$cmd 2>$null_file |");
8473 elsif ($mode == 2) {
8474 return open($fh, "$cmd 2>&1 |");
8478 =head2 translate_filename(filename)
8480 Applies all relevant registered translation functions to a filename. Mostly
8481 for internal use, and typically does nothing.
8484 sub translate_filename
8486 my ($realfile) = @_;
8487 my @funcs = grep { $_->[0] eq &get_module_name() ||
8488 !defined($_->[0]) } @main::filename_callbacks;
8489 foreach my $f (@funcs) {
8491 $realfile = &$func($realfile, @{$f->[2]});
8496 =head2 translate_command(filename)
8498 Applies all relevant registered translation functions to a command. Mostly
8499 for internal use, and typically does nothing.
8502 sub translate_command
8505 my @funcs = grep { $_->[0] eq &get_module_name() ||
8506 !defined($_->[0]) } @main::command_callbacks;
8507 foreach my $f (@funcs) {
8509 $realcmd = &$func($realcmd, @{$f->[2]});
8514 =head2 register_filename_callback(module|undef, &function, &args)
8516 Registers some function to be called when the specified module (or all
8517 modules) tries to open a file for reading and writing. The function must
8518 return the actual file to open. This allows you to override which files
8519 other code actually operates on, via the translate_filename function.
8522 sub register_filename_callback
8524 my ($mod, $func, $args) = @_;
8525 push(@main::filename_callbacks, [ $mod, $func, $args ]);
8528 =head2 register_command_callback(module|undef, &function, &args)
8530 Registers some function to be called when the specified module (or all
8531 modules) tries to execute a command. The function must return the actual
8532 command to run. This allows you to override which commands other other code
8533 actually runs, via the translate_command function.
8536 sub register_command_callback
8538 my ($mod, $func, $args) = @_;
8539 push(@main::command_callbacks, [ $mod, $func, $args ]);
8542 =head2 capture_function_output(&function, arg, ...)
8544 Captures output that some function prints to STDOUT, and returns it. Useful
8545 for functions outside your control that print data when you really want to
8546 manipulate it before output.
8549 sub capture_function_output
8551 my ($func, @args) = @_;
8552 socketpair(SOCKET2, SOCKET1, AF_UNIX, SOCK_STREAM, PF_UNSPEC);
8553 my $old = select(SOCKET1);
8554 my @rv = &$func(@args);
8563 return wantarray ? ($out, \@rv) : $out;
8566 =head2 capture_function_output_tempfile(&function, arg, ...)
8568 Behaves the same as capture_function_output, but uses a temporary file
8569 to avoid buffer full problems.
8572 sub capture_function_output_tempfile
8574 my ($func, @args) = @_;
8575 my $temp = &transname();
8576 open(BUFFER, ">$temp");
8577 my $old = select(BUFFER);
8578 my @rv = &$func(@args);
8581 my $out = &read_file_contents($temp);
8582 &unlink_file($temp);
8583 return wantarray ? ($out, \@rv) : $out;
8586 =head2 modules_chooser_button(field, multiple, [form])
8588 Returns HTML for a button for selecting one or many Webmin modules.
8589 field - Name of the HTML field to place the module names into.
8590 multiple - Set to 1 if multiple modules can be selected.
8591 form - Index of the form on the page.
8594 sub modules_chooser_button
8596 return &theme_modules_chooser_button(@_)
8597 if (defined(&theme_modules_chooser_button));
8598 my $form = defined($_[2]) ? $_[2] : 0;
8599 my $w = $_[1] ? 700 : 500;
8601 if ($_[1] && $gconfig{'db_sizemodules'}) {
8602 ($w, $h) = split(/x/, $gconfig{'db_sizemodules'});
8604 elsif (!$_[1] && $gconfig{'db_sizemodule'}) {
8605 ($w, $h) = split(/x/, $gconfig{'db_sizemodule'});
8607 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";
8610 =head2 substitute_template(text, &hash)
8612 Given some text and a hash reference, for each ocurrance of $FOO or ${FOO} in
8613 the text replaces it with the value of the hash key foo. Also supports blocks
8614 like ${IF-FOO} ... ${ENDIF-FOO}, whose contents are only included if foo is
8615 non-zero, and ${IF-FOO} ... ${ELSE-FOO} ... ${ENDIF-FOO}.
8618 sub substitute_template
8620 # Add some extra fixed parameters to the hash
8621 my %hash = %{$_[1]};
8622 $hash{'hostname'} = &get_system_hostname();
8623 $hash{'webmin_config'} = $config_directory;
8624 $hash{'webmin_etc'} = $config_directory;
8625 $hash{'module_config'} = &get_module_variable('$module_config_directory');
8626 $hash{'webmin_var'} = $var_directory;
8628 # Add time-based parameters, for use in DNS
8629 $hash{'current_time'} = time();
8630 my @tm = localtime($hash{'current_time'});
8631 $hash{'current_year'} = $tm[5]+1900;
8632 $hash{'current_month'} = sprintf("%2.2d", $tm[4]+1);
8633 $hash{'current_day'} = sprintf("%2.2d", $tm[3]);
8634 $hash{'current_hour'} = sprintf("%2.2d", $tm[2]);
8635 $hash{'current_minute'} = sprintf("%2.2d", $tm[1]);
8636 $hash{'current_second'} = sprintf("%2.2d", $tm[0]);
8638 # Actually do the substition
8640 foreach my $s (keys %hash) {
8641 next if ($s eq ''); # Prevent just $ from being subbed
8644 $rv =~ s/\$\{\Q$us\E\}/$sv/g;
8645 $rv =~ s/\$\Q$us\E/$sv/g;
8647 # Replace ${IF}..${ELSE}..${ENDIF} block with first value,
8648 # and ${IF}..${ENDIF} with value
8649 $rv =~ s/\$\{IF-\Q$us\E\}(\n?)([\000-\377]*?)\$\{ELSE-\Q$us\E\}(\n?)([\000-\377]*?)\$\{ENDIF-\Q$us\E\}(\n?)/$2/g;
8650 $rv =~ s/\$\{IF-\Q$us\E\}(\n?)([\000-\377]*?)\$\{ENDIF-\Q$us\E\}(\n?)/$2/g;
8652 # Replace $IF..$ELSE..$ENDIF block with first value,
8653 # and $IF..$ENDIF with value
8654 $rv =~ s/\$IF-\Q$us\E(\n?)([\000-\377]*?)\$ELSE-\Q$us\E(\n?)([\000-\377]*?)\$ENDIF-\Q$us\E(\n?)/$2/g;
8655 $rv =~ s/\$IF-\Q$us\E(\n?)([\000-\377]*?)\$ENDIF-\Q$us\E(\n?)/$2/g;
8657 # Replace ${IFEQ}..${ENDIFEQ} block with first value if
8658 # matching, nothing if not
8659 $rv =~ s/\$\{IFEQ-\Q$us\E-\Q$sv\E\}(\n?)([\000-\377]*?)\$\{ENDIFEQ-\Q$us\E-\Q$sv\E\}(\n?)/$2/g;
8660 $rv =~ s/\$\{IFEQ-\Q$us\E-[^\}]+}(\n?)([\000-\377]*?)\$\{ENDIFEQ-\Q$us\E-[^\}]+\}(\n?)//g;
8662 # Replace $IFEQ..$ENDIFEQ block with first value if
8663 # matching, nothing if not
8664 $rv =~ s/\$IFEQ-\Q$us\E-\Q$sv\E(\n?)([\000-\377]*?)\$ENDIFEQ-\Q$us\E-\Q$sv\E(\n?)/$2/g;
8665 $rv =~ s/\$IFEQ-\Q$us\E-\S+(\n?)([\000-\377]*?)\$ENDIFEQ-\Q$us\E-\S+(\n?)//g;
8668 # Replace ${IF}..${ELSE}..${ENDIF} block with second value,
8669 # and ${IF}..${ENDIF} with nothing
8670 $rv =~ s/\$\{IF-\Q$us\E\}(\n?)([\000-\377]*?)\$\{ELSE-\Q$us\E\}(\n?)([\000-\377]*?)\$\{ENDIF-\Q$us\E\}(\n?)/$4/g;
8671 $rv =~ s/\$\{IF-\Q$us\E\}(\n?)([\000-\377]*?)\$\{ENDIF-\Q$us\E\}(\n?)//g;
8673 # Replace $IF..$ELSE..$ENDIF block with second value,
8674 # and $IF..$ENDIF with nothing
8675 $rv =~ s/\$IF-\Q$us\E(\n?)([\000-\377]*?)\$ELSE-\Q$us\E(\n?)([\000-\377]*?)\$ENDIF-\Q$us\E(\n?)/$4/g;
8676 $rv =~ s/\$IF-\Q$us\E(\n?)([\000-\377]*?)\$ENDIF-\Q$us\E(\n?)//g;
8678 # Replace ${IFEQ}..${ENDIFEQ} block with nothing
8679 $rv =~ s/\$\{IFEQ-\Q$us\E-[^\}]+}(\n?)([\000-\377]*?)\$\{ENDIFEQ-\Q$us\E-[^\}]+\}(\n?)//g;
8680 $rv =~ s/\$IFEQ-\Q$us\E-\S+(\n?)([\000-\377]*?)\$ENDIFEQ-\Q$us\E-\S+(\n?)//g;
8684 # Now assume any $IF blocks whose variables are not present in the hash
8685 # evaluate to false.
8686 # $IF...$ELSE x $ENDIF => x
8687 $rv =~ s/\$\{IF\-([A-Z]+)\}.*?\$\{ELSE\-\1\}(.*?)\$\{ENDIF\-\1\}/$2/gs;
8688 # $IF...x...$ENDIF => (nothing)
8689 $rv =~ s/\$\{IF\-([A-Z]+)\}.*?\$\{ENDIF\-\1\}//gs;
8690 # ${var} => (nothing)
8691 $rv =~ s/\$\{[A-Z]+\}//g;
8696 =head2 running_in_zone
8698 Returns 1 if the current Webmin instance is running in a Solaris zone. Used to
8699 disable module and features that are not appropriate, like those that modify
8700 mounted filesystems.
8705 return 0 if ($gconfig{'os_type'} ne 'solaris' ||
8706 $gconfig{'os_version'} < 10);
8707 my $zn = `zonename 2>$null_file`;
8709 return $zn && $zn ne "global";
8712 =head2 running_in_vserver
8714 Returns 1 if the current Webmin instance is running in a Linux VServer.
8715 Used to disable modules and features that are not appropriate.
8718 sub running_in_vserver
8720 return 0 if ($gconfig{'os_type'} !~ /^\*-linux$/);
8723 open(MTAB, "/etc/mtab");
8725 my ($dev, $mp) = split(/\s+/, $_);
8726 if ($mp eq "/" && $dev =~ /^\/dev\/hdv/) {
8735 =head2 running_in_xen
8737 Returns 1 if Webmin is running inside a Xen instance, by looking
8738 at /proc/xen/capabilities.
8743 return 0 if (!-r "/proc/xen/capabilities");
8744 my $cap = &read_file_contents("/proc/xen/capabilities");
8745 return $cap =~ /control_d/ ? 0 : 1;
8748 =head2 running_in_openvz
8750 Returns 1 if Webmin is running inside an OpenVZ container, by looking
8751 at /proc/vz/veinfo for a non-zero line.
8754 sub running_in_openvz
8756 return 0 if (!-r "/proc/vz/veinfo");
8757 my $lref = &read_file_lines("/proc/vz/veinfo", 1);
8758 return 0 if (!$lref || !@$lref);
8759 foreach my $l (@$lref) {
8761 my @ll = split(/\s+/, $l);
8762 return 0 if ($ll[0] eq '0');
8767 =head2 list_categories(&modules, [include-empty])
8769 Returns a hash mapping category codes to names, including any custom-defined
8770 categories. The modules parameter must be an array ref of module hash objects,
8771 as returned by get_all_module_infos.
8776 my ($mods, $empty) = @_;
8777 my (%cats, %catnames);
8778 &read_file("$config_directory/webmin.catnames", \%catnames);
8779 foreach my $o (@lang_order_list) {
8780 &read_file("$config_directory/webmin.catnames.$o", \%catnames);
8785 foreach my $m (@$mods) {
8786 my $c = $m->{'category'};
8787 next if ($cats{$c});
8788 if (defined($catnames{$c})) {
8789 $cats{$c} = $catnames{$c};
8791 elsif ($text{"category_$c"}) {
8792 $cats{$c} = $text{"category_$c"};
8795 # try to get category name from module ..
8796 my %mtext = &load_language($m->{'dir'});
8797 if ($mtext{"category_$c"}) {
8798 $cats{$c} = $mtext{"category_$c"};
8801 $c = $m->{'category'} = "";
8802 $cats{$c} = $text{"category_$c"};
8809 =head2 is_readonly_mode
8811 Returns 1 if the current user is in read-only mode, and thus all writes
8812 to files and command execution should fail.
8815 sub is_readonly_mode
8817 if (!defined($main::readonly_mode_cache)) {
8818 my %gaccess = &get_module_acl(undef, "");
8819 $main::readonly_mode_cache = $gaccess{'readonly'} ? 1 : 0;
8821 return $main::readonly_mode_cache;
8824 =head2 command_as_user(user, with-env?, command, ...)
8826 Returns a command to execute some command as the given user, using the
8827 su statement. If on Linux, the /bin/sh shell is forced in case the user
8828 does not have a valid shell. If with-env is set to 1, the -s flag is added
8829 to the su command to read the user's .profile or .bashrc file.
8834 my ($user, $env, @args) = @_;
8835 my @uinfo = getpwnam($user);
8836 if ($uinfo[8] ne "/bin/sh" && $uinfo[8] !~ /\/bash$/) {
8837 # User shell doesn't appear to be valid
8838 if ($gconfig{'os_type'} =~ /-linux$/) {
8839 # Use -s /bin/sh to force it
8840 $shellarg = " -s /bin/sh";
8842 elsif ($gconfig{'os_type'} eq 'freebsd' ||
8843 $gconfig{'os_type'} eq 'solaris' &&
8844 $gconfig{'os_version'} >= 11 ||
8845 $gconfig{'os_type'} eq 'macos') {
8846 # Use -m and force /bin/sh
8847 @args = ( "/bin/sh", "-c", quotemeta(join(" ", @args)) );
8851 my $rv = "su".($env ? " -" : "").$shellarg.
8852 " ".quotemeta($user)." -c ".quotemeta(join(" ", @args));
8856 =head2 list_osdn_mirrors(project, file)
8858 This function is now deprecated in favor of letting sourceforge just
8859 redirect to the best mirror, and now just returns their primary download URL.
8862 sub list_osdn_mirrors
8864 my ($project, $file) = @_;
8865 return ( { 'url' => "http://downloads.sourceforge.net/$project/$file",
8867 'mirror' => 'downloads' } );
8870 =head2 convert_osdn_url(url)
8872 Given a URL like http://osdn.dl.sourceforge.net/sourceforge/project/file.zip
8873 or http://prdownloads.sourceforge.net/project/file.zip , convert it
8874 to a real URL on the sourceforge download redirector.
8877 sub convert_osdn_url
8880 if ($url =~ /^http:\/\/[^\.]+.dl.sourceforge.net\/sourceforge\/([^\/]+)\/(.*)$/ ||
8881 $url =~ /^http:\/\/prdownloads.sourceforge.net\/([^\/]+)\/(.*)$/) {
8882 # Always use the Sourceforge mail download URL, which does
8883 # a location-based redirect for us
8884 my ($project, $file) = ($1, $2);
8885 $url = "http://prdownloads.sourceforge.net/sourceforge/".
8887 return wantarray ? ( $url, 0 ) : $url;
8890 # Some other source .. don't change
8891 return wantarray ? ( $url, 2 ) : $url;
8895 =head2 get_current_dir
8897 Returns the directory the current process is running in.
8903 if ($gconfig{'os_type'} eq 'windows') {
8916 =head2 supports_users
8918 Returns 1 if the current OS supports Unix user concepts and functions like
8919 su , getpw* and so on. This will be true on Linux and other Unixes, but false
8925 return $gconfig{'os_type'} ne 'windows';
8928 =head2 supports_symlinks
8930 Returns 1 if the current OS supports symbolic and hard links. This will not
8931 be the case on Windows.
8934 sub supports_symlinks
8936 return $gconfig{'os_type'} ne 'windows';
8939 =head2 quote_path(path)
8941 Returns a path with safe quoting for the current operating system.
8947 if ($gconfig{'os_type'} eq 'windows' || $path =~ /^[a-z]:/i) {
8948 # Windows only supports "" style quoting
8952 return quotemeta($path);
8956 =head2 get_windows_root
8958 Returns the base windows system directory, like c:/windows.
8961 sub get_windows_root
8963 if ($ENV{'SystemRoot'}) {
8964 my $rv = $ENV{'SystemRoot'};
8969 return -d "c:/windows" ? "c:/windows" : "c:/winnt";
8973 =head2 read_file_contents(file)
8975 Given a filename, returns its complete contents as a string. Effectively
8976 the same as the Perl construct `cat file`.
8979 sub read_file_contents
8981 &open_readfile(FILE, $_[0]) || return undef;
8988 =head2 unix_crypt(password, salt)
8990 Performs Unix encryption on a password, using the built-in crypt function or
8991 the Crypt::UnixCrypt module if the former does not work. The salt parameter
8992 must be either an already-hashed password, or a two-character alpha-numeric
8998 my ($pass, $salt) = @_;
8999 return "" if ($salt !~ /^[a-zA-Z0-9\.\/]{2}/); # same as real crypt
9000 my $rv = eval "crypt(\$pass, \$salt)";
9002 return $rv if ($rv && !$@);
9003 eval "use Crypt::UnixCrypt";
9005 return Crypt::UnixCrypt::crypt($pass, $salt);
9008 &error("Failed to encrypt password : $err");
9012 =head2 split_quoted_string(string)
9014 Given a string like I<foo "bar baz" quux>, returns the array :
9018 sub split_quoted_string
9022 while($str =~ /^"([^"]*)"\s*([\000-\377]*)$/ ||
9023 $str =~ /^'([^']*)'\s*([\000-\377]*)$/ ||
9024 $str =~ /^(\S+)\s*([\000-\377]*)$/) {
9031 =head2 write_to_http_cache(url, file|&data)
9033 Updates the Webmin cache with the contents of the given file, possibly also
9034 clearing out old data. Mainly for internal use by http_download.
9037 sub write_to_http_cache
9039 my ($url, $file) = @_;
9040 return 0 if (!$gconfig{'cache_size'});
9042 # Don't cache downloads that look dynamic
9043 if ($url =~ /cgi-bin/ || $url =~ /\?/) {
9047 # Check if the current module should do caching
9048 if ($gconfig{'cache_mods'} =~ /^\!(.*)$/) {
9049 # Caching all except some modules
9050 my @mods = split(/\s+/, $1);
9051 return 0 if (&indexof(&get_module_name(), @mods) != -1);
9053 elsif ($gconfig{'cache_mods'}) {
9054 # Only caching some modules
9055 my @mods = split(/\s+/, $gconfig{'cache_mods'});
9056 return 0 if (&indexof(&get_module_name(), @mods) == -1);
9062 $size = length($$file);
9065 my @st = stat($file);
9069 if ($size > $gconfig{'cache_size'}) {
9070 # Bigger than the whole cache - so don't save it
9075 $cfile = "$main::http_cache_directory/$cfile";
9077 # See how much we have cached currently, clearing old files
9079 mkdir($main::http_cache_directory, 0700) if (!-d $main::http_cache_directory);
9080 opendir(CACHEDIR, $main::http_cache_directory);
9081 foreach my $f (readdir(CACHEDIR)) {
9082 next if ($f eq "." || $f eq "..");
9083 my $path = "$main::http_cache_directory/$f";
9084 my @st = stat($path);
9085 if ($gconfig{'cache_days'} &&
9086 time()-$st[9] > $gconfig{'cache_days'}*24*60*60) {
9087 # This file is too old .. trash it
9092 push(@cached, [ $path, $st[7], $st[9] ]);
9096 @cached = sort { $a->[2] <=> $b->[2] } @cached;
9097 while($total+$size > $gconfig{'cache_size'} && @cached) {
9098 # Cache is too big .. delete some files until the new one will fit
9099 unlink($cached[0]->[0]);
9100 $total -= $cached[0]->[1];
9104 # Finally, write out the new file
9106 &open_tempfile(CACHEFILE, ">$cfile");
9107 &print_tempfile(CACHEFILE, $$file);
9108 &close_tempfile(CACHEFILE);
9111 my ($ok, $err) = ©_source_dest($file, $cfile);
9117 =head2 check_in_http_cache(url)
9119 If some URL is in the cache and valid, return the filename for it. Mainly
9120 for internal use by http_download.
9123 sub check_in_http_cache
9126 return undef if (!$gconfig{'cache_size'});
9128 # Check if the current module should do caching
9129 if ($gconfig{'cache_mods'} =~ /^\!(.*)$/) {
9130 # Caching all except some modules
9131 my @mods = split(/\s+/, $1);
9132 return 0 if (&indexof(&get_module_name(), @mods) != -1);
9134 elsif ($gconfig{'cache_mods'}) {
9135 # Only caching some modules
9136 my @mods = split(/\s+/, $gconfig{'cache_mods'});
9137 return 0 if (&indexof(&get_module_name(), @mods) == -1);
9142 $cfile = "$main::http_cache_directory/$cfile";
9143 my @st = stat($cfile);
9144 return undef if (!@st || !$st[7]);
9145 if ($gconfig{'cache_days'} && time()-$st[9] > $gconfig{'cache_days'}*24*60*60) {
9150 open(TOUCH, ">>$cfile"); # Update the file time, to keep it in the cache
9155 =head2 supports_javascript
9157 Returns 1 if the current browser is assumed to support javascript.
9160 sub supports_javascript
9162 if (defined(&theme_supports_javascript)) {
9163 return &theme_supports_javascript();
9165 return $ENV{'MOBILE_DEVICE'} ? 0 : 1;
9168 =head2 get_module_name
9170 Returns the name of the Webmin module that called this function. For internal
9171 use only by other API functions.
9176 return &get_module_variable('$module_name');
9179 =head2 get_module_variable(name, [ref])
9181 Returns the value of some variable which is set in the caller's context, if
9182 using the new WebminCore package. For internal use only.
9185 sub get_module_variable
9187 my ($v, $wantref) = @_;
9188 my $slash = $wantref ? "\\" : "";
9189 my $thispkg = &web_libs_package();
9190 if ($thispkg eq 'WebminCore') {
9191 my ($vt, $vn) = split('', $v, 2);
9193 for(my $i=0; ($callpkg) = caller($i); $i++) {
9194 last if ($callpkg ne $thispkg);
9196 return eval "${slash}${vt}${callpkg}::${vn}";
9198 return eval "${slash}${v}";
9201 =head2 clear_time_locale()
9203 Temporarily force the locale to C, until reset_time_locale is called. This is
9204 useful if your code is going to call C<strftime> from the POSIX package, and
9205 you want to ensure that the output is in a consistent format.
9208 sub clear_time_locale
9210 if ($main::clear_time_locale_count == 0) {
9213 $main::clear_time_locale_old = POSIX::setlocale(POSIX::LC_TIME);
9214 POSIX::setlocale(POSIX::LC_TIME, "C");
9217 $main::clear_time_locale_count++;
9220 =head2 reset_time_locale()
9222 Revert the locale to whatever it was before clear_time_locale was called
9225 sub reset_time_locale
9227 if ($main::clear_time_locale_count == 1) {
9229 POSIX::setlocale(POSIX::LC_TIME, $main::clear_time_locale_old);
9230 $main::clear_time_locale_old = undef;
9233 $main::clear_time_locale_count--;
9236 =head2 callers_package(filehandle)
9238 Convert a non-module filehandle like FOO to one qualified with the
9239 caller's caller's package, like fsdump::FOO. For internal use only.
9245 my $callpkg = (caller(1))[0];
9246 my $thispkg = &web_libs_package();
9247 if (!ref($fh) && $fh !~ /::/ &&
9248 $callpkg ne $thispkg && $thispkg eq 'WebminCore') {
9249 $fh = $callpkg."::".$fh;
9254 =head2 web_libs_package()
9256 Returns the package this code is in. We can't always trust __PACKAGE__. For
9260 sub web_libs_package
9262 if ($called_from_webmin_core) {
9263 return "WebminCore";
9268 =head2 get_userdb_string
9270 Returns the URL-style string for connecting to the users and groups database
9273 sub get_userdb_string
9275 return undef if ($main::no_miniserv_userdb);
9277 &get_miniserv_config(\%miniserv);
9278 return $miniserv{'userdb'};
9281 =head2 connect_userdb(string)
9283 Returns a handle for talking to a user database - may be a DBI or LDAP handle.
9284 On failure returns an error message string. In an array context, returns the
9291 my ($proto, $user, $pass, $host, $prefix, $args) = &split_userdb_string($str);
9292 if ($proto eq "mysql") {
9293 # Connect to MySQL with DBI
9294 my $drh = eval "use DBI; DBI->install_driver('mysql');";
9295 $drh || return $text{'sql_emysqldriver'};
9296 my ($host, $port) = split(/:/, $host);
9297 my $cstr = "database=$prefix;host=$host";
9298 $cstr .= ";port=$port" if ($port);
9299 my $dbh = $drh->connect($cstr, $user, $pass, { });
9300 $dbh || return &text('sql_emysqlconnect', $drh->errstr);
9301 return wantarray ? ($dbh, $proto, $prefix, $args) : $dbh;
9303 elsif ($proto eq "postgresql") {
9304 # Connect to PostgreSQL with DBI
9305 my $drh = eval "use DBI; DBI->install_driver('Pg');";
9306 $drh || return $text{'sql_epostgresqldriver'};
9307 my ($host, $port) = split(/:/, $host);
9308 my $cstr = "dbname=$prefix;host=$host";
9309 $cstr .= ";port=$port" if ($port);
9310 my $dbh = $drh->connect($cstr, $user, $pass);
9311 $dbh || return &text('sql_epostgresqlconnect', $drh->errstr);
9312 return wantarray ? ($dbh, $proto, $prefix, $args) : $dbh;
9314 elsif ($proto eq "ldap") {
9315 # Connect with perl LDAP module
9316 eval "use Net::LDAP";
9317 $@ && return $text{'sql_eldapdriver'};
9318 my ($host, $port) = split(/:/, $host);
9319 my $scheme = $args->{'scheme'} || 'ldap';
9321 $port = $scheme eq 'ldaps' ? 636 : 389;
9323 my $ldap = Net::LDAP->new($host,
9325 'scheme' => $scheme);
9326 $ldap || return &text('sql_eldapconnect', $host);
9328 if ($args->{'tls'}) {
9329 # Switch to TLS mode
9330 eval { $mesg = $ldap->start_tls(); };
9331 if ($@ || !$mesg || $mesg->code) {
9332 return &text('sql_eldaptls',
9333 $@ ? $@ : $mesg ? $mesg->error : "Unknown error");
9336 # Login to the server
9338 $mesg = $ldap->bind(dn => $user, password => $pass);
9341 $mesg = $ldap->bind(dn => $user, anonymous => 1);
9343 if (!$mesg || $mesg->code) {
9344 return &text('sql_eldaplogin', $user,
9345 $mesg ? $mesg->error : "Unknown error");
9347 return wantarray ? ($ldap, $proto, $prefix, $args) : $ldap;
9350 return "Unknown protocol $proto";
9354 =head2 disconnect_userdb(string, &handle)
9356 Closes a handle opened by connect_userdb
9359 sub disconnect_userdb
9362 if ($str =~ /^(mysql|postgresql):/) {
9364 if (!$h->{'AutoCommit'}) {
9369 elsif ($str =~ /^ldap:/) {
9376 =head2 split_userdb_string(string)
9378 Converts a string like mysql://user:pass@host/db into separate parts
9381 sub split_userdb_string
9384 if ($str =~ /^([a-z]+):\/\/([^:]*):([^\@]*)\@([a-z0-9\.\-\_]+)\/([^\?]+)(\?(.*))?$/) {
9385 my ($proto, $user, $pass, $host, $prefix, $argstr) =
9386 ($1, $2, $3, $4, $5, $7);
9387 my %args = map { split(/=/, $_, 2) } split(/\&/, $argstr);
9388 return ($proto, $user, $pass, $host, $prefix, \%args);
9393 $done_web_lib_funcs = 1;