Merge branch 'master' of git.rot13.org:/git/BackupPC
[BackupPC.git] / cgi-bin / BackupPC_Admin
index ccdb618..90dfdba 100755 (executable)
@@ -1,4 +1,4 @@
-#!/usr/bin/perl -T
+#!/usr/bin/perl
 #============================================================= -*-perl-*-w
 #
 # BackupPC_Admin: Apache/CGI interface for BackupPC.
 #   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  <cbarratt@users.sourceforge.net>
 #
 # 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
 #
 #========================================================================
 #
-# Version 1.5.0beta0, released 30 Jun 2002.
+# Version 3.2.0, released 31 Jul 2010.
 #
 # See http://backuppc.sourceforge.net.
 #
 #========================================================================
 
 use strict;
+no  utf8;
 use CGI;
+use CGI::Carp qw(fatalsToBrowser);
 use lib "/usr/local/BackupPC/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);
-
-use vars qw ($Lang);
-
-$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($Lang->{BackupPC__Lib__new_failed__check_apache_error_log})
-       if ( !($bpc = BackupPC::Lib->new) );
-    $TopDir = $bpc->TopDir();
-    $BinDir = $bpc->BinDir();
-    %Conf   = $bpc->Conf();
-    $Lang   = $bpc->Lang();
-    $ConfigMTime = $bpc->ConfigMTime();
-} elsif ( $bpc->ConfigMTime() != $ConfigMTime ) {
-    $bpc->ConfigRead();
-    %Conf   = $bpc->Conf();
-    $ConfigMTime = $bpc->ConfigMTime();
-    $Lang   = $bpc->Lang();
-}
-
-#
-# Clean up %ENV for taint checking
-#
-delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
-$ENV{PATH} = $Conf{MyPath};
-
-#
-# Verify we are running as the correct user
-#
-if ( $Conf{BackupPCUserVerify}
-        && $> != (my $uid = (getpwnam($Conf{BackupPCUser}))[2]) ) {
-    ErrorExit(eval("qq{$Lang->{Wrong_user__my_userid_is___}}"));
-}
-
-if ( !defined($Hosts) || $bpc->HostsMTime() != $HostsMTime ) {
-    $HostsMTime = $bpc->HostsMTime();
-    $Hosts = $bpc->HostInfoRead();
+use BackupPC::Lib;
+use BackupPC::CGI::Lib qw(:all);
 
-    # turn operators list into a hash for quick lookups
-    foreach my $host (keys %$Hosts) {
-       $Hosts->{$host}{moreUsers} =
-           {map {$_, 1} split(",", $Hosts->{$host}{moreUsers}) }
-    }
-}
+BackupPC::CGI::Lib::NewRequest;
 
 my %ActionDispatch = (
-    "summary"                   => \&Action_Summary,
-    $Lang->{Start_Incr_Backup}   => \&Action_StartStopBackup,
-    $Lang->{Start_Full_Backup}   => \&Action_StartStopBackup,
-    $Lang->{Stop_Dequeue_Backup} => \&Action_StartStopBackup,
-    "queue"                     => \&Action_Queue,
-    "view"                      => \&Action_View,
-    "LOGlist"                   => \&Action_LOGlist,
-    "emailSummary"              => \&Action_EmailSummary,
-    "browse"                    => \&Action_Browse,
-    $Lang->{Restore}            => \&Action_Restore,
-    "RestoreFile"               => \&Action_RestoreFile,
-    $Lang->{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} ||= $Lang->{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($Lang->{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);
-       if (! $incrAge) { $incrAge = "&nbsp;"; }
-        $str = <<EOF;
-<tr><td> ${HostLink($host)} </td>
-    <td align="center"> ${UserLink($Hosts->{$host}{user})} </td>
-    <td align="center"> $fullCnt </td>
-    <td align="center"> $fullAge </td>
-    <td align="center"> $fullSize </td>
-    <td align="center"> $fullRate </td>
-    <td align="center"> $incrCnt </td>
-    <td align="center"> $incrAge </td>
-    <td align="center"> $Status{$host}{state} </td>
-    <td> $Status{$host}{reason} </td></tr>
-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($Lang->{BackupPC__Server_Summary});
-    print eval ("qq{$Lang->{BackupPC_Summary}}");
-
-    Trailer();
-}
-
-sub Action_StartStopBackup
-{
-    my($str, $reply);
-
-    my $start = 1 if ( $In{action} eq $Lang->{Start_Incr_Backup}
-                       || $In{action} eq $Lang->{Start_Full_Backup} );
-    my $doFull = $In{action} eq $Lang->{Start_Full_Backup} ? 1 : 0;
-    my $type = $doFull ? "full" : "incremental";
-    my $host = $In{host};
-    my $Privileged = CheckPermission($host);
-
-    if ( !$Privileged ) {
-        ErrorExit(eval("qq{$Lang->{Only_privileged_users_can_stop_or_start_backups}}"));
-    }
-    ServerConnect();
-
-    if ( $In{doit} ) {
-        if ( $start ) {
-           if ( $Hosts->{$host}{dhcp} ) {
-               $reply = $bpc->ServerMesg(eval("qq{$Lang->{backup__In_hostIP___host}}"));
-               $str = eval("qq{$Lang->{Backup_requested_on_DHCP__host}}");
-           } else {
-               $reply = $bpc->ServerMesg(eval("qq{$Lang->{backup__host__host__User__doFull}}"));
-               $str = eval("qq{$Lang->{Backup_requested_on__host_by__User}}");
-           }
-        } else {
-            $reply = $bpc->ServerMesg(eval("qq{$Lang->{stop__host__User__In_backoff}}"));
-            $str = eval("qq{$Lang->{Backup_stopped_dequeued_on__host_by__User}}");
-        }
-
-        Header(eval ("qq{$Lang->{BackupPC__Backup_Requested_on__host}}") );
-        print (eval ("qq{$Lang->{REPLY_FROM_SERVER}}"));
-
-        Trailer();
-    } else {
-        if ( $start ) {
-           my $ipAddr = ConfirmIPAddress($host);
-
-            Header($Lang->{BackupPC__Start_Backup_Confirm_on__host});
-            print (eval ("qq{$Lang->{Are_you_sure_start}}"));
-        } else {
-            my $backoff = "";
-            GetStatusInfo("host($host)");
-            if ( $StatusHost{backoffTime} > time ) {
-                $backoff = sprintf("%.1f",
-                                  ($StatusHost{backoffTime} - time) / 3600);
-            }
-            Header($Lang->{BackupPC__Stop_Backup_Confirm_on__host});
-            print (eval ("qq{$Lang->{Are_you_sure_stop}}"));
-        }
-        Trailer();
-    }
-}
-
-sub Action_Queue
-{
-    my($strBg, $strUser, $strCmd);
-
-    GetStatusInfo("queues");
-    my $Privileged = CheckPermission();
-
-    if ( !$Privileged ) {
-       ErrorExit($Lang->{Only_privileged_users_can_view_queues_});
-    }
-
-    while ( @BgQueue ) {
-        my $req = pop(@BgQueue);
-        my($reqTime) = timeStamp2($req->{reqTime});
-        $strBg .= <<EOF;
-<tr><td> ${HostLink($req->{host})} </td>
-    <td align="center"> $reqTime </td>
-    <td align="center"> $req->{user} </td></tr>
-EOF
-    }
-    while ( @UserQueue ) {
-        my $req = pop(@UserQueue);
-        my $reqTime = timeStamp2($req->{reqTime});
-        $strUser .= <<EOF;
-<tr><td> ${HostLink($req->{host})} </td>
-    <td align="center"> $reqTime </td>
-    <td align="center"> $req->{user} </td></tr>
-EOF
-    }
-    while ( @CmdQueue ) {
-        my $req = pop(@CmdQueue);
-        my $reqTime = timeStamp2($req->{reqTime});
-        (my $cmd = $req->{cmd}) =~ s/$BinDir\///;
-        $strCmd .= <<EOF;
-<tr><td> ${HostLink($req->{host})} </td>
-    <td align="center"> $reqTime </td>
-    <td align="center"> $req->{user} </td>
-    <td> $cmd </td></tr>
-EOF
-    }
-    Header($Lang->{BackupPC__Queue_Summary});
-
-    print ( eval ( "qq{$Lang->{Backup_Queue_Summary}}") );
-
-    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(eval("qq{$Lang->{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($Lang->{BackupPC__Documentation});
-            print while ( <LOG> );
-            close(LOG);
-            Trailer();
-        } else {
-            ErrorExit(eval("qq{$Lang->{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($Lang->{Only_privileged_users_can_view_log_or_config_files});
-    }
-    if ( !-f $file && -f "$file.z" ) {
-        $file .= ".z";
-        $compress = 1;
-    }
-    Header(eval("qq{$Lang->{Backup_PC__Log_File__file}}")  );
-    print( eval ("qq{$Lang->{Log_File__file__comment}}"));
-    if ( defined($fh = BackupPC::FileZIO->open($file, 0, $compress)) ) {
-        my $mtimeStr = $bpc->timeStamp((stat($file))[9], 1);
-
-       print ( eval ("qq{$Lang->{Contents_of_log_file}}"));
-
-        print "<pre>";
-        if ( $type eq "XferErr" || $type eq "XferErrbad"
-                               || $type eq "RestoreErr" ) {
-           my $skipped;
-            while ( 1 ) {
-                $_ = $fh->readLine();
-                if ( $_ eq "" ) {
-                   print(eval ("qq{$Lang->{skipped__skipped_lines}}"));
-                   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(eval("qq{$Lang->{skipped__skipped_lines}}")) 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( eval("qq{$Lang->{_pre___Can_t_open_log_file__file}}"));
-    }
-    print <<EOF;
-</pre>
-EOF
-    Trailer();
-}
-
-sub Action_LOGlist
-{
-    my $Privileged = CheckPermission($In{host});
-
-    if ( !$Privileged ) {
-        ErrorExit($Lang->{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 .= <<EOF;
-<tr><td> <a href="$MyURL?action=view&type=LOG$url0$url1"><tt>$file</tt></a> </td>
-    <td align="right"> $size </td>
-    <td> $mtimeStr </td></tr>
-EOF
-    }
-    Header($Lang->{BackupPC__Log_File_History});
-    print (eval("qq{$Lang->{Log_File_History__hdr}}"));
-    Trailer();
-}
-
-sub Action_EmailSummary
-{
-    my $Privileged = CheckPermission();
-
-    if ( !$Privileged ) {
-        ErrorExit($Lang->{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}} .= <<EOF;
-<tr><td>${UserLink($u)} </td>
-    <td>${HostLink($UserEmailInfo{$u}{lastHost})} </td>
-    <td>$emailTimeStr </td>
-    <td>$UserEmailInfo{$u}{lastSubj} </td></tr>
-EOF
-    }
-    foreach my $t ( sort({$b <=> $a} keys(%EmailStr)) ) {
-        $str .= $EmailStr{$t};
-    }
-    Header($Lang->{Email_Summary});
-    print (eval("qq{$Lang->{Recent_Email_Summary}}"));
-    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(eval("qq{$Lang->{Only_privileged_users_can_browse_backup_files}}"));
-    }
-    my $host = $In{host};
-    my $num  = $In{num};
-    my $dir  = $In{dir};
-    if ( $host eq "" ) {
-        ErrorExit($Lang->{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(eval("qq{$Lang->{Can_t_browse_bad_directory_name}}"));
-        }
-        #
-        # 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(eval("qq{$Lang->{Directory___EscapeHTML}}"));
-       }
-    }
-    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(eval("qq{$Lang->{Can_t_browse_bad_directory_name2}}"));
-        }
-        #
-        # 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 = "<b>";
-                   $unbold = "</b>";
-                   $img |= 1 << 2;
-                   $img |= 1 << 3 if ( $s[3] > 2 );
-               }
-               my $imgFileName = sprintf("%07b.gif", $img);
-               $imgStr = "<img src=\"$Conf{CgiImageDirURL}/$imgFileName\" align=\"absmiddle\" width=\"9\" height=\"19\" border=\"0\">";
-               if ( "$relDir/$f" eq $dir ) {
-                   $BGcolor = " bgcolor=\"$Conf{CgiHeaderBgColor}\"";
-               } else {
-                   $BGcolor = "";
-               }
-               my $dirName = $fum;
-               $dirName =~ s/ /&nbsp;/g;
-               push(@DirStr, {needTick => 1,
-                              tdArgs   => $BGcolor,
-                              link     => <<EOF});
-<a href="$MyURL?action=browse&host=$host&num=$num&dir=$path">$imgStr</a><a href="$MyURL?action=browse&host=$host&num=$num&dir=$path" style="font-size:13px;font-family:arial;text-decoration:none;line-height:15px">&nbsp;$bold$dirName$unbold</a></td></tr>
-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 = "<img src=\"$Conf{CgiImageDirURL}/$imgFileName\" align=\"absmiddle\" width=\"9\" height=\"19\" border=\"0\">";
-                       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 .= <<EOF;
-    <td align="center">$typeStr</td>
-    <td align="right">$modeStr</td>
-    <td align="right">$a->{size}</td>
-    <td align="right">$mtimeStr</td>
-</tr>
-EOF
-                } else {
-                    $attrStr .= "<td colspan=\"4\" align=\"center\"> </td>\n";
-                }
-                if ( $gotDir ) {
-                    $fileStr .= <<EOF;
-<tr bgcolor="#ffffcc"><td><input type="checkbox" name="fcb$checkBoxCnt" value="$path">&nbsp;<a href="$MyURL?action=browse&host=$host&num=$num&dir=$path">${EscapeHTML($fum)}</a></td>
-$attrStr
-</tr>
-EOF
-                } else {
-                    $fileStr .= <<EOF;
-<tr bgcolor="#ffffcc"><td><input type="checkbox" name="fcb$checkBoxCnt" value="$path">&nbsp;<a href="$MyURL?action=RestoreFile&host=$host&num=$num&dir=$path">${EscapeHTML($fum)}</a></td>
-$attrStr
-</tr>
-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 = eval("qq{$Lang->{This_display_is_merged_with_backup}}");
-    }
-    Header(eval("qq{$Lang->{Browse_backup__num_for__host}}"));
-
-    foreach my $d ( @DirStrPrev ) {
-       $dirStr .= "<tr><td$d->{tdArgs}>$d->{link}\n";
-    }
-
-    ### hide checkall button if there are no files
-    my ($topCheckAll, $checkAll, $fileHeader);
-    if ( $fileStr ) {
-       $fileHeader = eval("qq{$Lang->{fileHeader}}");
-
-       $checkAll = $Lang->{checkAll};
-
-       # 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 = eval("qq{$Lang->{The_directory_is_empty}}");
-    }
-    print (eval("qq{$Lang->{Backup_browse_for__host}}"));
-    Trailer();
-}
-
-sub Action_Restore
-{
-    my($str, $reply, $i);
-    my $Privileged = CheckPermission($In{host});
-    if ( !$Privileged ) {
-        ErrorExit(eval("qq{$Lang->{Only_privileged_users_can_restore_backup_files}}"));
-    }
-    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(eval("qq{$Lang->{Bad_host_name}}"));
-    }
-    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;
-<input type="hidden" name="fcb$i" value="$In{'fcb' . $i}">
-EOF
-        $fileListStr .= <<EOF;
-<li> ${EscapeHTML($name)}
-EOF
-    }
-    $hiddenStr .= "<input type=\"hidden\" name=\"fcbMax\" value=\"$In{fcbMax}\">\n";
-    $badFileCnt++ if ( $In{pathHdr} =~ m{(^|/)\.\.(/|$)} );
-    $badFileCnt++ if ( $In{num} =~ m{(^|/)\.\.(/|$)} );
-    if ( @fileList == 0 ) {
-        ErrorExit($Lang->{You_haven_t_selected_any_files__please_go_Back_to});
-    }
-    if ( $badFileCnt ) {
-        ErrorExit($Lang->{Nice_try__but_you_can_t_put});
-    }
-    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(eval("qq{$Lang->{Restore_Options_for__host}}"));
-       print(eval("qq{$Lang->{Restore_Options_for__host2}}"));
-
-       #
-       # Verify that Archive::Zip is available before showing the
-       # zip restore option
-       #
-       if ( eval { require Archive::Zip } ) {
-           print (eval("qq{$Lang->{Option_2__Download_Zip_archive}}"));
-       } else {
-           print (eval("qq{$Lang->{Option_2__Download_Zip_archive2}}"));
-       }
-       print (eval("qq{$Lang->{Option_3__Download_Zip_archive}}"));
-        Trailer();
-    } elsif ( $In{type} == 1 ) {
-        #
-        # Provide the selected files via a tar archive.
-        #
-        $SIG{CHLD} = 'IGNORE';
-        my $pid = fork();
-        if ( !defined($pid) ) {
-            $bpc->ServerMesg(eval("qq{$Lang->{log_Can_t_fork_for_tar_restore_request_by__User}}"));
-            ErrorExit($Lang->{Can_t_fork_for_tar_restore});
-        }
-        if ( $pid ) {
-            #
-            # This is the parent.
-            #
-            my @fileListTrim = @fileList;
-            if ( @fileListTrim > 10 ) {
-                @fileListTrim = (@fileListTrim[0..9], '...');
-            }
-            $bpc->ServerMesg(eval("qq{$Lang->{log_User__User_downloaded_tar_archive_for__host}}"));
-            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(eval("qq{$Lang->{log_Can_t_fork_for_zip_restore_request_by__User}}"));
-            ErrorExit($Lang->{Can_t_fork_for_zip_restore});
-        }
-        if ( $pid ) {
-            #
-            # This is the parent.
-            #
-            my @fileListTrim = @fileList;
-            if ( @fileListTrim > 10 ) {
-                @fileListTrim = (@fileListTrim[0..9], '...');
-            }
-            $bpc->ServerMesg(eval("qq{$Lang->{log_User__User_downloaded_zip_archive_for__host}}"));
-            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(eval("qq{$Lang->{Host__doesn_t_exist}}"));
-       }
-       if ( !CheckPermission($In{hostDest}) ) {
-           ErrorExit(eval("qq{$Lang->{You_don_t_have_permission_to_restore_onto_host}}"));
-       }
-        $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 .= <<EOF;
-<tr><td>$host:/$strippedShare$f</td><td>$In{hostDest}:/$strippedShareDest$targetFile</td></tr>
-EOF
-        }
-        Header(eval("qq{$Lang->{Restore_Confirm_on__host}}"));
-       print(eval("qq{$Lang->{Are_you_sure}}"));
-        Trailer();
-    } elsif ( $In{type} == 4 ) {
-       if ( !defined($Hosts->{$In{hostDest}}) ) {
-           ErrorExit(eval("qq{$Lang->{Host_doesn_t_exist}}"));
-       }
-       if ( !CheckPermission($In{hostDest}) ) {
-           ErrorExit(eval("qq{$Lang->{You_don_t_have_permission_to_restore_onto_host}}"));
-       }
-       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(eval("qq{$Lang->{Can_t_open_create}}"));
-        }
-       $reply = $bpc->ServerMesg(eval("qq{$Lang->{restore__ipAddr}}"));
-       $str = eval("qq{$Lang->{Restore_requested_to_host__hostDest__backup___num}}");
-        Header(eval("qq{$Lang->{Restore_Requested_on__hostDest}}"));
-       print (eval("qq{$Lang->{Reply_from_server_was___reply}}"));
-        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(eval("qq{$Lang->{Only_privileged_users_can_restore_backup_files2}}"));
-    }
-    ServerConnect();
-    my @Backups = $bpc->BackupInfoRead($host);
-    if ( $host eq "" ) {
-        ErrorExit($Lang->{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(eval("qq{$Lang->{Can_t_restore_bad_file}}"));
-    }
-    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(eval("qq{$Lang->{Unknown_host_or_user}}"))
-                                if ( !defined($Hosts->{$host}) );
-        }
-        $In{host} = $host;
-    }
-    GetStatusInfo("host($host)");
-    $bpc->ConfigRead($host);
-    %Conf = $bpc->Conf();
-    my $Privileged = CheckPermission($host);
-    if ( !$Privileged ) {
-        ErrorExit(eval("qq{$Lang->{Only_privileged_users_can_view_information_about}}"));
-    }
-    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} ? $Lang->{No} : $Lang->{Yes};
-        $filled .= " ($Backups[$i]{fillFromNum}) "
-                            if ( $Backups[$i]{fillFromNum} ne "" );
-       my $ltype;
-       if ($Backups[$i]{type} eq "full") { $ltype = $Lang->{full}; }
-       else { $ltype = $Lang->{incremental}; }
-        $str .= <<EOF;
-<tr><td align="center"> <a href="$browseURL">$Backups[$i]{num}</a> </td>
-    <td align="center"> $ltype </td>
-    <td align="center"> $filled </td>
-    <td align="right">  $startTime </td>
-    <td align="right">  $duration </td>
-    <td align="right">  $age </td>
-    <td align="left">   <tt>$TopDir/pc/$host/$Backups[$i]{num}</tt> </td></tr>
-EOF
-        $sizeStr .= <<EOF;
-<tr><td align="center"> <a href="$browseURL">$Backups[$i]{num}</a> </td>
-    <td align="center"> $ltype </td>
-    <td align="right">  $Backups[$i]{nFiles} </td>
-    <td align="right">  $MB </td>
-    <td align="right">  $MBperSec </td>
-    <td align="right">  $Backups[$i]{nFilesExist} </td>
-    <td align="right">  $MBExist </td>
-    <td align="right">  $Backups[$i]{nFilesNew} </td>
-    <td align="right">  $MBNew </td>
-</tr>
-EOF
-        $Backups[$i]{compress} ||= "off";
-       my $is_compress = $Lang->{off};
-       if ($Backups[$i]{compress} ne "off") {$is_compress = $Lang->{on}; }
-       if (! $ExistComp) { $ExistComp = "&nbsp;"; }
-       if (! $MBExistComp) { $MBExistComp = "&nbsp;"; }
-        $compStr .= <<EOF;
-<tr><td align="center"> <a href="$browseURL">$Backups[$i]{num}</a> </td>
-    <td align="center"> $ltype </td>
-    <td align="center"> $is_compress </td> 
-    <td align="right">  $MBExist </td>
-    <td align="right">  $MBExistComp </td> 
-    <td align="right">  $ExistComp </td>   
-    <td align="right">  $MBNew </td>
-    <td align="right">  $MBNewComp </td>
-    <td align="right">  $NewComp </td>
-</tr>
-EOF
-        $errStr .= <<EOF;
-<tr><td align="center"> <a href="$browseURL">$Backups[$i]{num}</a> </td>
-    <td align="center"> $ltype </td>
-    <td align="center"> <a href="$MyURL?action=view&type=XferLOG&num=$Backups[$i]{num}&host=$host">XferLOG</a>,
-                      <a href="$MyURL?action=view&type=XferErr&num=$Backups[$i]{num}&host=$host">Errors</a> </td>
-    <td align="right">  $Backups[$i]{xferErrs} </td>
-    <td align="right">  $Backups[$i]{xferBadFile} </td>
-    <td align="right">  $Backups[$i]{xferBadShare} </td>
-    <td align="right">  $Backups[$i]{tarErrs} </td></tr>
-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));
-       my $Restores_Result = $Lang->{failed};
-       if ($Restores[$i]{result} ne "failed") { $Restores_Result = $Lang->{success}; }
-       $restoreStr  .= <<EOF;
-<tr><td align="center"><a href="$MyURL?action=restoreInfo&num=$Restores[$i]{num}&host=$host">$Restores[$i]{num}</a> </td>
-    <td align="center"> $Restores_Result </td>
-    <td align="right"> $startTime </td>
-    <td align="right"> $duration </td>
-    <td align="right"> $Restores[$i]{nFiles} </td>
-    <td align="right"> $MB </td>
-    <td align="right"> $Restores[$i]{tarCreateErrs} </td>
-    <td align="right"> $Restores[$i]{xferErrs} </td>
-</tr>
-EOF
-    }
-    if ( $restoreStr ne "" ) {
-       $restoreStr = eval("qq{$Lang->{Restore_Summary}}");
-    }
-    if ( @Backups == 0 ) {
-        $warnStr = $Lang->{This_PC_has_never_been_backed_up};
-    }
-    if ( defined($Hosts->{$host}) ) {
-        my $user = $Hosts->{$host}{user};
-       my @moreUsers = sort(keys(%{$Hosts->{$host}{moreUsers}}));
-       my $moreUserStr;
-       foreach my $u ( sort(keys(%{$Hosts->{$host}{moreUsers}})) ) {
-           $moreUserStr .= ", " if ( $moreUserStr ne "" );
-           $moreUserStr .= "${UserLink($u)}";
-       }
-       if ( $moreUserStr ne "" ) {
-           $moreUserStr = " ($Lang->{and} $moreUserStr).\n";
-       } else {
-           $moreUserStr = ".\n";
-       }
-        if ( $user ne "" ) {
-            $statusStr .= eval("qq{$Lang->{This_PC_is_used_by}$moreUserStr}");
-        }
-        if ( defined($UserEmailInfo{$user})
-                && $UserEmailInfo{$user}{lastHost} eq $host ) {
-            my $mailTime = timeStamp2($UserEmailInfo{$user}{lastTime});
-            my $subj     = $UserEmailInfo{$user}{lastSubj};
-            $statusStr  .= eval("qq{$Lang->{Last_email_sent_to__was_at___subject}}");
-        }
-    }
-    if ( defined($Jobs{$host}) ) {
-        my $startTime = timeStamp2($Jobs{$host}{startTime});
-        (my $cmd = $Jobs{$host}{cmd}) =~ s/$BinDir\///g;
-        $statusStr .= eval("qq{$Lang->{The_command_cmd_is_currently_running_for_started}}");
-    }
-    if ( $StatusHost{BgQueueOn} ) {
-        $statusStr .= eval("qq{$Lang->{Host_host_is_queued_on_the_background_queue_will_be_backed_up_soon}}");
-    }
-    if ( $StatusHost{UserQueueOn} ) {
-        $statusStr .= eval("qq{$Lang->{Host_host_is_queued_on_the_user_queue__will_be_backed_up_soon}}");
-    }
-    if ( $StatusHost{CmdQueueOn} ) {
-        $statusStr .= eval("qq{$Lang->{A_command_for_host_is_on_the_command_queue_will_run_soon}}");
-    }
-    my $startTime = timeStamp2($StatusHost{endTime} == 0 ?
-                $StatusHost{startTime} : $StatusHost{endTime});
-    my $reason = "";
-    if ( $StatusHost{reason} ne "" ) {
-        $reason = " ($StatusHost{reason})";
-    }
-    $statusStr .= eval("qq{$Lang->{Last_status_is_state_StatusHost_state_reason_as_of_startTime}}");
-
-    if ( $StatusHost{error} ne "" ) {
-        $statusStr .= eval("qq{$Lang->{Last_error_is____EscapeHTML_StatusHost_error}}");
-    }
-    my $priorStr = "Pings";
-    if ( $StatusHost{deadCnt} > 0 ) {
-        $statusStr .= eval("qq{$Lang->{Pings_to_host_have_failed_StatusHost_deadCnt__consecutive_times}}");
-        $priorStr = $Lang->{Prior_to_that__pings};
-    }
-    if ( $StatusHost{aliveCnt} > 0 ) {
-        $statusStr .= eval("qq{$Lang->{priorStr_to_host_have_succeeded_StatusHostaliveCnt_consecutive_times}}");
-
-        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 .= eval("qq{$Lang->{Because__host_has_been_on_the_network_at_least__Conf_BlackoutGoodCnt_consecutive_times___}}");
-        }
-    }
-    if ( $StatusHost{backoffTime} > time ) {
-        my $hours = sprintf("%.1f", ($StatusHost{backoffTime} - time) / 3600);
-        $statusStr .= eval("qq{$Lang->{Backups_are_deferred_for_hours_hours_change_this_number}}");
-
-    }
-    if ( @Backups ) {
-        # only allow incremental if there are already some backups
-        $startIncrStr = <<EOF;
-<input type="submit" value="\$Lang->{Start_Incr_Backup}" name="action">
-EOF
-    }
-
-    $startIncrStr = eval ("qq{$startIncrStr}");
-
-    Header(eval("qq{$Lang->{Host__host_Backup_Summary}}"));
-    print(eval("qq{$Lang->{Host__host_Backup_Summary2}}"));
-    Trailer();
-}
-
-sub Action_GeneralInfo
-{
-    GetStatusInfo($Lang->{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 .= <<EOF;
-<tr><td> ${HostLink($host)} </td>
-    <td align="center"> $Jobs{$host}{type} </td>
-    <td align="center"> ${UserLink($Hosts->{$host}{user})} </td>
-    <td> $startTime </td>
-    <td> $cmd </td>
-    <td align="center"> $Jobs{$host}{pid} </td>
-    <td align="center"> $Jobs{$host}{xferPid} </td>
-EOF
-        if ( $Jobs{$host}{tarPid} > 0 ) {
-            $jobStr .= "    <td align=\"center\"> $Jobs{$host}{tarPid} </td>\n";
-            $tarPidHdr ||= "<td align=\"center\"> tar PID </td>\n";
-        }
-        $jobStr .= "</tr>\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 = <<EOF;
-<a href="$MyURL?action=view&type=XferLOGbad&host=$host">XferLOG</a>,
-<a href="$MyURL?action=view&type=XferErrbad&host=$host">XferErr</a>
-EOF
-        } else {
-            $XferViewStr = "";
-        }
-        (my $shortErr = $Status{$host}{error}) =~ s/(.{48}).*/$1.../;   
-        $statusStr .= <<EOF;
-<tr><td> ${HostLink($host)} </td>
-    <td align="center"> $Status{$host}{type} </td>
-    <td align="center"> ${UserLink($Hosts->{$host}{user})} </td>
-    <td align="right"> $startTime </td>
-    <td> $XferViewStr </td>
-    <td align="right"> $errorTime </td>
-    <td> ${EscapeHTML($shortErr)} </td></tr>
-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 = <<EOF;
-<li>Uncompressed pool:
-<ul>
-$poolInfo
-</ul>
-<li>Compressed pool:
-<ul>
-$cpoolInfo
-</ul>
-EOF
-    } elsif ( $Info{cpoolFileCnt} > 0 ) {
-        $poolInfo = $cpoolInfo;
-    }
-
-    Header($Lang->{H_BackupPC_Server_Status});
-       #Header("H_BackupPC_Server_Status");
-    print (eval ("qq{$Lang->{BackupPC_Server_Status}}"));
-
-    #Header($Lang->{BackupPC_Server_Status});
-
-    #my $trans_text = $Lang->{BackupPC_Server_Status};
-    #print eval ("qq{$trans_text}");
-    Trailer();
-}
-
-sub Action_RestoreInfo
-{
-    my $Privileged = CheckPermission($In{host});
-    my $host = $1 if ( $In{host} =~ /(.*)/ );
-    my $num  = $In{num};
-    my $i;
-
-    if ( !$Privileged ) {
-        ErrorExit($Lang->{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(eval("qq{$Lang->{Restore_number__num_for_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 .= <<EOF;
-<tr><td>$RestoreReq{hostSrc}:/$strippedShareSrc$f</td><td>$RestoreReq{hostDest}:/$strippedShareDest$targetFile</td></tr>
-EOF
-    }
-
-    Header(eval("qq{$Lang->{Restore___num_details_for__host}}"));
-    print(eval("qq{$Lang->{Restore___num_details_for__host2 }}"));
-    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 = "<a href=\"$MyURL?host=$host\">$host</a>";
-    } 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 = "<a href=\""
-             . sprintf($Conf{CgiUserUrlCreate}, $user, $user, $user)
-             . "\">$user</a>";
-    } else {
-        $s = $user;
-    }
-    return \$s;
-}
-
-sub EscapeHTML
-{
-    my($s) = @_;
-    $s =~ s/&/&amp;/g;
-    $s =~ s/\"/&quot;/g;
-    $s =~ s/>/&gt;/g;
-    $s =~ s/</&lt;/g;
-    $s =~ s{([^[:print:]])}{sprintf("&\#x%02X", ord($1));}eg;
-    return \$s;
-}
-
-##sub URIEncode
-##{
-##    my($s) = @_;
-##    $s =~ s{(['"&%[:^print:]])}{sprintf("%%%02X", ord($1));}eg;
-##    return \$s;
-##}
-
-sub ErrorExit
-{
-    my(@mesg) = @_;
-    my($head) = shift(@mesg);
-    my($mesg) = join("</p>\n<p>", @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) );
-    if ( !defined($Lang->{Error}) ) {
-       Header("BackupPC: Error");
-       print <<EOF;
-${h1("Error: Language strings not defined!!")}
-<p>$mesg</p>
-EOF
-       Trailer();
-    } else {
-       Header(eval("qq{$Lang->{Error}}"));
-       print (eval("qq{$Lang->{Error____head}}"));
-       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(eval("qq{$Lang->{Unable_to_connect_to_BackupPC_server}}"));
-    }
-}
-
-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];
-    }
-}
-
-#
-# Check if the user is privileged.  A privileged user can access
-# any information (backup files, logs, status pages etc).
-#
-# 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};
-    $Privileged ||= defined($Hosts->{$host}{operators}{$User});
-
-    return $Privileged;
+if ( !defined($ActionDispatch{$In{action}}) ) {
+    $In{action} = defined($In{host}) ? "hostInfo" : "generalInfo";
 }
+my $action = $ActionDispatch{$In{action}};
 
 #
-# 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 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...
 #
-sub GetUserHosts
-{
-    if ( $Conf{CgiNavBarAdminAllHosts} && CheckPermission() ) {
-       return sort keys %$Hosts;
-    }
-
-    return sort grep { $Hosts->{$_}{user} eq $User ||
-                       defined($Hosts->{$_}{moreUsers}{$User}) } keys(%$Hosts);
-}
+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 = eval("qq{$Lang->{tryIP}}");
-               ($netBiosHost, $netBiosUser)
-                       = $bpc->NetBiosInfoGet($StatusHost{dhcpHostIP});
-           }
-           if ( $netBiosHost ne $host ) {
-               ErrorExit(eval("qq{$Lang->{Can_t_find_IP_address_for}}"),
-                         eval("qq{$Lang->{host_is_a_DHCP_host}}"));
-           }
-           $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 eval("qq{$Lang->{Pool_Stat}}");
-
-}
-
-###########################################################################
-# HTML layout subroutines
-###########################################################################
-
-sub Header
-{
-    my($title) = @_;
-    my @adminLinks = (
-        { link => "",                          name => $Lang->{Status},
-                                               priv => 1},
-        { 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", name => "SourceForge",
-                                                     priv => 1},
-    );
-    print $Cgi->header();
-    print <<EOF;
-<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN">
-<html><head>
-<title>$title</title>
-$Conf{CgiHeaders}
-</head><body>
-<table cellpadding="0" cellspacing="0" border="0">
-<tr valign="top"><td valign="top" bgcolor="$Conf{CgiNavBarBgColor}" width="10%">
-EOF
-    NavSectionTitle("BackupPC");
-    print "&nbsp;\n";
-    if ( defined($In{host}) && defined($Hosts->{$In{host}}) ) {
-        my $host = $In{host};
-        NavSectionTitle( eval("qq{$Lang->{Host_Inhost}}") );
-        NavSectionStart();
-        NavLink("?host=$host", $Lang->{Home});
-        NavLink("?action=view&type=LOG&host=$host", $Lang->{LOG_file});
-        NavLink("?action=LOGlist&host=$host", $Lang->{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",
-                                $Lang->{Last_bad_XferLOG});
-            NavLink("?action=view&type=XferErrbad&host=$host",
-                                $Lang->{Last_bad_XferLOG_errors_only});
-        }
-        if ( -f "$TopDir/pc/$host/config.pl" ) {
-            NavLink("?action=view&type=config&host=$host", $Lang->{Config_file});
-        }
-        NavSectionEnd();
-    }
-    NavSectionTitle($Lang->{Hosts});
-    if ( defined($Hosts) && %$Hosts > 0 ) {
-        NavSectionStart(0);
-        foreach my $host ( GetUserHosts() ) {
-            NavLink("?host=$host", $host);
-        }
-        NavSectionEnd();
-    }
-    print <<EOF;
-<table cellpadding="2" cellspacing="0" border="0" width="100%">
-    <tr><td>$Lang->{Host_or_User_name}</td>
-    <tr><td><form action="$MyURL" method="get"><small>
-    <input type="text" name="host" size="10" maxlength="64">
-    <input type="hidden" name="action" value="$Lang->{hostInfo}"><input type="submit" value="$Lang->{Go}" name="ignore">
-    </small></form></td></tr>
-</table>
-EOF
-    NavSectionTitle($Lang->{NavSectionTitle_});
-    NavSectionStart();
-    foreach my $l ( @adminLinks ) {
-        if ( $PrivAdmin || $l->{priv} ) {
-            NavLink($l->{link}, $l->{name});
-        } else {
-            NavLink(undef, $l->{name});
-        }
-    }
-    NavSectionEnd();
-    print <<EOF;
-</td><td valign="top" width="5">&nbsp;&nbsp;</td>
-<td valign="top" width="90%">
-EOF
-}
-
-sub Trailer
-{
-    print <<EOF;
-</td></table>
-</body></html>
-EOF
-}
-
-
-sub NavSectionTitle
-{
-    my($head) = @_;
-    print <<EOF;
-<table cellpadding="2" cellspacing="0" border="0" width="100%">
-<tr><td bgcolor="$Conf{CgiHeaderBgColor}"><font face="$Conf{CgiHeaderFontType}"
-size="$Conf{CgiHeaderFontSize}"><b>$head</b>
-</font></td></tr>
-</table>
-EOF
-}
-
-sub NavSectionStart
-{
-    my($padding) = @_;
-
-    $padding = 2 if ( !defined($padding) );
-    print <<EOF;
-<table cellpadding="$padding" cellspacing="0" border="0" width="100%">
-EOF
-}
-
-sub NavSectionEnd
-{
-    print "</table>\n";
-}
-
-sub NavLink
-{
-    my($link, $text) = @_;
-    print "<tr><td width=\"2%\" valign=\"top\"><b>&middot;</b></td>";
-    if ( defined($link) ) {
-        $link = "$MyURL$link" if ( $link eq "" || $link =~ /^\?/ );
-        print <<EOF;
-<td width="98%"><a href="$link"><small>$text</small></a></td></tr>
-EOF
-    } else {
-        print <<EOF;
-<td width="98%"><small>$text</small></td></tr>
-EOF
-    }
-}
-
-sub h1
-{
-    my($str) = @_;
-    return \<<EOF;
-<table cellpadding="2" cellspacing="0" border="0" width="100%">
-<tr>
-<td bgcolor="$Conf{CgiHeaderBgColor}">&nbsp;<font face="$Conf{CgiHeaderFontType}"
-    size="$Conf{CgiHeaderFontSize}"><b>$str</b></font>
-</td></tr>
-</table>
-EOF
-}
-
-sub h2
-{
-    my($str) = @_;
-    return \<<EOF;
-<table cellpadding="2" cellspacing="0" border="0" width="100%">
-<tr>
-<td bgcolor="$Conf{CgiHeaderBgColor}">&nbsp;<font face="$Conf{CgiHeaderFontType}"
-    size="$Conf{CgiHeaderFontSize}"><b>$str</b></font>
-</td></tr>
-</table>
-EOF
-}
+require "BackupPC/CGI/$action.pm"
+           if ( !defined($BackupPC::CGI::{"${action}::"}) );
+$BackupPC::CGI::{"${action}::"}{action}();