* Copyright date update
[BackupPC.git] / lib / BackupPC / CGI / Lib.pm
index 1a22698..e683fdf 100644 (file)
@@ -11,7 +11,7 @@
 #   Craig Barratt  <cbarratt@users.sourceforge.net>
 #
 # COPYRIGHT
-#   Copyright (C) 2003  Craig Barratt
+#   Copyright (C) 2003-2009  Craig Barratt
 #
 #   This program is free software; you can redistribute it and/or modify
 #   it under the terms of the GNU General Public License as published by
@@ -29,7 +29,7 @@
 #
 #========================================================================
 #
-# Version 2.1.0_CVS, released 3 Jul 2003.
+# Version 3.2.0beta0, released 5 April 2009.
 #
 # See http://backuppc.sourceforge.net.
 #
@@ -44,7 +44,7 @@ require Exporter;
 
 use vars qw( @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS );
 
-use vars qw($Cgi %In $MyURL $User %Conf $TopDir $BinDir $bpc);
+use vars qw($Cgi %In $MyURL $User %Conf $TopDir $LogDir $BinDir $bpc);
 use vars qw(%Status %Info %Jobs @BgQueue @UserQueue @CmdQueue
             %QueueLen %StatusHost);
 use vars qw($Hosts $HostsMTime $ConfigMTime $PrivAdmin);
@@ -76,7 +76,7 @@ use vars qw($Lang);
                    NavLink
                    h1
                    h2
-                   $Cgi %In $MyURL $User %Conf $TopDir $BinDir $bpc
+                   $Cgi %In $MyURL $User %Conf $TopDir $LogDir $BinDir $bpc
                    %Status %Info %Jobs @BgQueue @UserQueue @CmdQueue
                    %QueueLen %StatusHost
                    $Hosts $HostsMTime $ConfigMTime $PrivAdmin
@@ -93,11 +93,32 @@ sub NewRequest
     $Cgi = new CGI;
     %In = $Cgi->Vars;
 
+    if ( !defined($bpc) ) {
+       ErrorExit($Lang->{BackupPC__Lib__new_failed__check_apache_error_log})
+           if ( !($bpc = BackupPC::Lib->new(undef, undef, undef, 1)) );
+       $TopDir = $bpc->TopDir();
+       $LogDir = $bpc->LogDir();
+       $BinDir = $bpc->BinDir();
+       %Conf   = $bpc->Conf();
+       $Lang   = $bpc->Lang();
+       $ConfigMTime = $bpc->ConfigMTime();
+        umask($Conf{UmaskMode});
+    } elsif ( $bpc->ConfigMTime() != $ConfigMTime ) {
+        $bpc->ConfigRead();
+       $TopDir = $bpc->TopDir();
+       $LogDir = $bpc->LogDir();
+       $BinDir = $bpc->BinDir();
+        %Conf   = $bpc->Conf();
+        $Lang   = $bpc->Lang();
+        $ConfigMTime = $bpc->ConfigMTime();
+        umask($Conf{UmaskMode});
+    }
+
     #
     # Default REMOTE_USER so in a miminal installation the user
     # has a sensible default.
     #
-    $ENV{REMOTE_USER} = $Conf{BackupPCUser} if ( !defined($ENV{REMOTE_USER}) );
+    $ENV{REMOTE_USER} = $Conf{BackupPCUser} if ( $ENV{REMOTE_USER} eq "" );
 
     #
     # We require that Apache pass in $ENV{SCRIPT_NAME} and $ENV{REMOTE_USER}.
@@ -108,18 +129,10 @@ sub NewRequest
     $MyURL  = $ENV{SCRIPT_NAME};
     $User   = $ENV{REMOTE_USER};
 
-    if ( !defined($bpc) ) {
-       ErrorExit($Lang->{BackupPC__Lib__new_failed__check_apache_error_log})
-           if ( !($bpc = BackupPC::Lib->new(undef, undef, 1)) );
-       $TopDir = $bpc->TopDir();
-       $BinDir = $bpc->BinDir();
-       %Conf   = $bpc->Conf();
-       $Lang   = $bpc->Lang();
-       $ConfigMTime = $bpc->ConfigMTime();
-    } elsif ( $bpc->ConfigMTime() != $ConfigMTime ) {
-        $bpc->ServerMesg("log Re-read config file because mtime changed");
-        $bpc->ServerMesg("server reload");
-    }
+    #
+    # Handle LDAP uid=user when using mod_authz_ldap and otherwise untaint
+    #
+    $User   = $1 if ( $User =~ /uid=([^,]+)/i || $User =~ /(.*)/ );
 
     #
     # Clean up %ENV for taint checking
@@ -154,6 +167,15 @@ EOF
               {map {$_, 1} split(",", $Hosts->{$host}{moreUsers}) }
        }
     }
+
+    #
+    # Untaint the host name
+    #
+    if ( $In{host} =~ /^([\w.\s-]+)$/ ) {
+       $In{host} = $1;
+    } else {
+       delete($In{host});
+    }
 }
 
 sub timeStamp2
@@ -161,7 +183,10 @@ sub timeStamp2
     my($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)
               = localtime($_[0] == 0 ? time : $_[0] );
     $mon++;
-    if ( $Conf{CgiDateFormatMMDD} ) {
+    if ( $Conf{CgiDateFormatMMDD} == 2 ) {
+        $year += 1900;
+        return sprintf("%04d-%02d-%02d %02d:%02d", $year, $mon, $mday, $hour, $min);
+    } elsif ( $Conf{CgiDateFormatMMDD} ) {
         return sprintf("$mon/$mday %02d:%02d", $hour, $min);
     } else {
         return sprintf("$mday/$mon %02d:%02d", $hour, $min);
@@ -205,7 +230,7 @@ sub EscHTML
     $s =~ s/\"/&quot;/g;
     $s =~ s/>/&gt;/g;
     $s =~ s/</&lt;/g;
-    $s =~ s{([^[:print:]])}{sprintf("&\#x%02X;", ord($1));}eg;
+    ### $s =~ s{([^[:print:]])}{sprintf("&\#x%02X;", ord($1));}eg;
     return \$s;
 }
 
@@ -264,8 +289,8 @@ sub ServerConnect
         if ( CheckPermission() 
           && -f $Conf{ServerInitdPath}
           && $Conf{ServerInitdStartCmd} ne "" ) {
-            Header(eval("qq{$Lang->{Unable_to_connect_to_BackupPC_server}}"));
-            print (eval("qq{$Lang->{Admin_Start_Server}}"));
+            my $content = eval("qq{$Lang->{Admin_Start_Server}}");
+            Header(eval("qq{$Lang->{Unable_to_connect_to_BackupPC_server}}"), $content);
             Trailer();
             exit(1);
         } else {
@@ -278,21 +303,25 @@ sub GetStatusInfo
 {
     my($status) = @_;
     ServerConnect();
+    %Status = ()     if ( $status =~ /\bhosts\b/ );
+    %StatusHost = () if ( $status =~ /\bhost\(/ );
     my $reply = $bpc->ServerMesg("status $status");
     $reply = $1 if ( $reply =~ /(.*)/s );
     eval($reply);
     # ignore status related to admin and trashClean jobs
     if ( $status =~ /\bhosts\b/ ) {
-        delete($Status{$bpc->adminJob});
+       foreach my $host ( grep(/admin/, keys(%Status)) ) {
+           delete($Status{$host}) if ( $bpc->isAdminJob($host) );
+       }
         delete($Status{$bpc->trashJob});
     }
 }
 
 sub ReadUserEmailInfo
 {
-    if ( (stat("$TopDir/log/UserEmailInfo.pl"))[9] != $UserEmailInfoMTime ) {
-        do "$TopDir/log/UserEmailInfo.pl";
-        $UserEmailInfoMTime = (stat("$TopDir/log/UserEmailInfo.pl"))[9];
+    if ( (stat("$LogDir/UserEmailInfo.pl"))[9] != $UserEmailInfoMTime ) {
+        do "$LogDir/UserEmailInfo.pl";
+        $UserEmailInfoMTime = (stat("$LogDir/UserEmailInfo.pl"))[9];
     }
 }
 
@@ -313,42 +342,38 @@ sub CheckPermission
               || $host ne "" && !defined($Hosts->{$host}) );
     if ( $Conf{CgiAdminUserGroup} ne "" ) {
         my($n,$p,$gid,$mem) = getgrnam($Conf{CgiAdminUserGroup});
-        $Privileged ||= ($mem =~ /\b$User\b/);
+        $Privileged ||= ($mem =~ /\b\Q$User\E\b/);
     }
     if ( $Conf{CgiAdminUsers} ne "" ) {
-        $Privileged ||= ($Conf{CgiAdminUsers} =~ /\b$User\b/);
+        $Privileged ||= ($Conf{CgiAdminUsers} =~ /\b\Q$User\E\b/);
         $Privileged ||= $Conf{CgiAdminUsers} eq "*";
     }
     $PrivAdmin = $Privileged;
+    return $Privileged if ( !defined($host) );
+
     $Privileged ||= $User eq $Hosts->{$host}{user};
     $Privileged ||= defined($Hosts->{$host}{moreUsers}{$User});
-
     return $Privileged;
 }
 
 #
 # Returns the list of hosts that should appear in the navigation bar
-# for this user.  If $Conf{CgiNavBarAdminAllHosts} is set, the admin
-# gets all the hosts.  Otherwise, regular users get hosts for which
-# they are the user or are listed in the moreUsers column in the
-# hosts file.
+# for this user.  If $getAll is set, the admin gets all the hosts.
+# Otherwise, regular users get hosts for which they are the user or
+# are listed in the moreUsers column in the hosts file.
 #
 sub GetUserHosts
 {
-    my($host) = @_;
+    my($getAll) = @_;
     my @hosts;
 
-    if ( $Conf{CgiNavBarAdminAllHosts} && CheckPermission() ) {
+    if ( $getAll && CheckPermission() ) {
         @hosts = sort keys %$Hosts;
     } else {
         @hosts = sort grep { $Hosts->{$_}{user} eq $User ||
                        defined($Hosts->{$_}{moreUsers}{$User}) } keys(%$Hosts);
     }
-    #
-    # return the selected host first (if present)
-    #
-    return @hosts if ( !defined($host) || !grep(/^$host$/, @hosts) );
-    return ($host, grep(!/^$host$/, @hosts));
+    return @hosts;
 }
 
 #
@@ -392,96 +417,102 @@ sub ConfirmIPAddress
 
 sub Header
 {
-    my($title, $content) = @_;
+    my($title, $content, $noBrowse, $contentSub, $contentPost) = @_;
     my @adminLinks = (
-        { link => "",                         name => $Lang->{Status},
-                                              priv => 1},
-        { link => "?action=adminOpts",        name => $Lang->{Admin_Options} },
-        { link => "?action=summary",          name => $Lang->{PC_Summary} },
-        { link => "?action=view&type=LOG",    name => $Lang->{LOG_file} },
-        { link => "?action=LOGlist",          name => $Lang->{Old_LOGs} },
-        { link => "?action=emailSummary",     name => $Lang->{Email_summary} },
-        { link => "?action=view&type=config", name => $Lang->{Config_file} },
-        { link => "?action=view&type=hosts",  name => $Lang->{Hosts_file} },
-        { link => "?action=queue",            name => $Lang->{Current_queues} },
-        { link => "?action=view&type=docs",   name => $Lang->{Documentation},
-                                              priv => 1},
-        { link => "http://backuppc.sourceforge.net/faq", name => "FAQ",
-                                              priv => 1},
-        { link => "http://backuppc.sourceforge.net", name => "SourceForge",
-                                              priv => 1},
+        { link => "",                      name => $Lang->{Status}},
+        { link => "?action=summary",       name => $Lang->{PC_Summary}},
+        { link => "?action=editConfig",    name => $Lang->{CfgEdit_Edit_Config},
+                                           priv => 1},
+        { link => "?action=editConfig&newMenu=hosts",
+                                           name => $Lang->{CfgEdit_Edit_Hosts},
+                                           priv => 1},
+        { link => "?action=adminOpts",     name => $Lang->{Admin_Options},
+                                           priv => 1},
+        { link => "?action=view&type=LOG", name => $Lang->{LOG_file},
+                                           priv => 1},
+        { link => "?action=LOGlist",       name => $Lang->{Old_LOGs},
+                                           priv => 1},
+        { link => "?action=emailSummary",  name => $Lang->{Email_summary},
+                                           priv => 1},
+        { link => "?action=queue",         name => $Lang->{Current_queues},
+                                           priv => 1},
+        @{$Conf{CgiNavBarLinks} || []},
     );
-    print $Cgi->header();
+    my $host = $In{host};
+
+    binmode(STDOUT, ":utf8");
+    print $Cgi->header(-charset => "utf-8");
     print <<EOF;
 <!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN">
 <html><head>
 <title>$title</title>
-$Conf{CSSstylesheet}
+<link rel=stylesheet type="text/css" href="$Conf{CgiImageDirURL}/$Conf{CgiCSSFile}" title="CSSFile">
 $Conf{CgiHeaders}
+<script src="$Conf{CgiImageDirURL}/sorttable.js"></script>
 </head><body onLoad="document.getElementById('NavMenu').style.height=document.body.scrollHeight">
-<img src="$Conf{CgiImageDirURL}/logo.gif" hspace="5" vspace="7"><br>
+<a href="http://backuppc.sourceforge.net"><img src="$Conf{CgiImageDirURL}/logo.gif" hspace="5" vspace="7" border="0"></a><br>
 EOF
-    if (!defined($In{host})) {
-        print <<EOF;
-<div id="Content">
-$content
-<br><br><br>
+
+    if ( defined($Hosts) && defined($host) && defined($Hosts->{$host}) ) {
+       print "<div class=\"NavMenu\">";
+       NavSectionTitle("${EscHTML($host)}");
+       print <<EOF;
 </div>
-<div class="NavMenu" id="NavMenu" style="height:100%">
+<div class="NavMenu">
 EOF
-    } else {
-        print "<div class=\"NavMenu\">";
+       NavLink("?host=${EscURI($host)}",
+               "$host $Lang->{Home}", " class=\"navbar\"");
+       NavLink("?action=browse&host=${EscURI($host)}",
+               $Lang->{Browse}, " class=\"navbar\"") if ( !$noBrowse );
+       NavLink("?action=view&type=LOG&host=${EscURI($host)}",
+               $Lang->{LOG_file}, " class=\"navbar\"");
+       NavLink("?action=LOGlist&host=${EscURI($host)}",
+               $Lang->{LOG_files}, " class=\"navbar\"");
+       if ( -f "$TopDir/pc/$host/SmbLOG.bad"
+                   || -f "$TopDir/pc/$host/SmbLOG.bad.z"
+                   || -f "$TopDir/pc/$host/XferLOG.bad"
+                   || -f "$TopDir/pc/$host/XferLOG.bad.z" ) {
+          NavLink("?action=view&type=XferLOGbad&host=${EscURI($host)}",
+                   $Lang->{Last_bad_XferLOG}, " class=\"navbar\"");
+          NavLink("?action=view&type=XferErrbad&host=${EscURI($host)}",
+                   $Lang->{Last_bad_XferLOG_errors_only},
+                   " class=\"navbar\"");
+       }
+        if ( $Conf{CgiUserConfigEditEnable} || $PrivAdmin ) {
+            NavLink("?action=editConfig&host=${EscURI($host)}",
+                    $Lang->{CfgEdit_Edit_Config}, " class=\"navbar\"");
+        } elsif ( -f "$TopDir/pc/$host/config.pl"
+                    || ($host ne "config" && -f "$TopDir/conf/$host.pl") ) {
+            NavLink("?action=view&type=config&host=${EscURI($host)}",
+                    $Lang->{Config_file}, " class=\"navbar\"");
+        }
+       print "</div>\n";
     }
-    NavSectionTitle($Lang->{Hosts});
-    my $hostSelectbox = "<option value=\"#\">Select a host...</option>";
-    my @hosts;
-    if ( defined($Hosts) && %$Hosts > 0 ) {
-        @hosts = GetUserHosts($In{host});
-        foreach my $host ( @hosts ) {
-            if ($In{host} eq $host) {
-                print <<EOF;
-</div>
-<div class="HostOn"> <a href="?host=${EscURI($host)}">$host</a> <br>
-<div class="HostOnContent">
-EOF
-                NavLink("?host=${EscURI($host)}",
-                        "$host $Lang->{Home}", " class=\"navbar\"");
-                NavLink("?action=browse&host=${EscURI($host)}",
-                        $Lang->{Browse}, " class=\"navbar\"");
-                NavLink("?action=view&type=LOG&host=${EscURI($host)}",
-                        $Lang->{LOG_file}, " class=\"navbar\"");
-                NavLink("?action=LOGlist&host=${EscURI($host)}",
-                        $Lang->{LOG_files}, " class=\"navbar\"");
-                if ( -f "$TopDir/pc/$host/SmbLOG.bad"
-                            || -f "$TopDir/pc/$host/SmbLOG.bad.z"
-                            || -f "$TopDir/pc/$host/XferLOG.bad"
-                            || -f "$TopDir/pc/$host/XferLOG.bad.z" ) {
-                   NavLink("?action=view&type=XferLOGbad&host=${EscURI($host)}",
-                            $Lang->{Last_bad_XferLOG}, " class=\"navbar\"");
-                   NavLink("?action=view&type=XferErrbad&host=${EscURI($host)}",
-                            $Lang->{Last_bad_XferLOG_errors_only},
-                            " class=\"navbar\"");
-                }
-                if ( -f "$TopDir/pc/$host/config.pl" ) {
-                    NavLink("?action=view&type=config&host=${EscURI($host)}",
-                            $Lang->{Config_file}, " class=\"navbar\"");
-                }
-                print <<EOF;
-</div></div>
-<div id="Content">
-$content
+    print("<div id=\"Content\">\n$content\n");
+    if ( defined($contentSub) && ref($contentSub) eq "CODE" ) {
+       while ( (my $s = &$contentSub()) ne "" ) {
+           print($s);
+       }
+    }
+    print($contentPost) if ( defined($contentPost) );
+    print <<EOF;
 <br><br><br>
 </div>
-<div class="NavMenu" style="height:100%" id="NavMenu">
+<div class="NavMenu" id="NavMenu" style="height:100%">
 EOF
-            } else {
-                NavLink("?host=${EscURI($host)}", $host) if ( @hosts < 6 );
-                $hostSelectbox .= "<option value=\"?host=${EscURI($host)}\">"
-                                . "$host</option>";
-            }
+    my $hostSelectbox = "<option value=\"#\">$Lang->{Select_a_host}</option>";
+    my @hosts = GetUserHosts($Conf{CgiNavBarAdminAllHosts});
+    NavSectionTitle($Lang->{Hosts});
+    if ( defined($Hosts) && %$Hosts > 0 && @hosts ) {
+        foreach my $host ( @hosts ) {
+           NavLink("?host=${EscURI($host)}", $host)
+                   if ( @hosts < $Conf{CgiNavBarAdminAllHosts} );
+           my $sel = " selected" if ( $host eq $In{host} );
+           $hostSelectbox .= "<option value=\"?host=${EscURI($host)}\"$sel>"
+                           . "$host</option>";
         }
     }
-    if ( @hosts >= 6 ) {
+    if ( @hosts >= $Conf{CgiNavBarAdminAllHosts} ) {
         print <<EOF;
 <br>
 <select onChange="document.location=this.value">
@@ -490,21 +521,23 @@ $hostSelectbox
 <br><br>
 EOF
     }
-    NavSectionTitle($Lang->{Host_or_User_name});
-    print <<EOF;
+    if ( $Conf{CgiSearchBoxEnable} ) {
+        print <<EOF;
 <form action="$MyURL" method="get">
-    <input type="text" name="host" size="10" maxlength="64">
+    <input type="text" name="host" size="14" maxlength="64">
     <input type="hidden" name="action" value="hostInfo"><input type="submit" value="$Lang->{Go}" name="ignore">
     </form>
 EOF
+    }
     NavSectionTitle($Lang->{NavSectionTitle_});
     foreach my $l ( @adminLinks ) {
-        if ( $PrivAdmin || $l->{priv} ) {
-            NavLink($l->{link}, $l->{name});
+        if ( $PrivAdmin || !$l->{priv} ) {
+            my $txt = $l->{lname} ne "" ? $Lang->{$l->{lname}} : $l->{name};
+            NavLink($l->{link}, $txt);
+        }
     }
-}
 
-print <<EOF;
+    print <<EOF;
 <br><br><br>
 </div>
 EOF
@@ -522,9 +555,7 @@ sub NavSectionTitle
 {
     my($head) = @_;
     print <<EOF;
-<div class="NavTitle">
-$head
-</div>
+<div class="NavTitle">$head</div>
 EOF
 }