X-Git-Url: http://git.rot13.org/?p=BackupPC.git;a=blobdiff_plain;f=cgi-bin%2FBackupPC_Admin;h=90dfdba2667d94fe1a3f19fdb6ab2440b361c774;hp=c0c2af059522a504070c7393705e84ba154430c5;hb=38abb9a20f4f9562df117185570646049ce126fb;hpb=1ce7d1541ea1279aaa0a75c16986a3fd40b608ec diff --git a/cgi-bin/BackupPC_Admin b/cgi-bin/BackupPC_Admin index c0c2af0..90dfdba 100755 --- a/cgi-bin/BackupPC_Admin +++ b/cgi-bin/BackupPC_Admin @@ -1,4 +1,4 @@ -#!/bin/perl -T +#!/usr/bin/perl #============================================================= -*-perl-*-w # # BackupPC_Admin: Apache/CGI interface for BackupPC. @@ -14,13 +14,14 @@ # user name. # # Also, this script needs to run as the BackupPC user. To accomplish -# this the script is typically installed as setuid to the BackupPC user. +# this the script is typically installed as setuid to the BackupPC user, +# or it can run under mod_perl with httpd running as the BackupPC user. # # AUTHOR # Craig Barratt # # COPYRIGHT -# Copyright (C) 2001 Craig Barratt +# Copyright (C) 2001-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 @@ -38,2503 +39,73 @@ # #======================================================================== # -# Version 1.5.0, released 2 Aug 2002. +# Version 3.2.0, released 31 Jul 2010. # # See http://backuppc.sourceforge.net. # #======================================================================== use strict; +no utf8; use CGI; -use lib "__INSTALLDIR__/lib"; -use BackupPC::Lib; -use BackupPC::FileZIO; -use BackupPC::Attrib qw(:all); -use Data::Dumper; - -use vars qw($Cgi %In $MyURL $User %Conf $TopDir $BinDir $bpc); -use vars qw(%Status %Info %Jobs @BgQueue @UserQueue @CmdQueue - %QueueLen %StatusHost); -use vars qw($Hosts $HostsMTime $ConfigMTime $PrivAdmin); -use vars qw(%UserEmailInfo $UserEmailInfoMTime %RestoreReq); - -$Cgi = new CGI; -%In = $Cgi->Vars; - -# -# We require that Apache pass in $ENV{SCRIPT_NAME} and $ENV{REMOTE_USER}. -# The latter requires .ht_access style authentication. Replace this -# code if you are using some other type of authentication, and have -# a different way of getting the user name. -# -$MyURL = $ENV{SCRIPT_NAME}; -$User = $ENV{REMOTE_USER}; - -if ( !defined($bpc) ) { - ErrorExit("BackupPC::Lib->new failed: check apache error_log\n") - if ( !($bpc = BackupPC::Lib->new) ); - $TopDir = $bpc->TopDir(); - $BinDir = $bpc->BinDir(); - %Conf = $bpc->Conf(); - $ConfigMTime = $bpc->ConfigMTime(); -} elsif ( $bpc->ConfigMTime() != $ConfigMTime ) { - $bpc->ConfigRead(); - %Conf = $bpc->Conf(); - $ConfigMTime = $bpc->ConfigMTime(); -} +use CGI::Carp qw(fatalsToBrowser); +use lib "/usr/local/BackupPC/lib"; -# -# Clean up %ENV for taint checking -# -delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; -$ENV{PATH} = $Conf{MyPath}; +use BackupPC::Lib; +use BackupPC::CGI::Lib qw(:all); -# -# Verify we are running as the correct user -# -if ( $Conf{BackupPCUserVerify} - && $> != (my $uid = (getpwnam($Conf{BackupPCUser}))[2]) ) { - ErrorExit("Wrong user: my userid is $>, instead of $uid" - . " ($Conf{BackupPCUser})\n"); -} - -if ( !defined($Hosts) || $bpc->HostsMTime() != $HostsMTime ) { - $HostsMTime = $bpc->HostsMTime(); - $Hosts = $bpc->HostInfoRead(); -} +BackupPC::CGI::Lib::NewRequest; my %ActionDispatch = ( - "summary" => \&Action_Summary, - "Start Incr Backup" => \&Action_StartStopBackup, - "Start Full Backup" => \&Action_StartStopBackup, - "Stop/Dequeue Backup" => \&Action_StartStopBackup, - "queue" => \&Action_Queue, - "view" => \&Action_View, - "LOGlist" => \&Action_LOGlist, - "emailSummary" => \&Action_EmailSummary, - "browse" => \&Action_Browse, - "Restore" => \&Action_Restore, - "RestoreFile" => \&Action_RestoreFile, - "hostInfo" => \&Action_HostInfo, - "generalInfo" => \&Action_GeneralInfo, - "restoreInfo" => \&Action_RestoreInfo, + "summary" => "Summary", + "search" => "SearchArchives", + "burn" => "BurnMedia", + "Start_Incr_Backup" => "StartStopBackup", + "Start_Full_Backup" => "StartStopBackup", + "Stop_Dequeue_Backup" => "StartStopBackup", + "Stop_Dequeue_Archive" => "StartStopBackup", + "queue" => "Queue", + "view" => "View", + "LOGlist" => "LOGlist", + "emailSummary" => "EmailSummary", + "browse" => "Browse", + "dirHistory" => "DirHistory", + "Restore" => "Restore", + "RestoreFile" => "RestoreFile", + "hostInfo" => "HostInfo", + "generalInfo" => "GeneralInfo", + "restoreInfo" => "RestoreInfo", + "archiveInfo" => "ArchiveInfo", + "Start_Archive" => "Archive", + "Archive" => "Archive", + "Reload" => "ReloadServer", + "startServer" => "StartServer", + "Stop" => "StopServer", + "adminOpts" => "AdminOptions", + "editConfig" => "EditConfig", + "rss" => "RSS", ); # # Set default actions, then call sub handler # -$In{action} ||= "hostInfo" if ( defined($In{host}) ); -$In{action} = "generalInfo" if ( !defined($ActionDispatch{$In{action}}) ); -$ActionDispatch{$In{action}}(); -exit(0); - -########################################################################### -# Action handling subroutines -########################################################################### - -sub Action_Summary -{ - my($fullTot, $fullSizeTot, $incrTot, $incrSizeTot, $str, - $strNone, $strGood, $hostCntGood, $hostCntNone); - - $hostCntGood = $hostCntNone = 0; - GetStatusInfo("hosts"); - my $Privileged = CheckPermission(); - - if ( !$Privileged ) { - ErrorExit("Only privileged users can view PC summaries." ); - } - foreach my $host ( sort(keys(%Status)) ) { - my($fullDur, $incrCnt, $incrAge, $fullSize, $fullRate); - my @Backups = $bpc->BackupInfoRead($host); - my $fullCnt = $incrCnt = 0; - my $fullAge = $incrAge = -1; - for ( my $i = 0 ; $i < @Backups ; $i++ ) { - if ( $Backups[$i]{type} eq "full" ) { - $fullCnt++; - if ( $fullAge < 0 || $Backups[$i]{startTime} > $fullAge ) { - $fullAge = $Backups[$i]{startTime}; - $fullSize = $Backups[$i]{size} / (1024 * 1024); - $fullDur = $Backups[$i]{endTime} - $Backups[$i]{startTime}; - } - $fullSizeTot += $Backups[$i]{size} / (1024 * 1024); - } else { - $incrCnt++; - if ( $incrAge < 0 || $Backups[$i]{startTime} > $incrAge ) { - $incrAge = $Backups[$i]{startTime}; - } - $incrSizeTot += $Backups[$i]{size} / (1024 * 1024); - } - } - if ( $fullAge < 0 ) { - $fullAge = ""; - $fullRate = ""; - } else { - $fullAge = sprintf("%.1f", (time - $fullAge) / (24 * 3600)); - $fullRate = sprintf("%.2f", - $fullSize / ($fullDur <= 0 ? 1 : $fullDur)); - } - if ( $incrAge < 0 ) { - $incrAge = ""; - } else { - $incrAge = sprintf("%.1f", (time - $incrAge) / (24 * 3600)); - } - $fullTot += $fullCnt; - $incrTot += $incrCnt; - $fullSize = sprintf("%.2f", $fullSize / 1000); - $str = < ${HostLink($host)} - ${UserLink($Hosts->{$host}{user})} - $fullCnt - $fullAge - $fullSize - $fullRate - $incrCnt - $incrAge - $Status{$host}{state} - $Status{$host}{reason} -EOF - if ( @Backups == 0 ) { - $hostCntNone++; - $strNone .= $str; - } else { - $hostCntGood++; - $strGood .= $str; - } - } - $fullSizeTot = sprintf("%.2f", $fullSizeTot / 1000); - $incrSizeTot = sprintf("%.2f", $incrSizeTot / 1000); - my $now = timeStamp2(time); - - Header("BackupPC: Server Summary"); - - print < -This status was generated at $now. -

- -${h2("Hosts with good Backups")} -

-There are $hostCntGood hosts that have been backed up, for a total of: -

    -
  • $fullTot full backups of total size ${fullSizeTot}GB - (prior to pooling and compression), -
  • $incrTot incr backups of total size ${incrSizeTot}GB - (prior to pooling and compression). -
- - - - - - - - - - - -$strGood -
Host User #Full Full Age/days Full Size/GB Speed MB/sec #Incr Incr Age/days State Last attempt
-

- -${h2("Hosts with no Backups")} -

-There are $hostCntNone hosts with no backups. -

- - - - - - - - - - - -$strNone -
Host User #Full Full Age/days Full Size/GB Speed MB/sec #Incr Incr Age/days Current State Last backup attempt
-EOF - Trailer(); -} - -sub Action_StartStopBackup -{ - my($str, $reply); - my $start = 1 if ( $In{action} eq "Start Incr Backup" - || $In{action} eq "Start Full Backup" ); - my $doFull = $In{action} eq "Start Full Backup" ? 1 : 0; - my $type = $doFull ? "full" : "incremental"; - my $host = $In{host}; - my $Privileged = CheckPermission($host); - - if ( !$Privileged ) { - ErrorExit("Only privileged users can stop or start backups on" - . " ${EscapeHTML($host)}."); - } - ServerConnect(); - - if ( $In{doit} ) { - if ( $start ) { - if ( $Hosts->{$host}{dhcp} ) { - $reply = $bpc->ServerMesg("backup $In{hostIP} $host" - . " $User $doFull"); - $str = "Backup requested on DHCP $host ($In{hostIP}) by" - . " $User from $ENV{REMOTE_ADDR}"; - } else { - $reply = $bpc->ServerMesg("backup $host $host $User $doFull"); - $str = "Backup requested on $host by $User"; - } - } else { - $reply = $bpc->ServerMesg("stop $host $User $In{backoff}"); - $str = "Backup stopped/dequeued on $host by $User"; - } - Header("BackupPC: Backup Requested on $host"); - print < -Reply from server was: $reply -

-Go back to $host home page. -EOF - Trailer(); - } else { - if ( $start ) { - my $ipAddr = ConfirmIPAddress($host); - - Header("BackupPC: Start Backup Confirm on $host"); - print < -You are about to start a $type backup on $host. - -

- - - -Do you really want to do this? - - -
-EOF - } else { - my $backoff = ""; - GetStatusInfo("host($host)"); - if ( $StatusHost{backoffTime} > time ) { - $backoff = sprintf("%.1f", - ($StatusHost{backoffTime} - time) / 3600); - } - Header("BackupPC: Stop Backup Confirm on $host"); - print < -You are about to stop/dequeue backups on $host; - -
- - -Also, please don't start another backup for - hours. -

-Do you really want to do this? - - -

-EOF - } - Trailer(); - } -} - -sub Action_Queue -{ - my($strBg, $strUser, $strCmd); - - GetStatusInfo("queues"); - my $Privileged = CheckPermission(); - - if ( !$Privileged ) { - ErrorExit("Only privileged users can view queues." ); - } - - while ( @BgQueue ) { - my $req = pop(@BgQueue); - my($reqTime) = timeStamp2($req->{reqTime}); - $strBg .= < ${HostLink($req->{host})} - $reqTime - $req->{user} -EOF - } - while ( @UserQueue ) { - my $req = pop(@UserQueue); - my $reqTime = timeStamp2($req->{reqTime}); - $strUser .= < ${HostLink($req->{host})} - $reqTime - $req->{user} -EOF - } - while ( @CmdQueue ) { - my $req = pop(@CmdQueue); - my $reqTime = timeStamp2($req->{reqTime}); - (my $cmd = $req->{cmd}) =~ s/$BinDir\///; - $strCmd .= < ${HostLink($req->{host})} - $reqTime - $req->{user} - $cmd -EOF - } - Header("BackupPC: Queue Summary"); - print < -${h2("User Queue Summary")} -

-The following user requests are currently queued: - - - - -$strUser -
Host Req Time User
-

- -${h2("Background Queue Summary")} -

-The following background requests are currently queued: - - - - -$strBg -
Host Req Time User
-

- -${h2("Command Queue Summary")} -

-The following command requests are currently queued: - - - - - -$strCmd -
Host Req Time User Command
-EOF - Trailer(); -} - -sub Action_View -{ - my $Privileged = CheckPermission($In{host}); - my $compress = 0; - my $fh; - my $host = $In{host}; - my $num = $In{num}; - my $type = $In{type}; - my $linkHosts = 0; - my($file, $comment); - my $ext = $num ne "" ? ".$num" : ""; - - ErrorExit("Invalid number $num") if ( $num ne "" && $num !~ /^\d+$/ ); - if ( $type eq "XferLOG" ) { - $file = "$TopDir/pc/$host/SmbLOG$ext"; - $file = "$TopDir/pc/$host/XferLOG$ext" if ( !-f $file && !-f "$file.z"); - } elsif ( $type eq "XferLOGbad" ) { - $file = "$TopDir/pc/$host/SmbLOG.bad"; - $file = "$TopDir/pc/$host/XferLOG.bad" if ( !-f $file && !-f "$file.z"); - } elsif ( $type eq "XferErrbad" ) { - $file = "$TopDir/pc/$host/SmbLOG.bad"; - $file = "$TopDir/pc/$host/XferLOG.bad" if ( !-f $file && !-f "$file.z"); - $comment = "(Extracting only Errors)"; - } elsif ( $type eq "XferErr" ) { - $file = "$TopDir/pc/$host/SmbLOG$ext"; - $file = "$TopDir/pc/$host/XferLOG$ext" if ( !-f $file && !-f "$file.z"); - $comment = "(Extracting only Errors)"; - } elsif ( $type eq "RestoreLOG" ) { - $file = "$TopDir/pc/$host/RestoreLOG$ext"; - } elsif ( $type eq "RestoreErr" ) { - $file = "$TopDir/pc/$host/RestoreLOG$ext"; - $comment = "(Extracting only Errors)"; - } elsif ( $host ne "" && $type eq "config" ) { - $file = "$TopDir/pc/$host/config.pl"; - } elsif ( $type eq "docs" ) { - $file = "$BinDir/../doc/BackupPC.html"; - if ( open(LOG, $file) ) { - Header("BackupPC: Documentation"); - print while ( ); - close(LOG); - Trailer(); - } else { - ErrorExit("Unable to open $file: configuration problem?"); - } - return; - } elsif ( $type eq "config" ) { - $file = "$TopDir/conf/config.pl"; - } elsif ( $type eq "hosts" ) { - $file = "$TopDir/conf/hosts"; - } elsif ( $host ne "" ) { - $file = "$TopDir/pc/$host/LOG$ext"; - } else { - $file = "$TopDir/log/LOG$ext"; - $linkHosts = 1; - } - if ( !$Privileged ) { - ErrorExit("Only privileged users can view log or config files." ); - } - if ( !-f $file && -f "$file.z" ) { - $file .= ".z"; - $compress = 1; - } - Header("BackupPC: Log File $file"); - print < -EOF - if ( defined($fh = BackupPC::FileZIO->open($file, 0, $compress)) ) { - my $mtimeStr = $bpc->timeStamp((stat($file))[9], 1); - print <$file, modified $mtimeStr $comment -EOF - print "

";
-        if ( $type eq "XferErr" || $type eq "XferErrbad"
-				|| $type eq "RestoreErr" ) {
-	    my $skipped;
-            while ( 1 ) {
-                $_ = $fh->readLine();
-                if ( $_ eq "" ) {
-		    print("[ skipped $skipped lines ]\n") if ( $skipped );
-		    last;
-		}
-                if ( /smb: \\>/
-                        || /^\s*(\d+) \(\s*\d+\.\d kb\/s\) (.*)$/
-                        || /^tar: dumped \d+ files/
-                        || /^added interface/i
-                        || /^restore tar file /i
-                        || /^restore directory /i
-                        || /^tarmode is now/i
-                        || /^Total bytes written/i
-                        || /^Domain=/i
-                        || /^Getting files newer than/i
-                        || /^Output is \/dev\/null/
-                        || /^\([\d\.]* kb\/s\) \(average [\d\.]* kb\/s\)$/
-                        || /^\s+directory \\/
-                        || /^Timezone is/
-                        || /^\.\//
-			    ) {
-		    $skipped++;
-		    next;
-		}
-		print("[ skipped $skipped lines ]\n") if ( $skipped );
-		$skipped = 0;
-                print ${EscapeHTML($_)};
-            }
-        } elsif ( $linkHosts ) {
-            while ( 1 ) {
-                $_ = $fh->readLine();
-                last if ( $_ eq "" );
-                my $s = ${EscapeHTML($_)};
-                $s =~ s/\b([\w-]+)\b/defined($Hosts->{$1})
-                                        ? ${HostLink($1)} : $1/eg;
-                print $s;
-            }
-        } elsif ( $type eq "config" ) {
-            while ( 1 ) {
-                $_ = $fh->readLine();
-                last if ( $_ eq "" );
-                # remove any passwords and user names
-                s/(SmbSharePasswd.*=.*['"]).*(['"])/$1$2/ig;
-                s/(SmbShareUserName.*=.*['"]).*(['"])/$1$2/ig;
-                s/(ServerMesgSecret.*=.*['"]).*(['"])/$1$2/ig;
-                print ${EscapeHTML($_)};
-            }
-        } else {
-            while ( 1 ) {
-                $_ = $fh->readLine();
-                last if ( $_ eq "" );
-                print ${EscapeHTML($_)};
-            }
-        }
-        $fh->close();
-    } else {
-        printf("
\nCan't open log file $file\n");
-    }
-    print <
-EOF
-    Trailer();
-}
-
-sub Action_LOGlist
-{
-    my $Privileged = CheckPermission($In{host});
-
-    if ( !$Privileged ) {
-        ErrorExit("Only privileged users can view log files.");
-    }
-    my $host = $In{host};
-    my($url0, $hdr, $root, $str);
-    if ( $host ne "" ) {
-        $root = "$TopDir/pc/$host/LOG";
-        $url0 = "&host=$host";
-        $hdr = "for host $host";
-    } else {
-        $root = "$TopDir/log/LOG";
-        $url0 = "";
-        $hdr = "";
-    }
-    for ( my $i = -1 ; ; $i++ ) {
-        my $url1 = "";
-        my $file = $root;
-        if ( $i >= 0 ) {
-            $file .= ".$i";
-            $url1 = "&num=$i";
-        }
-        $file .= ".z" if ( !-f $file && -f "$file.z" );
-        last if ( !-f $file );
-        my $mtimeStr = $bpc->timeStamp((stat($file))[9], 1);
-        my $size     = (stat($file))[7];
-        $str .= < $file 
-     $size 
-     $mtimeStr 
-EOF
-    }
-    Header("BackupPC: Log File History");
-    print <
-
-
-    
-    
-$str
-
File Size Modification time
-EOF - Trailer(); -} - -sub Action_EmailSummary -{ - my $Privileged = CheckPermission(); - - if ( !$Privileged ) { - ErrorExit("Only privileged users can view email summaries." ); - } - GetStatusInfo("hosts"); - ReadUserEmailInfo(); - my(%EmailStr, $str); - foreach my $u ( keys(%UserEmailInfo) ) { - next if ( !defined($UserEmailInfo{$u}{lastTime}) ); - my $emailTimeStr = timeStamp2($UserEmailInfo{$u}{lastTime}); - $EmailStr{$UserEmailInfo{$u}{lastTime}} .= <${UserLink($u)} - ${HostLink($UserEmailInfo{$u}{lastHost})} - $emailTimeStr - $UserEmailInfo{$u}{lastSubj} -EOF - } - foreach my $t ( sort({$b <=> $a} keys(%EmailStr)) ) { - $str .= $EmailStr{$t}; - } - Header("BackupPC: Email Summary"); - print < - - - - - -$str -
Recipient Host Time Subject
-EOF - Trailer(); -} - -sub Action_Browse -{ - my $Privileged = CheckPermission($In{host}); - my($i, $dirStr, $fileStr, $mangle); - my($numF, $compressF, $mangleF, $fullDirF); - my $checkBoxCnt = 0; # checkbox counter - - if ( !$Privileged ) { - ErrorExit("Only privileged users can browse backup files" - . " for host ${EscapeHTML($In{host})}." ); - } - my $host = $In{host}; - my $num = $In{num}; - my $dir = $In{dir}; - if ( $host eq "" ) { - ErrorExit("Empty host name."); - } - # - # Find the requested backup and the previous filled backup - # - my @Backups = $bpc->BackupInfoRead($host); - for ( $i = 0 ; $i < @Backups ; $i++ ) { - if ( !$Backups[$i]{noFill} ) { - $numF = $Backups[$i]{num}; - $mangleF = $Backups[$i]{mangle}; - $compressF = $Backups[$i]{compress}; - } - last if ( $Backups[$i]{num} == $num ); - } - if ( $i >= @Backups ) { - ErrorExit("Backup number $num for host ${EscapeHTML($host)} does" - . " not exist."); - } - if ( !$Backups[$i]{noFill} ) { - # no need to back-fill a filled backup - $numF = $mangleF = $compressF = undef; - } - my $backupTime = timeStamp2($Backups[$i]{startTime}); - my $backupAge = sprintf("%.1f", (time - $Backups[$i]{startTime}) - / (24 * 3600)); - $mangle = $Backups[$i]{mangle}; - if ( $dir eq "" || $dir eq "." || $dir eq ".." ) { - if ( !opendir(DIR, "$TopDir/pc/$host/$num") ) { - ErrorExit("Can't browse bad directory name" - . " ${EscapeHTML(\"$TopDir/pc/$host/$num\")}"); - } - # - # Read this directory and find the first directory - # - foreach my $f ( readdir(DIR) ) { - next if ( $f eq "." || $f eq ".." ); - if ( -d "$TopDir/pc/$host/$num/$f" ) { - $dir = "/$f"; - last; - } - } - closedir(DIR); - if ( $dir eq "" || $dir eq "." || $dir eq ".." ) { - ErrorExit("Directory ${EscapeHTML(\"$TopDir/pc/$host/$num\")}" - . " is empty"); - } - } - my $relDir = $dir; - my $fullDir = "$TopDir/pc/$host/$num/$relDir"; - if ( defined($numF) ) { - # get full path to filled backup - if ( $mangle && !$mangleF ) { - $fullDirF = "$TopDir/pc/$host/$numF/" - . $bpc->fileNameUnmangle($relDir); - } else { - $fullDirF = "$TopDir/pc/$host/$numF/$relDir"; - } - } - my $currDir = undef; - # - # Read attributes for the directory and optionally for the filled backup - # - my $attr = BackupPC::Attrib->new({ compress => $Backups[$i]{compress}}); - my $attrF = BackupPC::Attrib->new({ compress => $compressF}) - if ( defined($numF) ); - $attr->read($fullDir) if ( -f $attr->fileName($fullDir) ); - if ( defined($numF) && -f $attrF->fileName($fullDirF) - && $attrF->read($fullDirF) ) { - $attr->merge($attrF); - } - # - # Loop up the directory tree until we hit the top. - # - my(@DirStrPrev); - while ( 1 ) { - my($fLast, $fum, $fLastum, @DirStr); - - if ( $fullDir =~ m{(^|/)\.\.(/|$)} || !opendir(DIR, $fullDir) ) { - ErrorExit("Can't browse bad directory name" - . " ${EscapeHTML($fullDir)}"); - } - # - # Read this directory and optionally the corresponding filled directory - # - my @Dir = readdir(DIR); - closedir(DIR); - if ( defined($numF) && opendir(DIR, $fullDirF) ) { - if ( $mangle == $mangleF ) { - @Dir = (@Dir, readdir(DIR)); - } else { - foreach my $f ( readdir(DIR) ) { - next if ( $f eq "." || $f eq ".." ); - push(@Dir, $bpc->fileNameMangle($f)); - } - } - closedir(DIR); - } - my $fileCnt = 0; # file counter - $fLast = $dirStr = ""; - # - # Loop over each of the files in this directory - # - my(@DirUniq); - foreach my $f ( sort({uc($a) cmp uc($b)} @Dir) ) { - next if ( $f eq "." || $f eq ".." - || $f eq $fLast || ($mangle && $f eq "attrib") ); - $fLast = $f; - push(@DirUniq, $f); - } - while ( defined(my $f = shift(@DirUniq)) ) { - my $path = "$relDir/$f"; - my($dirOpen, $gotDir, $imgStr, $img); - $fum = $mangle ? $bpc->fileNameUnmangle($f) : $f; # unmangled $f - my $fumURI = $fum; # URI escaped $f - $path =~ s{^/+}{/}; - $path =~ s/([^\w.\/-])/uc sprintf("%%%02x", ord($1))/eg; - $fumURI =~ s/([^\w.\/-])/uc sprintf("%%%02x", ord($1))/eg; - $dirOpen = 1 if ( defined($currDir) && $f eq $currDir ); - if ( -d "$fullDir/$f" ) { - # - # Display directory if it exists in current backup. - # First find out if there are subdirs - # - my @s = (defined($numF) && -d "$fullDirF/$f") - ? stat("$fullDirF/$f") - : stat("$fullDir/$f"); - my($bold, $unbold, $BGcolor); - $img |= 1 << 6; - $img |= 1 << 5 if ( $s[3] > 2 ); - if ( $dirOpen ) { - $bold = ""; - $unbold = ""; - $img |= 1 << 2; - $img |= 1 << 3 if ( $s[3] > 2 ); - } - my $imgFileName = sprintf("%07b.gif", $img); - $imgStr = ""; - if ( "$relDir/$f" eq $dir ) { - $BGcolor = " bgcolor=\"$Conf{CgiHeaderBgColor}\""; - } else { - $BGcolor = ""; - } - my $dirName = $fum; - $dirName =~ s/ / /g; - push(@DirStr, {needTick => 1, - tdArgs => $BGcolor, - link => <$imgStr $bold$dirName$unbold -EOF - $fileCnt++; - $gotDir = 1; - if ( $dirOpen ) { - my($lastTick, $doneLastTick); - foreach my $d ( @DirStrPrev ) { - $lastTick = $d if ( $d->{needTick} ); - } - $doneLastTick = 1 if ( !defined($lastTick) ); - foreach my $d ( @DirStrPrev ) { - $img = 0; - if ( $d->{needTick} ) { - $img |= 1 << 0; - } - if ( $d == $lastTick ) { - $img |= 1 << 4; - $doneLastTick = 1; - } elsif ( !$doneLastTick ) { - $img |= 1 << 3 | 1 << 4; - } - my $imgFileName = sprintf("%07b.gif", $img); - $imgStr = ""; - push(@DirStr, {needTick => 0, - tdArgs => $d->{tdArgs}, - link => $imgStr . $d->{link} - }); - } - } - } - if ( $relDir eq $dir ) { - # - # This is the selected directory, so display all the files - # - my $attrStr; - if ( defined($a = $attr->get($fum)) ) { - my $mtimeStr = $bpc->timeStamp($a->{mtime}); - my $typeStr = $attr->fileType2Text($a->{type}); - my $modeStr = sprintf("0%o", $a->{mode} & 07777); - $attrStr .= <$typeStr - $modeStr - $a->{size} - $mtimeStr - -EOF - } else { - $attrStr .= " \n"; - } - if ( $gotDir ) { - $fileStr .= < ${EscapeHTML($fum)} -$attrStr - -EOF - } else { - $fileStr .= < ${EscapeHTML($fum)} -$attrStr - -EOF - } - $checkBoxCnt++; - } - } - @DirStrPrev = @DirStr; - last if ( $relDir eq "" ); - # - # Prune the last directory off $relDir - # - $relDir =~ s/(.*)\/(.*)/$1/; - $currDir = $2; - $fullDir = "$TopDir/pc/$host/$num/$relDir"; - $fullDirF = "$TopDir/pc/$host/$numF/$relDir" if ( defined($numF) ); - } - my $dirDisplay = $mangle ? $bpc->fileNameUnmangle($dir) : $dir; - $dirDisplay =~ s{//}{/}g; - my $filledBackup; - if ( defined($numF) ) { - $filledBackup = < This display is merged with backup #$numF, the most recent prior - filled (full) dump. -EOF - } - Header("BackupPC: Browse backup $num for $host"); - - foreach my $d ( @DirStrPrev ) { - $dirStr .= "{tdArgs}>$d->{link}\n"; - } - - ### hide checkall button if there are no files - my ($topCheckAll, $checkAll, $fileHeader); - if ( $fileStr ) { - $fileHeader = < Name - Type - Mode - Size - Mod time - -EOF - $checkAll = < - Select all - - - -EOF - # and put a checkall box on top if there are at least 20 files - if ( $checkBoxCnt >= 20 ) { - $topCheckAll = $checkAll; - $topCheckAll =~ s{allFiles}{allFilestop}g; - } - } else { - $fileStr = <The directory ${EscapeHTML($dirDisplay)} is empty - -EOF - } - - print < - - - -
    -
  • You are browsing backup #$num, which started around $backupTime - ($backupAge days ago), -$filledBackup -
  • Click on a directory below to navigate into that directory, -
  • Click on a file below to restore that file. -
- -${h2("Contents of ${EscapeHTML($dirDisplay)}")} -
- - - - -
- -
- -
- $dirStr -
-
- - -
-
- - $fileHeader - $topCheckAll - $fileStr - $checkAll -
-
-
- -
-
-EOF - Trailer(); -} - -sub Action_Restore -{ - my($str, $reply, $i); - my $Privileged = CheckPermission($In{host}); - if ( !$Privileged ) { - ErrorExit("Only privileged users can restore backup files" - . " for host ${EscapeHTML($In{host})}." ); - } - my $host = $In{host}; - my $num = $In{num}; - my(@fileList, $fileListStr, $hiddenStr, $share, $pathHdr, $badFileCnt); - my @Backups = $bpc->BackupInfoRead($host); - for ( $i = 0 ; $i < @Backups ; $i++ ) { - last if ( $Backups[$i]{num} == $num ); - } - my $mangle = $Backups[$i]{mangle}; - ServerConnect(); - - if ( !defined($Hosts->{$host}) ) { - ErrorExit("Bad host name ${EscapeHTML($host)}"); - } - for ( my $i = 0 ; $i < $In{fcbMax} ; $i++ ) { - next if ( !defined($In{"fcb$i"}) ); - (my $name = $In{"fcb$i"}) =~ s/%([0-9A-F]{2})/chr(hex($1))/eg; - $badFileCnt++ if ( $name =~ m{(^|/)\.\.(/|$)} ); - if ( $name =~ m{^/+(.*?)(/.*)} ) { - $share = $1; - $name = $mangle ? $bpc->fileNameUnmangle($2) : $2; - if ( @fileList == 0 ) { - $pathHdr = $name; - } else { - while ( substr($name, 0, length($pathHdr)) ne $pathHdr ) { - $pathHdr = substr($pathHdr, 0, rindex($pathHdr, "/")); - } - } - } - push(@fileList, $name); - $share = $mangle ? $bpc->fileNameUnmangle($share) : $share; - $hiddenStr .= < -EOF - $fileListStr .= < ${EscapeHTML($name)} -EOF - } - $hiddenStr .= "\n"; - $badFileCnt++ if ( $In{pathHdr} =~ m{(^|/)\.\.(/|$)} ); - $badFileCnt++ if ( $In{num} =~ m{(^|/)\.\.(/|$)} ); - if ( @fileList == 0 ) { - ErrorExit("You haven't selected any files; please go Back to" - . " select some files."); - } - if ( $badFileCnt ) { - ErrorExit("Nice try, but you can't put '..' in any of the file names"); - } - if ( @fileList == 1 ) { - $pathHdr =~ s/(.*)\/.*/$1/; - } - $pathHdr = "/" if ( $pathHdr eq "" ); - if ( $In{type} != 0 && @fileList == $In{fcbMax} ) { - # - # All the files in the list were selected, so just restore the - # entire parent directory - # - @fileList = ( $pathHdr ); - } - if ( $In{type} == 0 ) { - # - # Tell the user what options they have - # - Header("BackupPC: Restore Options for $host"); - print < -You have selected the following files/directories from -share $share, backup number #$num: -
    -$fileListStr -
-

-You have three choices for restoring these files/directories. -Please select one of the following options. -

-${h2("Option 1: Direct Restore")} -

-You can start a restore that will restore these files directly onto -$host. -

-Warning: any existing files that match the ones you have -selected will be overwritten! - -

- - - -$hiddenStr - - - - - - - - - - - - - -
Restore the files to host
Restore the files to share
Restore the files below dir
(relative to share)
-
-EOF - - # - # Verify that Archive::Zip is available before showing the - # zip restore option - # - if ( eval { require Archive::Zip } ) { - print < -You can download a Zip archive containing all the files/directories you have -selected. You can then use a local application, such as WinZip, -to view or extract any of the files. -

-Warning: depending upon which files/directories you have selected, -this archive might be very very large. It might take many minutes to -create and transfer the archive, and you will need enough local disk -space to store it. -

-

- - - -$hiddenStr - - Make archive relative -to ${EscapeHTML($pathHdr eq "" ? "/" : $pathHdr)} -(otherwise archive will contain full paths). -
-Compression (0=off, 1=fast,...,9=best) - -
- -
-EOF - } else { - print < -You could download a zip archive, but Archive::Zip is not installed. -Please ask your system adminstrator to install Archive::Zip from -www.cpan.org. -

-EOF - } - print < -You can download a Tar archive containing all the files/directories you -have selected. You can then use a local application, such as tar or -WinZip to view or extract any of the files. -

-Warning: depending upon which files/directories you have selected, -this archive might be very very large. It might take many minutes to -create and transfer the archive, and you will need enough local disk -space to store it. -

-

- - - -$hiddenStr - - Make archive relative -to ${EscapeHTML($pathHdr eq "" ? "/" : $pathHdr)} -(otherwise archive will contain full paths). -
- -
-EOF - Trailer(); - } elsif ( $In{type} == 1 ) { - # - # Provide the selected files via a tar archive. - # - $SIG{CHLD} = 'IGNORE'; - my $pid = fork(); - if ( !defined($pid) ) { - $bpc->ServerMesg("log Can't fork for tar restore request by $User"); - ErrorExit("Can't fork for tar restore"); - } - if ( $pid ) { - # - # This is the parent. - # - my @fileListTrim = @fileList; - if ( @fileListTrim > 10 ) { - @fileListTrim = (@fileListTrim[0..9], '...'); - } - $bpc->ServerMesg("log User $User downloaded tar archive for $host," - . " backup $num; files were: " - . join(", ", @fileListTrim)); - return; - } - # - # This is the child. Print the headers and run BackupPC_tarCreate. - # - my @pathOpts; - if ( $In{relative} ) { - @pathOpts = ("-r", $pathHdr, "-p", ""); - } - $bpc->ServerDisconnect(); - print "Content-Type: application/x-gtar\n"; - print "Content-Transfer-Encoding: binary\n"; - print "Content-Disposition: attachment; filename=\"restore.tar\"\n\n"; - exec("$BinDir/BackupPC_tarCreate", - "-h", $host, - "-n", $num, - "-s", $share, - @pathOpts, - @fileList - ); - } elsif ( $In{type} == 2 ) { - # - # Provide the selected files via a zip archive. - # - $SIG{CHLD} = 'IGNORE'; - my $pid = fork(); - if ( !defined($pid) ) { - $bpc->ServerMesg("log Can't fork for zip restore request by $User"); - ErrorExit("Can't fork for zip restore"); - } - if ( $pid ) { - # - # This is the parent. - # - my @fileListTrim = @fileList; - if ( @fileListTrim > 10 ) { - @fileListTrim = (@fileListTrim[0..9], '...'); - } - $bpc->ServerMesg("log User $User downloaded zip archive for $host," - . " backup $num; files were: " - . join(", ", @fileListTrim)); - return; - } - # - # This is the child. Print the headers and run BackupPC_tarCreate. - # - my @pathOpts; - if ( $In{relative} ) { - @pathOpts = ("-r", $pathHdr, "-p", ""); - } - $bpc->ServerDisconnect(); - print "Content-Type: application/zip\n"; - print "Content-Transfer-Encoding: binary\n"; - print "Content-Disposition: attachment; filename=\"restore.zip\"\n\n"; - $In{compressLevel} = 5 if ( $In{compressLevel} !~ /^\d+$/ ); - exec("$BinDir/BackupPC_zipCreate", - "-h", $host, - "-n", $num, - "-c", $In{compressLevel}, - "-s", $share, - @pathOpts, - @fileList - ); - } elsif ( $In{type} == 3 ) { - # - # Do restore directly onto host - # - if ( !defined($Hosts->{$In{hostDest}}) ) { - ErrorExit("Host ${EscapeHTML($In{hostDest})} doesn't exist"); - } - if ( !CheckPermission($In{hostDest}) ) { - ErrorExit("You don't have permission to restore onto host" - . " ${EscapeHTML($In{hostDest})}"); - } - $fileListStr = ""; - foreach my $f ( @fileList ) { - my $targetFile = $f; - (my $strippedShare = $share) =~ s/^\///; - (my $strippedShareDest = $In{shareDest}) =~ s/^\///; - substr($targetFile, 0, length($pathHdr)) = $In{pathHdr}; - $fileListStr .= <$host:/$strippedShare$f$In{hostDest}:/$strippedShareDest$targetFile -EOF - } - Header("BackupPC: Restore Confirm on $host"); - print < -You are about to start a restore directly to the machine $In{hostDest}. -The following files will be restored to share $In{shareDest}, from -backup number $num: -

- - -$fileListStr -
Original file/dirWill be restored to
- -

- - - - - - -$hiddenStr -Do you really want to do this? - - -
-EOF - Trailer(); - } elsif ( $In{type} == 4 ) { - if ( !defined($Hosts->{$In{hostDest}}) ) { - ErrorExit("Host ${EscapeHTML($In{hostDest})} doesn't exist"); - } - if ( !CheckPermission($In{hostDest}) ) { - ErrorExit("You don't have permission to restore onto host" - . " ${EscapeHTML($In{hostDest})}"); - } - my $hostDest = $1 if ( $In{hostDest} =~ /(.+)/ ); - my $ipAddr = ConfirmIPAddress($hostDest); - # - # Prepare and send the restore request. We write the request - # information using Data::Dumper to a unique file, - # $TopDir/pc/$hostDest/restoreReq.$$.n. We use a file - # in case the list of files to restore is very long. - # - my $reqFileName; - for ( my $i = 0 ; ; $i++ ) { - $reqFileName = "restoreReq.$$.$i"; - last if ( !-f "$TopDir/pc/$hostDest/$reqFileName" ); - } - my %restoreReq = ( - # source of restore is hostSrc, #num, path shareSrc/pathHdrSrc - num => $In{num}, - hostSrc => $host, - shareSrc => $share, - pathHdrSrc => $pathHdr, - - # destination of restore is hostDest:shareDest/pathHdrDest - hostDest => $hostDest, - shareDest => $In{shareDest}, - pathHdrDest => $In{pathHdr}, - - # list of files to restore - fileList => \@fileList, - - # other info - user => $User, - reqTime => time, - ); - my($dump) = Data::Dumper->new( - [ \%restoreReq], - [qw(*RestoreReq)]); - $dump->Indent(1); - if ( open(REQ, ">$TopDir/pc/$hostDest/$reqFileName") ) { - print(REQ $dump->Dump); - close(REQ); - } else { - ErrorExit("Can't open/create " - . ${EscapeHTML("$TopDir/pc/$hostDest/$reqFileName")}); - } - $reply = $bpc->ServerMesg("restore $ipAddr" - . " $hostDest $User $reqFileName"); - $str = "Restore requested to host $hostDest, backup #$num," - . " by $User from $ENV{REMOTE_ADDR}"; - Header("BackupPC: Restore Requested on $hostDest"); - print < -Reply from server was: $reply -

-Go back to $hostDest home page. -EOF - Trailer(); - } -} - -sub Action_RestoreFile -{ - restoreFile($In{host}, $In{num}, $In{dir}); -} - -sub restoreFile -{ - my($host, $num, $dir, $skipHardLink, $origName) = @_; - my($Privileged) = CheckPermission($host); - my($i, $numF, $mangleF, $compressF, $mangle, $compress, $dirUM); - # - # Some common content (media) types from www.iana.org (via MIME::Types). - # - my $Ext2ContentType = { - 'asc' => 'text/plain', - 'avi' => 'video/x-msvideo', - 'bmp' => 'image/bmp', - 'book' => 'application/x-maker', - 'cc' => 'text/plain', - 'cpp' => 'text/plain', - 'csh' => 'application/x-csh', - 'csv' => 'text/comma-separated-values', - 'c' => 'text/plain', - 'deb' => 'application/x-debian-package', - 'doc' => 'application/msword', - 'dot' => 'application/msword', - 'dtd' => 'text/xml', - 'dvi' => 'application/x-dvi', - 'eps' => 'application/postscript', - 'fb' => 'application/x-maker', - 'fbdoc'=> 'application/x-maker', - 'fm' => 'application/x-maker', - 'frame'=> 'application/x-maker', - 'frm' => 'application/x-maker', - 'gif' => 'image/gif', - 'gtar' => 'application/x-gtar', - 'gz' => 'application/x-gzip', - 'hh' => 'text/plain', - 'hpp' => 'text/plain', - 'h' => 'text/plain', - 'html' => 'text/html', - 'htmlx'=> 'text/html', - 'htm' => 'text/html', - 'iges' => 'model/iges', - 'igs' => 'model/iges', - 'jpeg' => 'image/jpeg', - 'jpe' => 'image/jpeg', - 'jpg' => 'image/jpeg', - 'js' => 'application/x-javascript', - 'latex'=> 'application/x-latex', - 'maker'=> 'application/x-maker', - 'mid' => 'audio/midi', - 'midi' => 'audio/midi', - 'movie'=> 'video/x-sgi-movie', - 'mov' => 'video/quicktime', - 'mp2' => 'audio/mpeg', - 'mp3' => 'audio/mpeg', - 'mpeg' => 'video/mpeg', - 'mpg' => 'video/mpeg', - 'mpp' => 'application/vnd.ms-project', - 'pdf' => 'application/pdf', - 'pgp' => 'application/pgp-signature', - 'php' => 'application/x-httpd-php', - 'pht' => 'application/x-httpd-php', - 'phtml'=> 'application/x-httpd-php', - 'png' => 'image/png', - 'ppm' => 'image/x-portable-pixmap', - 'ppt' => 'application/powerpoint', - 'ppt' => 'application/vnd.ms-powerpoint', - 'ps' => 'application/postscript', - 'qt' => 'video/quicktime', - 'rgb' => 'image/x-rgb', - 'rtf' => 'application/rtf', - 'rtf' => 'text/rtf', - 'shar' => 'application/x-shar', - 'shtml'=> 'text/html', - 'swf' => 'application/x-shockwave-flash', - 'tex' => 'application/x-tex', - 'texi' => 'application/x-texinfo', - 'texinfo'=> 'application/x-texinfo', - 'tgz' => 'application/x-gtar', - 'tiff' => 'image/tiff', - 'tif' => 'image/tiff', - 'txt' => 'text/plain', - 'vcf' => 'text/x-vCard', - 'vrml' => 'model/vrml', - 'wav' => 'audio/x-wav', - 'wmls' => 'text/vnd.wap.wmlscript', - 'wml' => 'text/vnd.wap.wml', - 'wrl' => 'model/vrml', - 'xls' => 'application/vnd.ms-excel', - 'xml' => 'text/xml', - 'xwd' => 'image/x-xwindowdump', - 'z' => 'application/x-compress', - 'zip' => 'application/zip', - }; - - if ( !$Privileged ) { - ErrorExit("Only privileged users can restore backup files" - . " for host ${EscapeHTML($host)}." ); - } - ServerConnect(); - my @Backups = $bpc->BackupInfoRead($host); - if ( $host eq "" ) { - ErrorExit("Empty host name"); - } - $dir = "/" if ( $dir eq "" ); - for ( $i = 0 ; $i < @Backups ; $i++ ) { - if ( !$Backups[$i]{noFill} ) { - $numF = $Backups[$i]{num}; - $mangleF = $Backups[$i]{mangle}; - $compressF = $Backups[$i]{compress}; - } - last if ( $Backups[$i]{num} == $num ); - } - $mangle = $Backups[$i]{mangle}; - $compress = $Backups[$i]{compress}; - if ( !$Backups[$i]{noFill} ) { - # no need to back-fill a filled backup - $numF = $mangleF = $compressF = undef; - } - my $fullPath = "$TopDir/pc/$host/$num/$dir"; - $fullPath =~ s{/+}{/}g; - if ( !-f $fullPath && defined($numF) ) { - my $dirF = $dir; - my $fullPathF; - if ( $mangle && !$mangleF ) { - $fullPathF = "$TopDir/pc/$host/$numF/" - . $bpc->fileNameUnmangle($dir); - } else { - $fullPathF = "$TopDir/pc/$host/$numF/$dir"; - } - if ( -f $fullPathF ) { - $fullPath = $fullPathF; - $compress = $compressF; - } - } - if ( $fullPath =~ m{(^|/)\.\.(/|$)} || !-f $fullPath ) { - ErrorExit("Can't restore bad file ${EscapeHTML($fullPath)}"); - } - my $dirUM = $mangle ? $bpc->fileNameUnmangle($dir) : $dir; - my $attr = BackupPC::Attrib->new({compress => $compress}); - my $fullDir = $fullPath; - $fullDir =~ s{(.*)/.*}{$1}; - my $fileName = $1 if ( $dirUM =~ /.*\/(.*)/ ); - $attr->read($fullDir) if ( -f $attr->fileName($fullDir) ); - my $a = $attr->get($fileName); - - my $f = BackupPC::FileZIO->open($fullPath, 0, $compress); - my $data; - if ( !$skipHardLink && $a->{type} == BPC_FTYPE_HARDLINK ) { - # - # hardlinks should look like the file they point to - # - my $linkName; - while ( $f->read(\$data, 65536) > 0 ) { - $linkName .= $data; - } - $f->close; - $linkName =~ s/^\.\///; - my $share = $1 if ( $dir =~ /^\/?(.*?)\// ); - restoreFile($host, $num, - "$share/" . ($mangle ? $bpc->fileNameMangle($linkName) - : $linkName), 1, $dir); - return; - } - $dirUM =~ s{//}{/}g; - $fullPath =~ s{//}{/}g; - $bpc->ServerMesg("log User $User recovered file $dirUM ($fullPath)"); - $dir = $origName if ( defined($origName) ); - $dirUM = $mangle ? $bpc->fileNameUnmangle($dir) : $dir; - my $ext = $1 if ( $dirUM =~ /\.([^\/\.]+)$/ ); - my $contentType = $Ext2ContentType->{lc($ext)} - || "application/octet-stream"; - $fileName = $1 if ( $dirUM =~ /.*\/(.*)/ ); - $fileName =~ s/"/\\"/g; - print "Content-Type: $contentType\n"; - print "Content-Transfer-Encoding: binary\n"; - print "Content-Disposition: attachment; filename=\"$fileName\"\n\n"; - while ( $f->read(\$data, 1024 * 1024) > 0 ) { - print STDOUT $data; - } - $f->close; -} - -sub Action_HostInfo -{ - my $host = $1 if ( $In{host} =~ /(.*)/ ); - my($statusStr, $startIncrStr); - - $host =~ s/^\s+//; - $host =~ s/\s+$//; - return Action_GeneralInfo() if ( $host eq "" ); - $host = lc($host) - if ( !-d "$TopDir/pc/$host" && -d "$TopDir/pc/" . lc($host) ); - if ( $host =~ /\.\./ || !-d "$TopDir/pc/$host" ) { - # - # try to lookup by user name - # - if ( !defined($Hosts->{$host}) ) { - foreach my $h ( keys(%$Hosts) ) { - if ( $Hosts->{$h}{user} eq $host - || lc($Hosts->{$h}{user}) eq lc($host) ) { - $host = $h; - last; - } - } - CheckPermission(); - ErrorExit("Unknown host or user ${EscapeHTML($host)}") - if ( !defined($Hosts->{$host}) ); - } - $In{host} = $host; - } - GetStatusInfo("host($host)"); - $bpc->ConfigRead($host); - %Conf = $bpc->Conf(); - my $Privileged = CheckPermission($host); - if ( !$Privileged ) { - ErrorExit("Only privileged users can view information about" - . " host ${EscapeHTML($host)}." ); - } - ReadUserEmailInfo(); - - my @Backups = $bpc->BackupInfoRead($host); - my($str, $sizeStr, $compStr, $errStr, $warnStr); - for ( my $i = 0 ; $i < @Backups ; $i++ ) { - my $startTime = timeStamp2($Backups[$i]{startTime}); - my $dur = $Backups[$i]{endTime} - $Backups[$i]{startTime}; - $dur = 1 if ( $dur <= 0 ); - my $duration = sprintf("%.1f", $dur / 60); - my $MB = sprintf("%.1f", $Backups[$i]{size} / (1024*1024)); - my $MBperSec = sprintf("%.2f", $Backups[$i]{size} / (1024*1024*$dur)); - my $MBExist = sprintf("%.1f", $Backups[$i]{sizeExist} / (1024*1024)); - my $MBNew = sprintf("%.1f", $Backups[$i]{sizeNew} / (1024*1024)); - my($MBExistComp, $ExistComp, $MBNewComp, $NewComp); - if ( $Backups[$i]{sizeExist} && $Backups[$i]{sizeExistComp} ) { - $MBExistComp = sprintf("%.1f", $Backups[$i]{sizeExistComp} - / (1024 * 1024)); - $ExistComp = sprintf("%.1f%%", 100 * - (1 - $Backups[$i]{sizeExistComp} / $Backups[$i]{sizeExist})); - } - if ( $Backups[$i]{sizeNew} && $Backups[$i]{sizeNewComp} ) { - $MBNewComp = sprintf("%.1f", $Backups[$i]{sizeNewComp} - / (1024 * 1024)); - $NewComp = sprintf("%.1f%%", 100 * - (1 - $Backups[$i]{sizeNewComp} / $Backups[$i]{sizeNew})); - } - my $age = sprintf("%.1f", (time - $Backups[$i]{startTime}) / (24*3600)); - my $browseURL = "$MyURL?action=browse&host=$host&num=$Backups[$i]{num}"; - my $filled = $Backups[$i]{noFill} ? "no" : "yes"; - $filled .= " ($Backups[$i]{fillFromNum}) " - if ( $Backups[$i]{fillFromNum} ne "" ); - $str .= < $Backups[$i]{num} - $Backups[$i]{type} - $filled - $startTime - $duration - $age - $TopDir/pc/$host/$Backups[$i]{num} -EOF - $sizeStr .= < $Backups[$i]{num} - $Backups[$i]{type} - $Backups[$i]{nFiles} - $MB - $MBperSec - $Backups[$i]{nFilesExist} - $MBExist - $Backups[$i]{nFilesNew} - $MBNew - -EOF - $Backups[$i]{compress} ||= "off"; - $compStr .= < $Backups[$i]{num} - $Backups[$i]{type} - $Backups[$i]{compress} - $MBExist - $MBExistComp - $ExistComp - $MBNew - $MBNewComp - $NewComp - -EOF - $errStr .= < $Backups[$i]{num} - $Backups[$i]{type} - XferLOG, - Errors - $Backups[$i]{xferErrs} - $Backups[$i]{xferBadFile} - $Backups[$i]{xferBadShare} - $Backups[$i]{tarErrs} -EOF - } - - my @Restores = $bpc->RestoreInfoRead($host); - my $restoreStr; - - for ( my $i = 0 ; $i < @Restores ; $i++ ) { - my $startTime = timeStamp2($Restores[$i]{startTime}); - my $dur = $Restores[$i]{endTime} - $Restores[$i]{startTime}; - $dur = 1 if ( $dur <= 0 ); - my $duration = sprintf("%.1f", $dur / 60); - my $MB = sprintf("%.1f", $Restores[$i]{size} / (1024*1024)); - my $MBperSec = sprintf("%.2f", $Restores[$i]{size} / (1024*1024*$dur)); - $restoreStr .= <$Restores[$i]{num} - $Restores[$i]{result} - $startTime - $duration - $Restores[$i]{nFiles} - $MB - $Restores[$i]{tarCreateErrs} - $Restores[$i]{xferErrs} - -EOF - } - $restoreStr = < -Click on the restore number for more details. - - - - - - - - - - -$restoreStr -
Restore# Result Start Date Dur/mins #files MB #tar errs #xferErrs
-

-EOF - - if ( @Backups == 0 ) { - $warnStr = "

This PC has never been backed up!!

\n"; - } - if ( defined($Hosts->{$host}) ) { - my $user = $Hosts->{$host}{user}; - if ( $user ne "" ) { - $statusStr .= <This PC is used by ${UserLink($user)}. -EOF - } - if ( defined($UserEmailInfo{$user}) - && $UserEmailInfo{$user}{lastHost} eq $host ) { - my $mailTime = timeStamp2($UserEmailInfo{$user}{lastTime}); - my $subj = $UserEmailInfo{$user}{lastSubj}; - $statusStr .= <Last email sent to ${UserLink($user)} was at $mailTime, subject "$subj". -EOF - } - } - if ( defined($Jobs{$host}) ) { - my $startTime = timeStamp2($Jobs{$host}{startTime}); - (my $cmd = $Jobs{$host}{cmd}) =~ s/$BinDir\///g; - $statusStr .= <The command $cmd is currently running for $host, started $startTime. -EOF - } - if ( $StatusHost{BgQueueOn} ) { - $statusStr .= <Host $host is queued on the background queue (will be backed up soon). -EOF - } - if ( $StatusHost{UserQueueOn} ) { - $statusStr .= <Host $host is queued on the user queue (will be backed up soon). -EOF - } - if ( $StatusHost{CmdQueueOn} ) { - $statusStr .= <A command for $host is on the command queue (will run soon). -EOF - } - my $startTime = timeStamp2($StatusHost{endTime} == 0 ? - $StatusHost{startTime} : $StatusHost{endTime}); - my $reason = ""; - if ( $StatusHost{reason} ne "" ) { - $reason = " ($StatusHost{reason})"; - } - $statusStr .= <Last status is state "$StatusHost{state}"$reason - as of $startTime. -EOF - if ( $StatusHost{error} ne "" ) { - $statusStr .= <Last error is "${EscapeHTML($StatusHost{error})}" -EOF - } - my $priorStr = "Pings"; - if ( $StatusHost{deadCnt} > 0 ) { - $statusStr .= <Pings to $host have failed $StatusHost{deadCnt} consecutive times. -EOF - $priorStr = "Prior to that, pings"; - } - if ( $StatusHost{aliveCnt} > 0 ) { - $statusStr .= <$priorStr to $host have succeeded $StatusHost{aliveCnt} - consecutive times. -EOF - if ( $StatusHost{aliveCnt} >= $Conf{BlackoutGoodCnt} - && $Conf{BlackoutGoodCnt} >= 0 && $Conf{BlackoutHourBegin} >= 0 - && $Conf{BlackoutHourEnd} >= 0 ) { - my(@days) = qw(Sun Mon Tue Wed Thu Fri Sat); - my($days) = join(", ", @days[@{$Conf{BlackoutWeekDays}}]); - my($t0) = sprintf("%d:%02d", $Conf{BlackoutHourBegin}, - 60 * ($Conf{BlackoutHourBegin} - - int($Conf{BlackoutHourBegin}))); - my($t1) = sprintf("%d:%02d", $Conf{BlackoutHourEnd}, - 60 * ($Conf{BlackoutHourEnd} - - int($Conf{BlackoutHourEnd}))); - $statusStr .= <Because $host has been on the network at least $Conf{BlackoutGoodCnt} -consecutive times, it will not be backed up from $t0 to $t1 on $days. -EOF - } - } - if ( $StatusHost{backoffTime} > time ) { - my $hours = sprintf("%.1f", ($StatusHost{backoffTime} - time) / 3600); - $statusStr .= <Backups are deferred for $hours hours - (change this - number). -EOF - } - if ( @Backups ) { - # only allow incremental if there are already some backups - $startIncrStr = < -EOF - } - - Header("BackupPC: Host $host Backup Summary"); - print < -$warnStr -
    -$statusStr -
- -${h2("User Actions")} -

-

- -$startIncrStr - - -
- -${h2("Backup Summary")} -

-Click on the backup number to browse and restore backup files. - - - - - - - - - -$str -
Backup# Type Filled Start Date Duration/mins Age/days Server Backup Path
-

- -$restoreStr - -${h2("Xfer Error Summary")} -

- - - - - - - - - -$errStr -
Backup# Type View #Xfer errs #bad files #bad share #tar errs
-

- -${h2("File Size/Count Reuse Summary")} -

-Existing files are those already in the pool; new files are those added -to the pool. -Empty files and SMB errors aren't counted in the reuse and new counts. - - - - - - - - - - - - - - - - - -$sizeStr -
Totals Existing Files New Files
Backup# Type #Files Size/MB MB/sec #Files Size/MB #Files Size/MB
-

- -${h2("Compression Summary")} -

-Compression performance for files already in the pool and newly -compressed files. - - - - - - - - - - - - - - - -$compStr -
Existing Files New Files
Backup# Type Comp Level Size/MB Comp/MB Comp Size/MB Comp/MB Comp
-

-EOF - Trailer(); -} - -sub Action_GeneralInfo -{ - GetStatusInfo("info jobs hosts queueLen"); - my $Privileged = CheckPermission(); - - my($jobStr, $statusStr, $tarPidHdr, $ rivLinks); - foreach my $host ( sort(keys(%Jobs)) ) { - my $startTime = timeStamp2($Jobs{$host}{startTime}); - next if ( $host eq $bpc->trashJob - && $Jobs{$host}{processState} ne "running" ); - $Jobs{$host}{type} = $Status{$host}{type} - if ( $Jobs{$host}{type} eq "" && defined($Status{$host})); - (my $cmd = $Jobs{$host}{cmd}) =~ s/$BinDir\///g; - $jobStr .= < ${HostLink($host)} - $Jobs{$host}{type} - ${UserLink($Hosts->{$host}{user})} - $startTime - $cmd - $Jobs{$host}{pid} - $Jobs{$host}{xferPid} -EOF - if ( $Jobs{$host}{tarPid} > 0 ) { - $jobStr .= " $Jobs{$host}{tarPid} \n"; - $tarPidHdr ||= " tar PID \n"; - } - $jobStr .= "\n"; - } - foreach my $host ( sort(keys(%Status)) ) { - next if ( $Status{$host}{reason} ne "backup failed" ); - my $startTime = timeStamp2($Status{$host}{startTime}); - my($errorTime, $XferViewStr); - if ( $Status{$host}{errorTime} > 0 ) { - $errorTime = timeStamp2($Status{$host}{errorTime}); - } - 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" - ) { - $XferViewStr = <XferLOG, -XferErr -EOF - } else { - $XferViewStr = ""; - } - (my $shortErr = $Status{$host}{error}) =~ s/(.{48}).*/$1.../; - $statusStr .= < ${HostLink($host)} - $Status{$host}{type} - ${UserLink($Hosts->{$host}{user})} - $startTime - $XferViewStr - $errorTime - ${EscapeHTML($shortErr)} -EOF - } - my $now = timeStamp2(time); - my $nextWakeupTime = timeStamp2($Info{nextWakeup}); - my $DUlastTime = timeStamp2($Info{DUlastValueTime}); - my $DUmaxTime = timeStamp2($Info{DUDailyMaxTime}); - my $numBgQueue = $QueueLen{BgQueue}; - my $numUserQueue = $QueueLen{UserQueue}; - my $numCmdQueue = $QueueLen{CmdQueue}; - my $serverStartTime = timeStamp2($Info{startTime}); - my $poolInfo = genPoolInfo("pool", \%Info); - my $cpoolInfo = genPoolInfo("cpool", \%Info); - if ( $Info{poolFileCnt} > 0 && $Info{cpoolFileCnt} > 0 ) { - $poolInfo = <Uncompressed pool: -

    -$poolInfo -
-
  • Compressed pool: -
      -$cpoolInfo -
    -EOF - } elsif ( $Info{cpoolFileCnt} > 0 ) { - $poolInfo = $cpoolInfo; - } - Header("BackupPC: Server Status"); - print < - -${h2("General Server Information")} - -
      -
    • The server's PID is $Info{pid} on host $Conf{ServerHost}, - version $Info{Version}, started at $serverStartTime. -
    • This status was generated at $now. -
    • PCs will be next queued at $nextWakeupTime. -
    • Other info: -
        -
      • $numBgQueue pending backup requests from last scheduled wakeup, -
      • $numUserQueue pending user backup requests, -
      • $numCmdQueue pending command requests, - $poolInfo -
      • Pool file system was recently at $Info{DUlastValue}% - ($DUlastTime), today's max is $Info{DUDailyMax}% ($DUmaxTime) - and yesterday's max was $Info{DUDailyMaxPrev}%. -
      -
    - -${h2("Currently Running Jobs")} -

    - - - - - - - - - $tarPidHdr -$jobStr -
    Host Type User Start Time Command PID Xfer PID
    -

    - -${h2("Failures that need attention")} -

    - - - - - - - - -$statusStr -
    Host Type User Last Try Details Error Time Last error (other than no ping)
    -EOF - Trailer(); -} - -sub Action_RestoreInfo -{ - my $Privileged = CheckPermission($In{host}); - my $host = $1 if ( $In{host} =~ /(.*)/ ); - my $num = $In{num}; - my $i; - - if ( !$Privileged ) { - ErrorExit("Only privileged users can view restore information." ); - } - # - # Find the requested restore - # - my @Restores = $bpc->RestoreInfoRead($host); - for ( $i = 0 ; $i < @Restores ; $i++ ) { - last if ( $Restores[$i]{num} == $num ); - } - if ( $i >= @Restores ) { - ErrorExit("Restore number $num for host ${EscapeHTML($host)} does" - . " not exist."); - } - - %RestoreReq = (); - do "$TopDir/pc/$host/RestoreInfo.$Restores[$i]{num}" - if ( -f "$TopDir/pc/$host/RestoreInfo.$Restores[$i]{num}" ); - - my $startTime = timeStamp2($Restores[$i]{startTime}); - my $reqTime = timeStamp2($RestoreReq{reqTime}); - my $dur = $Restores[$i]{endTime} - $Restores[$i]{startTime}; - $dur = 1 if ( $dur <= 0 ); - my $duration = sprintf("%.1f", $dur / 60); - my $MB = sprintf("%.1f", $Restores[$i]{size} / (1024*1024)); - my $MBperSec = sprintf("%.2f", $Restores[$i]{size} / (1024*1024*$dur)); - - my $fileListStr = ""; - foreach my $f ( @{$RestoreReq{fileList}} ) { - my $targetFile = $f; - (my $strippedShareSrc = $RestoreReq{shareSrc}) =~ s/^\///; - (my $strippedShareDest = $RestoreReq{shareDest}) =~ s/^\///; - substr($targetFile, 0, length($RestoreReq{pathHdrSrc})) - = $RestoreReq{pathHdrDest}; - $fileListStr .= <$RestoreReq{hostSrc}:/$strippedShareSrc$f$RestoreReq{hostDest}:/$strippedShareDest$targetFile -EOF - } - - Header("BackupPC: Restore #$num details for $host"); - print < - - - - - - - - - - - - - - - - - - - -
    Number $Restores[$i]{num}
    Requested by $RestoreReq{user}
    Request time $reqTime
    Result $Restores[$i]{result}
    Error Message $Restores[$i]{errorMsg}
    Source host $RestoreReq{hostSrc}
    Source backup num $RestoreReq{num}
    Source share $RestoreReq{shareSrc}
    Destination host $RestoreReq{hostDest}
    Destination share $RestoreReq{shareDest}
    Start time $startTime
    Duration $duration min
    Number of files $Restores[$i]{nFiles}
    Total size ${MB} MB
    Transfer rate $MBperSec MB/sec
    TarCreate errors $Restores[$i]{tarCreateErrs}
    Xfer errors $Restores[$i]{xferErrs}
    Xfer log file -View, -Errors -
    -

    -${h1("File/Directory list")} -

    - - -$fileListStr -
    Original file/dirRestored to
    -EOF - Trailer(); -} - -########################################################################### -# Miscellaneous subroutines -########################################################################### - -sub timeStamp2 -{ - my($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) - = localtime($_[0] == 0 ? time : $_[0] ); - $year += 1900; - $mon++; - if ( $Conf{CgiDateFormatMMDD} ) { - return sprintf("$mon/$mday %02d:%02d", $hour, $min); - } else { - return sprintf("$mday/$mon %02d:%02d", $hour, $min); - } -} - -sub HostLink -{ - my($host) = @_; - my($s); - if ( defined($Hosts->{$host}) || defined($Status{$host}) ) { - $s = "$host"; - } else { - $s = $host; - } - return \$s; -} - -sub UserLink -{ - my($user) = @_; - my($s); - - return \$user if ( $user eq "" - || $Conf{CgiUserUrlCreate} eq "" ); - if ( $Conf{CgiUserHomePageCheck} eq "" - || -f sprintf($Conf{CgiUserHomePageCheck}, $user, $user, $user) ) { - $s = "$user"; - } else { - $s = $user; - } - return \$s; -} - -sub EscapeHTML -{ - my($s) = @_; - $s =~ s/&/&/g; - $s =~ s/\"/"/g; - $s =~ s/>/>/g; - $s =~ s/\n

    ", @mesg); - $Conf{CgiHeaderFontType} ||= "arial"; - $Conf{CgiHeaderFontSize} ||= "3"; - $Conf{CgiNavBarBgColor} ||= "#ddeeee"; - $Conf{CgiHeaderBgColor} ||= "#99cc33"; - - $bpc->ServerMesg("log User $User (host=$In{host}) got CGI error: $head") - if ( defined($bpc) ); - Header("BackupPC: Error"); - print <$mesg

    -EOF - Trailer(); - exit(1); -} - -sub ServerConnect -{ - # - # Verify that the server connection is ok - # - return if ( $bpc->ServerOK() ); - $bpc->ServerDisconnect(); - if ( my $err = $bpc->ServerConnect($Conf{ServerHost}, $Conf{ServerPort}) ) { - ErrorExit( - "Unable to connect to BackupPC server", - "This CGI script ($MyURL) is unable to connect to the BackupPC" - . " server on $Conf{ServerHost} port $Conf{ServerPort}. The error" - . " was: $err.", - "Perhaps the BackupPC server is not running or there is a " - . " configuration error. Please report this to your Sys Admin." - ); - } -} - -sub GetStatusInfo -{ - my($status) = @_; - ServerConnect(); - 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}); - 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 ( !defined($ActionDispatch{$In{action}}) ) { + $In{action} = defined($In{host}) ? "hostInfo" : "generalInfo"; } +my $action = $ActionDispatch{$In{action}}; # -# Check if the user is privileged. A privileged user can access -# any information (backup files, logs, status pages etc). +# For some reason under mod_perl, the use lib above is unreliable, +# and sometimes the module below cannot be found. Explicitly push +# the directory onto INC if it is missing. This is an ugly hack; +# need to figure out what's really going on... # -# A user is privileged if they belong to the group -# $Conf{CgiAdminUserGroup}, or they are in $Conf{CgiAdminUsers} -# or they are the user assigned to a host in the host file. -# -sub CheckPermission -{ - my($host) = @_; - my $Privileged = 0; - - return 0 if ( $User eq "" || ($host ne "" && !defined($Hosts->{$host})) ); - if ( $Conf{CgiAdminUserGroup} ne "" ) { - my($n,$p,$gid,$mem) = getgrnam($Conf{CgiAdminUserGroup}); - $Privileged ||= ($mem =~ /\b$User\b/); - } - if ( $Conf{CgiAdminUsers} ne "" ) { - $Privileged ||= ($Conf{CgiAdminUsers} =~ /\b$User\b/); - $Privileged ||= $Conf{CgiAdminUsers} eq "*"; - } - $PrivAdmin = $Privileged; - $Privileged ||= $User eq $Hosts->{$host}{user}; - return $Privileged; -} +my $installDir = '/usr/local/BackupPC/lib'; +push(@INC, $installDir) if ( !grep($_ eq $installDir, @INC) ); # -# Given a host name tries to find the IP address. For non-dhcp hosts -# we just return the host name. For dhcp hosts we check the address -# the user is using ($ENV{REMOTE_ADDR}) and also the last-known IP -# address for $host. (Later we should replace this with a broadcast -# nmblookup.) +# Load the relevant action script and run it # -sub ConfirmIPAddress -{ - my($host) = @_; - my $ipAddr = $host; - - if ( $Hosts->{$host}{dhcp} - && $ENV{REMOTE_ADDR} =~ /^(\d+[\.\d]*)$/ ) { - $ipAddr = $1; - my($netBiosHost, $netBiosUser) = $bpc->NetBiosInfoGet($ipAddr); - if ( $netBiosHost ne $host ) { - my($tryIP); - GetStatusInfo("host($host)"); - if ( defined($StatusHost{dhcpHostIP}) - && $StatusHost{dhcpHostIP} ne $ipAddr ) { - $tryIP = " and $StatusHost{dhcpHostIP}"; - ($netBiosHost, $netBiosUser) - = $bpc->NetBiosInfoGet($StatusHost{dhcpHostIP}); - } - if ( $netBiosHost ne $host ) { - ErrorExit("Can't find IP address for ${EscapeHTML($host)}", - < -Until I see $host at a particular DHCP address, you can only -start this request from the client machine itself. -EOF - } - $ipAddr = $StatusHost{dhcpHostIP}; - } - } - return $ipAddr; -} - -sub genPoolInfo -{ - my($name, $info) = @_; - my $poolSize = sprintf("%.2f", $info->{"${name}Kb"} / (1000 * 1024)); - my $poolRmSize = sprintf("%.2f", $info->{"${name}KbRm"} / (1000 * 1024)); - my $poolTime = timeStamp2($info->{"${name}Time"}); - $info->{"${name}FileCntRm"} = $info->{"${name}FileCntRm"} + 0; - return <Pool is ${poolSize}GB comprising $info->{"${name}FileCnt"} files - and $info->{"${name}DirCnt"} directories (as of $poolTime), -
  • Pool hashing gives $info->{"${name}FileCntRep"} repeated - files with longest chain $info->{"${name}FileRepMax"}, -
  • Nightly cleanup removed $info->{"${name}FileCntRm"} files of - size ${poolRmSize}GB (around $poolTime), -EOF -} - -########################################################################### -# HTML layout subroutines -########################################################################### - -sub Header -{ - my($title) = @_; - my @adminLinks = ( - { link => "", name => "Status", - priv => 1}, - { link => "?action=summary", name => "PC Summary" }, - { link => "?action=view&type=LOG", name => "LOG file" }, - { link => "?action=LOGlist", name => "Old LOGs" }, - { link => "?action=emailSummary", name => "Email summary" }, - { link => "?action=view&type=config", name => "Config file" }, - { link => "?action=view&type=hosts", name => "Hosts file" }, - { link => "?action=queue", name => "Current queues" }, - { link => "?action=view&type=docs", name => "Documentation", - priv => 1}, - { link => "http://backuppc.sourceforge.net", name => "SourceForge", - priv => 1}, - ); - print $Cgi->header(); - print < - -$title -$Conf{CgiHeaders} - - - - -
    -EOF - NavSectionTitle("BackupPC"); - print " \n"; - if ( defined($In{host}) && defined($Hosts->{$In{host}}) ) { - my $host = $In{host}; - NavSectionTitle("Host $In{host}"); - NavSectionStart(); - NavLink("?host=$host", "Home"); - NavLink("?action=view&type=LOG&host=$host", "LOG file"); - NavLink("?action=LOGlist&host=$host", "Old LOGs"); - 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=$host", - "Last bad XferLOG"); - NavLink("?action=view&type=XferErrbad&host=$host", - "Last bad XferLOG (errors only)"); - } - if ( -f "$TopDir/pc/$host/config.pl" ) { - NavLink("?action=view&type=config&host=$host", "Config file"); - } - NavSectionEnd(); - } - NavSectionTitle("Hosts"); - if ( %$Hosts > 0 ) { - NavSectionStart(); - foreach my $host ( sort(keys(%$Hosts)) ) { - next if ( $Hosts->{$host}{user} ne $User ); - NavLink("?host=$host", $host); - } - NavSectionEnd(); - } - print < -
    Host or User name:
    - - -
    -EOF - NavSectionTitle("Server"); - NavSectionStart(); - foreach my $l ( @adminLinks ) { - if ( $PrivAdmin || $l->{priv} ) { - NavLink($l->{link}, $l->{name}); - } else { - NavLink(undef, $l->{name}); - } - } - NavSectionEnd(); - print <   - -EOF -} - -sub Trailer -{ - print < - -EOF -} - -sub h1 -{ - my($str) = @_; - return \< - - $str - - -EOF -} - -sub h2 -{ - my($str) = @_; - return \< - - $str - - -EOF -} - -sub NavSectionTitle -{ - my($head) = @_; - print < -$head - - -EOF -} - -sub NavSectionStart -{ - print < -EOF -} - -sub NavSectionEnd -{ - print "\n"; -} - -sub NavLink -{ - my($link, $text) = @_; - print "·"; - if ( defined($link) ) { - $link = "$MyURL$link" if ( $link eq "" || $link =~ /^\?/ ); - print <$text -EOF - } else { - print <$text -EOF - } -} +require "BackupPC/CGI/$action.pm" + if ( !defined($BackupPC::CGI::{"${action}::"}) ); +$BackupPC::CGI::{"${action}::"}{action}();