"Fossies" - the Fresh Open Source Software Archive

Member "BackupPC-4.4.0/lib/BackupPC/CGI/Lib.pm" (20 Jun 2020, 19339 Bytes) of package /linux/privat/BackupPC-4.4.0.tar.gz:


The requested HTML page contains a <FORM> tag that is unusable on "Fossies" in "automatic" (rendered) mode so that page is shown as HTML source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "Lib.pm" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 4.3.2_vs_4.4.0.

    1 #============================================================= -*-perl-*-
    2 #
    3 # BackupPC::CGI::Lib package
    4 #
    5 # DESCRIPTION
    6 #
    7 #   This library defines a BackupPC::Lib class and a variety of utility
    8 #   functions used by BackupPC.
    9 #
   10 # AUTHOR
   11 #   Craig Barratt  <cbarratt@users.sourceforge.net>
   12 #
   13 # COPYRIGHT
   14 #   Copyright (C) 2003-2020  Craig Barratt
   15 #
   16 #   This program is free software: you can redistribute it and/or modify
   17 #   it under the terms of the GNU General Public License as published by
   18 #   the Free Software Foundation, either version 3 of the License, or
   19 #   (at your option) any later version.
   20 #
   21 #   This program is distributed in the hope that it will be useful,
   22 #   but WITHOUT ANY WARRANTY; without even the implied warranty of
   23 #   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   24 #   GNU General Public License for more details.
   25 #
   26 #   You should have received a copy of the GNU General Public License
   27 #   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   28 #
   29 #========================================================================
   30 #
   31 # Version 4.4.0, released 20 Jun 2020.
   32 #
   33 # See http://backuppc.sourceforge.net.
   34 #
   35 #========================================================================
   36 
   37 package BackupPC::CGI::Lib;
   38 
   39 use strict;
   40 use BackupPC::Lib;
   41 
   42 require Exporter;
   43 
   44 use vars qw( @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS );
   45 
   46 use vars qw($Cgi %In $MyURL $User %Conf $TopDir $LogDir $BinDir $bpc);
   47 use vars qw(%Status %Info %Jobs @BgQueue @UserQueue @CmdQueue
   48   %QueueLen %StatusHost);
   49 use vars qw($Hosts $HostsMTime $ConfigMTime $PrivAdmin);
   50 use vars qw(%UserEmailInfo $UserEmailInfoMTime %RestoreReq %ArchiveReq);
   51 use vars qw($Lang);
   52 
   53 @ISA = qw(Exporter);
   54 
   55 @EXPORT = qw( );
   56 
   57 @EXPORT_OK = qw(
   58   timeStamp2
   59   HostLink
   60   UserLink
   61   EscHTML
   62   EscURI
   63   ErrorExit
   64   ServerConnect
   65   GetStatusInfo
   66   ReadUserEmailInfo
   67   CheckPermission
   68   GetUserHosts
   69   ConfirmIPAddress
   70   Header
   71   Trailer
   72   NavSectionTitle
   73   NavSectionStart
   74   NavSectionEnd
   75   NavLink
   76   h1
   77   h2
   78   $Cgi %In $MyURL $User %Conf $TopDir $LogDir $BinDir $bpc
   79   %Status %Info %Jobs @BgQueue @UserQueue @CmdQueue
   80   %QueueLen %StatusHost
   81   $Hosts $HostsMTime $ConfigMTime $PrivAdmin
   82   %UserEmailInfo $UserEmailInfoMTime %RestoreReq %ArchiveReq
   83   $Lang
   84 );
   85 
   86 %EXPORT_TAGS = ('all' => [@EXPORT_OK]);
   87 
   88 sub NewRequest
   89 {
   90     $Cgi = new CGI;
   91     %In  = $Cgi->Vars;
   92 
   93     if ( !defined($bpc) ) {
   94         ErrorExit($Lang->{BackupPC__Lib__new_failed__check_apache_error_log})
   95           if ( !($bpc = BackupPC::Lib->new(undef, undef, undef, 1)) );
   96         $TopDir      = $bpc->TopDir();
   97         $LogDir      = $bpc->LogDir();
   98         $BinDir      = $bpc->BinDir();
   99         %Conf        = $bpc->Conf();
  100         $Lang        = $bpc->Lang();
  101         $ConfigMTime = $bpc->ConfigMTime();
  102         umask($Conf{UmaskMode});
  103     } elsif ( $bpc->ConfigMTime() != $ConfigMTime ) {
  104         $bpc->ConfigRead();
  105         $TopDir      = $bpc->TopDir();
  106         $LogDir      = $bpc->LogDir();
  107         $BinDir      = $bpc->BinDir();
  108         %Conf        = $bpc->Conf();
  109         $Lang        = $bpc->Lang();
  110         $ConfigMTime = $bpc->ConfigMTime();
  111         umask($Conf{UmaskMode});
  112     }
  113 
  114     #
  115     # Default REMOTE_USER so in a miminal installation the user
  116     # has a sensible default.
  117     #
  118     $ENV{REMOTE_USER} = $Conf{BackupPCUser} if ( $ENV{REMOTE_USER} eq "" );
  119 
  120     #
  121     # We require that Apache pass in $ENV{SCRIPT_NAME} and $ENV{REMOTE_USER}.
  122     # The latter requires .ht_access style authentication.  Replace this
  123     # code if you are using some other type of authentication, and have
  124     # a different way of getting the user name.
  125     #
  126     $MyURL = $ENV{SCRIPT_NAME};
  127     $User  = $ENV{REMOTE_USER};
  128 
  129     #
  130     # Handle LDAP uid=user when using mod_authz_ldap and otherwise untaint
  131     #
  132     $User = $1 if ( $User =~ /uid=([^,]+)/i || $User =~ /(.*)/ );
  133 
  134     #
  135     # Clean up %ENV for taint checking
  136     #
  137     delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
  138     $ENV{PATH} = $Conf{MyPath};
  139 
  140     #
  141     # Verify we are running as the correct user
  142     #
  143     if ( $Conf{BackupPCUserVerify} && $> != (my $uid = getpwnam($Conf{BackupPCUser})) ) {
  144         ErrorExit(eval("qq{$Lang->{Wrong_user__my_userid_is___}}"), <<EOF);
  145 This script needs to run as the user specified in \$Conf{BackupPCUser},
  146 which is set to $Conf{BackupPCUser}.
  147 <p>
  148 This is an installation problem.  If you are using mod_perl then
  149 it appears that Apache is not running as user $Conf{BackupPCUser}.
  150 If you are not using mod_perl, then most like setuid is not working
  151 properly on BackupPC_Admin.  Check the permissions on
  152 $Conf{CgiDir}/BackupPC_Admin and look at the documentation.
  153 EOF
  154     }
  155 
  156     if ( !defined($Hosts) || $bpc->HostsMTime() != $HostsMTime ) {
  157         $HostsMTime = $bpc->HostsMTime();
  158         $Hosts      = $bpc->HostInfoRead();
  159 
  160         # turn moreUsers list into a hash for quick lookups
  161         foreach my $host ( keys %$Hosts ) {
  162             $Hosts->{$host}{moreUsers} =
  163               {map { $_, 1 } split(",", $Hosts->{$host}{moreUsers})};
  164         }
  165     }
  166 
  167     #
  168     # Untaint the host name
  169     #
  170     if ( $In{host} =~ /^([\w.\s-]+)$/ ) {
  171         $In{host} = $1;
  172     } else {
  173         delete($In{host});
  174     }
  175 }
  176 
  177 sub timeStamp2
  178 {
  179     my $now = $_[0] == 0 ? time : $_[0];
  180     my($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime($now);
  181     $mon++;
  182     if ( $Conf{CgiDateFormatMMDD} == 2 ) {
  183         $year += 1900;
  184         return sprintf("%04d-%02d-%02d %02d:%02d", $year, $mon, $mday, $hour, $min);
  185     } elsif ( $Conf{CgiDateFormatMMDD} ) {
  186         #
  187         # Add the year if the time is more than 330 days ago
  188         #
  189         if ( time - $now > 330 * 24 * 3600 ) {
  190             $year -= 100;
  191             return sprintf("$mon/$mday/%02d %02d:%02d", $year, $hour, $min);
  192         } else {
  193             return sprintf("$mon/$mday %02d:%02d", $hour, $min);
  194         }
  195     } else {
  196         #
  197         # Add the year if the time is more than 330 days ago
  198         #
  199         if ( time - $now > 330 * 24 * 3600 ) {
  200             $year -= 100;
  201             return sprintf("$mday/$mon/%02d %02d:%02d", $year, $hour, $min);
  202         } else {
  203             return sprintf("$mday/$mon %02d:%02d", $hour, $min);
  204         }
  205     }
  206 }
  207 
  208 sub HostLink
  209 {
  210     my($host) = @_;
  211     my($s);
  212     if ( defined($Hosts->{$host}) ) {
  213         $s = "<a href=\"$MyURL?host=${EscURI($host)}\">$host</a>";
  214     } else {
  215         $s = $host;
  216     }
  217     return \$s;
  218 }
  219 
  220 sub UserLink
  221 {
  222     my($user) = @_;
  223     my($s);
  224 
  225     return \$user if ( $user eq "" || $Conf{CgiUserUrlCreate} eq "" );
  226     if ( $Conf{CgiUserHomePageCheck} eq "" || -f sprintf($Conf{CgiUserHomePageCheck}, $user, $user, $user) ) {
  227         $s = "<a href=\"" . sprintf($Conf{CgiUserUrlCreate}, $user, $user, $user) . "\">$user</a>";
  228     } else {
  229         $s = $user;
  230     }
  231     return \$s;
  232 }
  233 
  234 sub EscHTML
  235 {
  236     my($s) = @_;
  237     $s =~ s/&/&amp;/g;
  238     $s =~ s/\"/&quot;/g;
  239     $s =~ s/>/&gt;/g;
  240     $s =~ s/</&lt;/g;
  241     ### $s =~ s{([^[:print:]])}{sprintf("&\#x%02X;", ord($1));}eg;
  242     return \$s;
  243 }
  244 
  245 sub EscURI
  246 {
  247     my($s) = @_;
  248     $s =~ s{([^\w.\/-])}{sprintf("%%%02X", ord($1));}eg;
  249     return \$s;
  250 }
  251 
  252 sub ErrorExit
  253 {
  254     my(@mesg) = @_;
  255     my($head) = shift(@mesg);
  256     my($mesg) = join("</p>\n<p>", @mesg);
  257 
  258     if ( !defined($ENV{REMOTE_USER}) ) {
  259         $mesg .= <<EOF;
  260 <p>
  261 Note: \$ENV{REMOTE_USER} is not set, which could mean there is an
  262 installation problem.  BackupPC_Admin expects Apache to authenticate
  263 the user and pass their user name into this script as the REMOTE_USER
  264 environment variable.  See the documentation.
  265 EOF
  266     }
  267 
  268     $bpc->ServerMesg("log User $User (host=$In{host}) got CGI error: $head")
  269       if ( defined($bpc) );
  270     if ( !defined($Lang->{Error}) ) {
  271         $mesg = <<EOF if ( !defined($mesg) );
  272 There is some problem with the BackupPC installation.
  273 Please check the permissions on BackupPC_Admin.
  274 EOF
  275         my $content = <<EOF;
  276 ${h1("Error: Unable to read config.pl or language strings!!")}
  277 <p>$mesg</p>
  278 EOF
  279         Header("BackupPC: Error", $content);
  280         Trailer();
  281     } else {
  282         my $content = eval("qq{$Lang->{Error____head}}");
  283         Header(eval("qq{$Lang->{Error}}"), $content);
  284         Trailer();
  285     }
  286     exit(1);
  287 }
  288 
  289 sub ServerConnect
  290 {
  291     #
  292     # Verify that the server connection is ok
  293     #
  294     return if ( $bpc->ServerOK() );
  295     $bpc->ServerDisconnect();
  296     if ( my $err = $bpc->ServerConnect($Conf{ServerHost}, $Conf{ServerPort}) ) {
  297         if (   CheckPermission()
  298             && -f $Conf{ServerInitdPath}
  299             && $Conf{ServerInitdStartCmd} ne "" ) {
  300             my $content = eval("qq{$Lang->{Admin_Start_Server}}");
  301             Header(eval("qq{$Lang->{Unable_to_connect_to_BackupPC_server}}"), $content);
  302             Trailer();
  303             exit(1);
  304         } else {
  305             ErrorExit(
  306                 eval("qq{$Lang->{Unable_to_connect_to_BackupPC_server}}"),
  307                 eval("qq{$Lang->{Unable_to_connect_to_BackupPC_server_error_message}}")
  308             );
  309         }
  310     }
  311 }
  312 
  313 sub GetStatusInfo
  314 {
  315     my($status) = @_;
  316     ServerConnect();
  317     %Status     = () if ( $status =~ /\bhosts\b/ );
  318     %StatusHost = () if ( $status =~ /\bhost\(/ );
  319     my $reply = $bpc->ServerMesg("status $status");
  320     $reply = $1 if ( $reply =~ /(.*)/s );
  321     eval($reply);
  322 
  323     # ignore status related to admin jobs
  324     if ( $status =~ /\bhosts\b/ ) {
  325         foreach my $host ( grep(/admin/, keys(%Status)) ) {
  326             delete($Status{$host}) if ( $bpc->isAdminJob($host) );
  327         }
  328         delete($Status{$bpc->scgiJob});
  329     }
  330 }
  331 
  332 sub ReadUserEmailInfo
  333 {
  334     if ( (stat("$LogDir/UserEmailInfo.pl"))[9] != $UserEmailInfoMTime ) {
  335         do "$LogDir/UserEmailInfo.pl";
  336         $UserEmailInfoMTime = (stat("$LogDir/UserEmailInfo.pl"))[9];
  337     }
  338 }
  339 
  340 #
  341 # Check if the user is privileged.  A privileged user can access
  342 # any information (backup files, logs, status pages etc).
  343 #
  344 # A user is privileged if they belong to the group
  345 # $Conf{CgiAdminUserGroup}, or they are in $Conf{CgiAdminUsers}
  346 # or they are the user assigned to a host in the host file.
  347 #
  348 sub CheckPermission
  349 {
  350     my($host) = @_;
  351     my $Privileged = 0;
  352 
  353     return 0 if ( $User eq "" && $Conf{CgiAdminUsers} ne "*" || $host ne "" && !defined($Hosts->{$host}) );
  354     if ( $Conf{CgiAdminUserGroup} ne "" ) {
  355         for ( split(/\s+/, $Conf{CgiAdminUserGroup}) ) {
  356             my($n, $p, $gid, $mem) = getgrnam($_);
  357             $Privileged ||= ($mem =~ /\b\Q$User\E\b/);
  358             last if ( $Privileged );
  359         }
  360     }
  361     if ( $Conf{CgiAdminUsers} ne "" ) {
  362         $Privileged ||= ($Conf{CgiAdminUsers} =~ /\b\Q$User\E\b/);
  363         $Privileged ||= $Conf{CgiAdminUsers} eq "*";
  364     }
  365     $PrivAdmin = $Privileged;
  366     return $Privileged if ( !defined($host) );
  367 
  368     $Privileged ||= $User eq $Hosts->{$host}{user};
  369     $Privileged ||= defined($Hosts->{$host}{moreUsers}{$User});
  370     return $Privileged;
  371 }
  372 
  373 #
  374 # Returns the list of hosts that should appear in the navigation bar
  375 # for this user.  If $getAll is set, the admin gets all the hosts.
  376 # Otherwise, regular users get hosts for which they are the user or
  377 # are listed in the moreUsers column in the hosts file.
  378 #
  379 sub GetUserHosts
  380 {
  381     my($getAll) = @_;
  382     my @hosts;
  383 
  384     if ( $getAll && CheckPermission() ) {
  385         @hosts = sort keys %$Hosts;
  386     } else {
  387         @hosts = sort grep { $Hosts->{$_}{user} eq $User || defined($Hosts->{$_}{moreUsers}{$User}) } keys(%$Hosts);
  388     }
  389     return @hosts;
  390 }
  391 
  392 #
  393 # Given a host name tries to find the IP address.  For non-dhcp hosts
  394 # we just return the host name.  For dhcp hosts we check the address
  395 # the user is using ($ENV{REMOTE_ADDR}) and also the last-known IP
  396 # address for $host.  (Later we should replace this with a broadcast
  397 # nmblookup.)
  398 #
  399 sub ConfirmIPAddress
  400 {
  401     my($host) = @_;
  402     my $ipAddr = $host;
  403 
  404     if ( defined($Hosts->{$host}) && $Hosts->{$host}{dhcp} && $ENV{REMOTE_ADDR} =~ /^(\d+[\.\d]*)$/ ) {
  405         $ipAddr = $1;
  406         my($netBiosHost, $netBiosUser) = $bpc->NetBiosInfoGet($ipAddr);
  407         if ( $netBiosHost ne $host ) {
  408             my($tryIP);
  409             GetStatusInfo("host(${EscURI($host)})");
  410             if ( defined($StatusHost{dhcpHostIP}) && $StatusHost{dhcpHostIP} ne $ipAddr ) {
  411                 $tryIP = eval("qq{$Lang->{tryIP}}");
  412                 ($netBiosHost, $netBiosUser) = $bpc->NetBiosInfoGet($StatusHost{dhcpHostIP});
  413             }
  414             if ( $netBiosHost ne $host ) {
  415                 ErrorExit(eval("qq{$Lang->{Can_t_find_IP_address_for}}"), eval("qq{$Lang->{host_is_a_DHCP_host}}"));
  416             }
  417             $ipAddr = $StatusHost{dhcpHostIP};
  418         }
  419     }
  420     return $ipAddr;
  421 }
  422 
  423 ###########################################################################
  424 # HTML layout subroutines
  425 ###########################################################################
  426 
  427 sub Header
  428 {
  429     my($title, $content, $noBrowse, $contentSub, $contentPost) = @_;
  430     my @adminLinks = (
  431         {link => "?action=status",  name => $Lang->{Status}},
  432         {link => "?action=summary", name => $Lang->{PC_Summary}},
  433         {
  434             link => "?action=editConfig",
  435             name => $Lang->{CfgEdit_Edit_Config},
  436             priv => 1
  437         },
  438         {
  439             link => "?action=editConfig&newMenu=hosts",
  440             name => $Lang->{CfgEdit_Edit_Hosts},
  441             priv => 1
  442         },
  443         {
  444             link => "?action=adminOpts",
  445             name => $Lang->{Admin_Options},
  446             priv => 1
  447         },
  448         {
  449             link => "?action=view&type=LOG",
  450             name => $Lang->{LOG_file},
  451             priv => 1
  452         },
  453         {
  454             link => "?action=LOGlist",
  455             name => $Lang->{Old_LOGs},
  456             priv => 1
  457         },
  458         {
  459             link => "?action=emailSummary",
  460             name => $Lang->{Email_summary},
  461             priv => 1
  462         },
  463         {
  464             link => "?action=queue",
  465             name => $Lang->{Current_queues},
  466             priv => 1
  467         },
  468         @{$Conf{CgiNavBarLinks} || []},
  469     );
  470     my $host = $In{host};
  471 
  472     binmode(select, ":utf8");
  473     print $Cgi->header(-charset => "utf-8");
  474     print <<EOF;
  475 <!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN">
  476 <html><head>
  477 <title>$title</title>
  478 <link rel=stylesheet type="text/css" href="$Conf{CgiImageDirURL}/$Conf{CgiCSSFile}?version=2" title="CSSFile">
  479 
  480 <link rel="apple-touch-icon" sizes="180x180" href="$Conf{CgiImageDirURL}/apple-touch-icon.png?v=2">
  481 <link rel="icon" type="image/png" sizes="32x32" href="$Conf{CgiImageDirURL}/favicon-32x32.png?v=2">
  482 <link rel="icon" type="image/png" sizes="16x16" href="$Conf{CgiImageDirURL}/favicon-16x16.png?v=2">
  483 <link rel="mask-icon" href="$Conf{CgiImageDirURL}/safari-pinned-tab.svg?v=2" color="#5bbad5">
  484 <link rel="shortcut icon" href="$Conf{CgiImageDirURL}/favicon.ico?v=2">
  485 
  486 $Conf{CgiHeaders}
  487 <script src="$Conf{CgiImageDirURL}/sorttable.js"></script>
  488 </head><body onLoad="document.getElementById('NavMenu').style.height=document.body.scrollHeight">
  489 
  490 <div id="navigation-container">
  491     <div id="logo-container">
  492         <a href="https://backuppc.github.io/backuppc/"><img src="$Conf{CgiImageDirURL}/logo320.png"></a>
  493     </div>
  494 EOF
  495 
  496     if ( defined($Hosts) && defined($host) && defined($Hosts->{$host}) ) {
  497         print "<div class=\"NavMenu section-title\">";
  498         NavSectionTitle("${EscHTML($host)}");
  499         print <<EOF;
  500 </div>
  501 <div class="NavMenu host">
  502 EOF
  503         NavLink("?host=${EscURI($host)}",               "$host $Lang->{Home}", " class=\"navbar\"");
  504         NavLink("?action=browse&host=${EscURI($host)}", $Lang->{Browse},       " class=\"navbar\"") if ( !$noBrowse );
  505         NavLink("?action=view&type=LOG&host=${EscURI($host)}", $Lang->{LOG_file},  " class=\"navbar\"");
  506         NavLink("?action=LOGlist&host=${EscURI($host)}",       $Lang->{LOG_files}, " class=\"navbar\"");
  507         if (   -f "$TopDir/pc/$host/SmbLOG.bad"
  508             || -f "$TopDir/pc/$host/SmbLOG.bad.z"
  509             || -f "$TopDir/pc/$host/XferLOG.bad"
  510             || -f "$TopDir/pc/$host/XferLOG.bad.z" ) {
  511             NavLink(
  512                 "?action=view&type=XferLOGbad&host=${EscURI($host)}",
  513                 $Lang->{Last_bad_XferLOG},
  514                 " class=\"navbar\""
  515             );
  516             NavLink(
  517                 "?action=view&type=XferErrbad&host=${EscURI($host)}",
  518                 $Lang->{Last_bad_XferLOG_errors_only},
  519                 " class=\"navbar\""
  520             );
  521         }
  522         if ( $Conf{CgiUserConfigEditEnable} || $PrivAdmin ) {
  523             NavLink("?action=editConfig&host=${EscURI($host)}", $Lang->{CfgEdit_Edit_Config}, " class=\"navbar\"");
  524         } elsif ( -f "$TopDir/pc/$host/config.pl" || ($host ne "config" && -f "$TopDir/conf/$host.pl") ) {
  525             NavLink("?action=view&type=config&host=${EscURI($host)}", $Lang->{Config_file}, " class=\"navbar\"");
  526         }
  527         print "</div>\n";
  528     }
  529     print <<EOF;
  530 <div class="NavMenu" id="NavMenu">
  531 EOF
  532     my $hostSelectbox = "<option value=\"#\">$Lang->{Select_a_host}</option>";
  533     my @hosts         = GetUserHosts($Conf{CgiNavBarAdminAllHosts});
  534     NavSectionTitle($Lang->{Hosts});
  535     if ( defined($Hosts) && %$Hosts > 0 && @hosts ) {
  536         foreach my $host ( @hosts ) {
  537             NavLink("?host=${EscURI($host)}", $host)
  538               if ( @hosts < $Conf{CgiNavBarAdminAllHosts} );
  539             my $sel = " selected" if ( $host eq $In{host} );
  540             $hostSelectbox .= "<option value=\"?host=${EscURI($host)}\"$sel>$host</option>";
  541         }
  542     }
  543     if ( @hosts >= $Conf{CgiNavBarAdminAllHosts} ) {
  544         print <<EOF;
  545 <select onChange="document.location=this.value">
  546 $hostSelectbox
  547 </select>
  548 EOF
  549     }
  550     if ( $Conf{CgiSearchBoxEnable} ) {
  551         print <<EOF;
  552 <form action="$MyURL" method="get">
  553     <input type="text" name="host" size="14" maxlength="64">
  554     <input type="hidden" name="action" value="hostInfo"><input type="submit" value="$Lang->{Go}" name="ignore">
  555     </form>
  556 EOF
  557     }
  558     NavSectionTitle($Lang->{NavSectionTitle_});
  559     foreach my $l ( @adminLinks ) {
  560         if ( $PrivAdmin || !$l->{priv} ) {
  561             my $txt = $l->{lname} ne "" ? $Lang->{$l->{lname}} : $l->{name};
  562             NavLink($l->{link}, $txt);
  563         }
  564     }
  565 
  566     print <<EOF;
  567 </div>
  568 </div> <!-- end #navigation-container -->
  569 EOF
  570 
  571     print("<div id=\"Content\">\n$content\n");
  572     if ( defined($contentSub) && ref($contentSub) eq "CODE" ) {
  573         while ( (my $s = &$contentSub()) ne "" ) {
  574             print($s);
  575         }
  576     }
  577     print($contentPost) if ( defined($contentPost) );
  578 }
  579 
  580 sub Trailer
  581 {
  582     print <<EOF;
  583 </body></html>
  584 EOF
  585 }
  586 
  587 sub NavSectionTitle
  588 {
  589     my($head) = @_;
  590     print <<EOF;
  591 <h2 class="NavTitle">$head</h2>
  592 EOF
  593 }
  594 
  595 sub NavSectionStart
  596 {
  597 }
  598 
  599 sub NavSectionEnd
  600 {
  601 }
  602 
  603 sub NavLink
  604 {
  605     my($link, $text) = @_;
  606     if ( defined($link) ) {
  607         my($class);
  608         $class = " class=\"NavCurrent\""
  609           if ( length($link) && $ENV{REQUEST_URI} =~ /\Q$link\E$/
  610             || length($link) && $link =~ /\&host=/ && $ENV{REQUEST_URI} =~ /\Q$link\E/
  611             || $link eq "" && $ENV{REQUEST_URI} !~ /\?/ );
  612         $link = "$MyURL$link" if ( $link eq "" || $link =~ /^\?/ );
  613         print <<EOF;
  614 <a href="$link"$class>$text</a>
  615 EOF
  616     } else {
  617         print <<EOF;
  618 $text<br>
  619 EOF
  620     }
  621 }
  622 
  623 sub h1
  624 {
  625     my($str) = @_;
  626     return \<<EOF;
  627     <h1>$str</h1>
  628 EOF
  629 }
  630 
  631 sub h2
  632 {
  633     my($str) = @_;
  634     return \<<EOF;
  635     <h2>$str</h2>
  636 EOF
  637 }