diff --git a/Lib.pm b/Lib.pm
new file mode 100644
index 0000000..0115359
--- /dev/null
+++ b/Lib.pm
@@ -0,0 +1,628 @@
+#============================================================= -*-perl-*-
+#
+# BackupPC::CGI::Lib package
+#
+# DESCRIPTION
+#
+# This library defines a BackupPC::Lib class and a variety of utility
+# functions used by BackupPC.
+#
+# AUTHOR
+# Craig Barratt
+#
+# COPYRIGHT
+# Copyright (C) 2003-2019 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
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+#
+#========================================================================
+#
+# Version 4.3.1, released 14 Jul 2019.
+#
+# See http://backuppc.sourceforge.net.
+#
+#========================================================================
+
+package BackupPC::CGI::Lib;
+
+use strict;
+use BackupPC::Lib;
+
+require Exporter;
+
+use vars qw( @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS );
+
+use vars qw($Cgi %In $MyURL $User %Conf $TopDir $LogDir $BinDir $bpc);
+use vars qw(%Status %Info %Jobs @BgQueue @UserQueue @CmdQueue
+ %QueueLen %StatusHost);
+use vars qw($Hosts $HostsMTime $ConfigMTime $PrivAdmin);
+use vars qw(%UserEmailInfo $UserEmailInfoMTime %RestoreReq %ArchiveReq);
+use vars qw($Lang);
+
+@ISA = qw(Exporter);
+
+@EXPORT = qw( );
+
+@EXPORT_OK = qw(
+ timeStamp2
+ HostLink
+ UserLink
+ EscHTML
+ EscURI
+ ErrorExit
+ ServerConnect
+ GetStatusInfo
+ ReadUserEmailInfo
+ CheckPermission
+ GetUserHosts
+ ConfirmIPAddress
+ Header
+ Trailer
+ NavSectionTitle
+ NavSectionStart
+ NavSectionEnd
+ NavLink
+ h1
+ h2
+ $Cgi %In $MyURL $User %Conf $TopDir $LogDir $BinDir $bpc
+ %Status %Info %Jobs @BgQueue @UserQueue @CmdQueue
+ %QueueLen %StatusHost
+ $Hosts $HostsMTime $ConfigMTime $PrivAdmin
+ %UserEmailInfo $UserEmailInfoMTime %RestoreReq %ArchiveReq
+ $Lang
+ );
+
+%EXPORT_TAGS = (
+ 'all' => [ @EXPORT_OK ],
+);
+
+sub NewRequest
+{
+ $Cgi = new CGI;
+ %In = $Cgi->Vars;
+
+ if ( !defined($bpc) ) {
+ ErrorExit($Lang->{BackupPC__Lib__new_failed__check_apache_error_log})
+ if ( !($bpc = BackupPC::Lib->new(undef, undef, undef, 1)) );
+ $TopDir = $bpc->TopDir();
+ $LogDir = $bpc->LogDir();
+ $BinDir = $bpc->BinDir();
+ %Conf = $bpc->Conf();
+ $Lang = $bpc->Lang();
+ $ConfigMTime = $bpc->ConfigMTime();
+ umask($Conf{UmaskMode});
+ } elsif ( $bpc->ConfigMTime() != $ConfigMTime ) {
+ $bpc->ConfigRead();
+ $TopDir = $bpc->TopDir();
+ $LogDir = $bpc->LogDir();
+ $BinDir = $bpc->BinDir();
+ %Conf = $bpc->Conf();
+ $Lang = $bpc->Lang();
+ $ConfigMTime = $bpc->ConfigMTime();
+ umask($Conf{UmaskMode});
+ }
+
+ #
+ # Default REMOTE_USER so in a miminal installation the user
+ # has a sensible default.
+ #
+ $ENV{REMOTE_USER} = $Conf{BackupPCUser} if ( $ENV{REMOTE_USER} eq "" );
+
+ #
+ # 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};
+
+ #
+ # Handle LDAP uid=user when using mod_authz_ldap and otherwise untaint
+ #
+ $User = $1 if ( $User =~ /uid=([^,]+)/i || $User =~ /(.*)/ );
+
+ #
+ # Clean up %ENV for taint checking
+ #
+ 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})) ) {
+ ErrorExit(eval("qq{$Lang->{Wrong_user__my_userid_is___}}"), <
+This is an installation problem. If you are using mod_perl then
+it appears that Apache is not running as user $Conf{BackupPCUser}.
+If you are not using mod_perl, then most like setuid is not working
+properly on BackupPC_Admin. Check the permissions on
+$Conf{CgiDir}/BackupPC_Admin and look at the documentation.
+EOF
+ }
+
+ if ( !defined($Hosts) || $bpc->HostsMTime() != $HostsMTime ) {
+ $HostsMTime = $bpc->HostsMTime();
+ $Hosts = $bpc->HostInfoRead();
+
+ # turn moreUsers list into a hash for quick lookups
+ foreach my $host (keys %$Hosts) {
+ $Hosts->{$host}{moreUsers} =
+ {map {$_, 1} split(",", $Hosts->{$host}{moreUsers}) }
+ }
+ }
+
+ #
+ # Untaint the host name
+ #
+ if ( $In{host} =~ /^([\w.\s-]+)$/ ) {
+ $In{host} = $1;
+ } else {
+ delete($In{host});
+ }
+}
+
+sub timeStamp2
+{
+ my $now = $_[0] == 0 ? time : $_[0];
+ my($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)
+ = localtime($now);
+ $mon++;
+ if ( $Conf{CgiDateFormatMMDD} == 2 ) {
+ $year += 1900;
+ return sprintf("%04d-%02d-%02d %02d:%02d", $year, $mon, $mday, $hour, $min);
+ } elsif ( $Conf{CgiDateFormatMMDD} ) {
+ #
+ # Add the year if the time is more than 330 days ago
+ #
+ if ( time - $now > 330 * 24 * 3600 ) {
+ $year -= 100;
+ return sprintf("$mon/$mday/%02d %02d:%02d", $year, $hour, $min);
+ } else {
+ return sprintf("$mon/$mday %02d:%02d", $hour, $min);
+ }
+ } else {
+ #
+ # Add the year if the time is more than 330 days ago
+ #
+ if ( time - $now > 330 * 24 * 3600 ) {
+ $year -= 100;
+ return sprintf("$mday/$mon/%02d %02d:%02d", $year, $hour, $min);
+ } else {
+ return sprintf("$mday/$mon %02d:%02d", $hour, $min);
+ }
+ }
+}
+
+sub HostLink
+{
+ my($host) = @_;
+ my($s);
+ if ( defined($Hosts->{$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 EscHTML
+{
+ my($s) = @_;
+ $s =~ s/&/&/g;
+ $s =~ s/\"/"/g;
+ $s =~ s/>/>/g;
+ $s =~ s/</g;
+ ### $s =~ s{([^[:print:]])}{sprintf("&\#x%02X;", ord($1));}eg;
+ return \$s;
+}
+
+sub EscURI
+{
+ my($s) = @_;
+ $s =~ s{([^\w.\/-])}{sprintf("%%%02X", ord($1));}eg;
+ return \$s;
+}
+
+sub ErrorExit
+{
+ my(@mesg) = @_;
+ my($head) = shift(@mesg);
+ my($mesg) = join("
\n
", @mesg);
+
+ if ( !defined($ENV{REMOTE_USER}) ) {
+ $mesg .= <
+Note: \$ENV{REMOTE_USER} is not set, which could mean there is an
+installation problem. BackupPC_Admin expects Apache to authenticate
+the user and pass their user name into this script as the REMOTE_USER
+environment variable. See the documentation.
+EOF
+ }
+
+ $bpc->ServerMesg("log User $User (host=$In{host}) got CGI error: $head")
+ if ( defined($bpc) );
+ if ( !defined($Lang->{Error}) ) {
+ $mesg = <$mesg
+EOF
+ Header("BackupPC: Error", $content);
+ Trailer();
+ } else {
+ my $content = eval("qq{$Lang->{Error____head}}");
+ Header(eval("qq{$Lang->{Error}}"), $content);
+ 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}) ) {
+ if ( CheckPermission()
+ && -f $Conf{ServerInitdPath}
+ && $Conf{ServerInitdStartCmd} ne "" ) {
+ my $content = eval("qq{$Lang->{Admin_Start_Server}}");
+ Header(eval("qq{$Lang->{Unable_to_connect_to_BackupPC_server}}"), $content);
+ Trailer();
+ exit(1);
+ } else {
+ ErrorExit(eval("qq{$Lang->{Unable_to_connect_to_BackupPC_server}}"),
+ eval("qq{$Lang->{Unable_to_connect_to_BackupPC_server_error_message}}"));
+ }
+ }
+}
+
+sub GetStatusInfo
+{
+ my($status) = @_;
+ ServerConnect();
+ %Status = () if ( $status =~ /\bhosts\b/ );
+ %StatusHost = () if ( $status =~ /\bhost\(/ );
+ my $reply = $bpc->ServerMesg("status $status");
+ $reply = $1 if ( $reply =~ /(.*)/s );
+ eval($reply);
+ # ignore status related to admin jobs
+ if ( $status =~ /\bhosts\b/ ) {
+ foreach my $host ( grep(/admin/, keys(%Status)) ) {
+ delete($Status{$host}) if ( $bpc->isAdminJob($host) );
+ }
+ delete($Status{$bpc->scgiJob});
+ }
+}
+
+sub ReadUserEmailInfo
+{
+ if ( (stat("$LogDir/UserEmailInfo.pl"))[9] != $UserEmailInfoMTime ) {
+ do "$LogDir/UserEmailInfo.pl";
+ $UserEmailInfoMTime = (stat("$LogDir/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 "" && $Conf{CgiAdminUsers} ne "*"
+ || $host ne "" && !defined($Hosts->{$host}) );
+ if ( $Conf{CgiAdminUserGroup} ne "" ) {
+ for ( split(/\s+/, $Conf{CgiAdminUserGroup}) ) {
+ my ($n, $p, $gid, $mem) = getgrnam($_);
+ $Privileged ||= ( $mem =~ /\b\Q$User\E\b/ );
+ last if ( $Privileged );
+ }
+ }
+ if ( $Conf{CgiAdminUsers} ne "" ) {
+ $Privileged ||= ($Conf{CgiAdminUsers} =~ /\b\Q$User\E\b/);
+ $Privileged ||= $Conf{CgiAdminUsers} eq "*";
+ }
+ $PrivAdmin = $Privileged;
+ return $Privileged if ( !defined($host) );
+
+ $Privileged ||= $User eq $Hosts->{$host}{user};
+ $Privileged ||= defined($Hosts->{$host}{moreUsers}{$User});
+ return $Privileged;
+}
+
+#
+# Returns the list of hosts that should appear in the navigation bar
+# for this user. If $getAll is set, the admin gets all the hosts.
+# Otherwise, regular users get hosts for which they are the user or
+# are listed in the moreUsers column in the hosts file.
+#
+sub GetUserHosts
+{
+ my($getAll) = @_;
+ my @hosts;
+
+ if ( $getAll && CheckPermission() ) {
+ @hosts = sort keys %$Hosts;
+ } else {
+ @hosts = sort grep { $Hosts->{$_}{user} eq $User ||
+ defined($Hosts->{$_}{moreUsers}{$User}) } keys(%$Hosts);
+ }
+ return @hosts;
+}
+
+#
+# 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.)
+#
+sub ConfirmIPAddress
+{
+ my($host) = @_;
+ my $ipAddr = $host;
+
+ if ( defined($Hosts->{$host}) && $Hosts->{$host}{dhcp}
+ && $ENV{REMOTE_ADDR} =~ /^(\d+[\.\d]*)$/ ) {
+ $ipAddr = $1;
+ my($netBiosHost, $netBiosUser) = $bpc->NetBiosInfoGet($ipAddr);
+ if ( $netBiosHost ne $host ) {
+ my($tryIP);
+ GetStatusInfo("host(${EscURI($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;
+}
+
+###########################################################################
+# HTML layout subroutines
+###########################################################################
+
+sub Header
+{
+ my($title, $content, $noBrowse, $contentSub, $contentPost) = @_;
+ my @adminLinks = (
+ { link => "?action=status", name => $Lang->{Status}},
+ { link => "?action=summary", name => $Lang->{PC_Summary}},
+ { link => "?action=editConfig", name => $Lang->{CfgEdit_Edit_Config},
+ priv => 1},
+ { link => "?action=editConfig&newMenu=hosts",
+ name => $Lang->{CfgEdit_Edit_Hosts},
+ priv => 1},
+ { link => "?action=adminOpts", name => $Lang->{Admin_Options},
+ priv => 1},
+ { link => "?action=view&type=LOG", name => $Lang->{LOG_file},
+ priv => 1},
+ { link => "?action=LOGlist", name => $Lang->{Old_LOGs},
+ priv => 1},
+ { link => "?action=emailSummary", name => $Lang->{Email_summary},
+ priv => 1},
+ { link => "?action=queue", name => $Lang->{Current_queues},
+ priv => 1},
+ { link => "?action=check", name => 'Check',
+ priv => 1},
+ @{$Conf{CgiNavBarLinks} || []},
+ );
+ my $host = $In{host};
+
+ binmode(select, ":utf8");
+ print $Cgi->header(-charset => "utf-8");
+ print <
+
+$title
+
+
+$Conf{CgiHeaders}
+
+
+
+