Files
bpfire/html/cgi-bin/backup.cgi
Stefan Schantl c797789c1f backup.cgi: Read and deliver the requested backup in pieces
Read-in the requested backup file line by line and directly deliver this
as stream to the client.

This fixes the problem with very big backups on systems with very
limited RAM, which resulted in an OOM kill of the CGI and delivery
process.

Fixes #13096.

Signed-off-by: Stefan Schantl <stefan.schantl@ipfire.org>
Reviewed-by: Michael Tremer <michael.tremer@ipfire.org>
2023-05-11 20:07:13 +00:00

379 lines
15 KiB
Perl

#!/usr/bin/perl
###############################################################################
# #
# IPFire.org - A linux based firewall #
# Copyright (C) 2005-2013 IPFire Team <info@ipfire.org> #
# #
# 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 <http://www.gnu.org/licenses/>. #
# #
###############################################################################
use strict;
# enable only the following on debugging purpose
#use warnings;
#use CGI::Carp 'fatalsToBrowser';
use File::Copy;
use File::Basename;
require '/var/ipfire/general-functions.pl';
require "${General::swroot}/lang.pl";
require "${General::swroot}/header.pl";
my %color = ();
my %mainsettings = ();
my %cgiparams=();
my %checked = ();
my $message = "";
my $errormessage = "";
my @backups = "";
my @backupisos = "";
$a = new CGI;
&General::readhash("${General::swroot}/main/settings", \%mainsettings);
&General::readhash("/srv/web/ipfire/html/themes/ipfire/include/colors.txt", \%color);
$cgiparams{'ACTION'} = '';
$cgiparams{'FILE'} = '';
$cgiparams{'UPLOAD'} = '';
$cgiparams{'BACKUPLOGS'} = '';
&Header::getcgihash(\%cgiparams);
############################################################################################################################
################################################ Workaround for Directories ################################################
&General::system("/usr/local/bin/backupctrl", "makedirs") unless ( -e '/var/ipfire/backup/addons/backup') ;
############################################################################################################################
############################################## System calls ohne Http Header ###############################################
if ($cgiparams{'ACTION'} eq "download") {
my $file = &sanitise_file($cgiparams{'FILE'});
exit(1) unless defined($file);
&deliver_file($file);
exit(0);
} elsif ($cgiparams{'ACTION'} eq "downloadiso") {
my $file = &sanitise_file($cgiparams{'FILE'});
exit(1) unless defined($file);
&deliver_file($file);
exit(0);
} elsif ($cgiparams{'ACTION'} eq "downloadaddon") {
my $file = &sanitise_file($cgiparams{'FILE'});
exit(1) unless defined($file);
&deliver_file($file);
exit(0);
} elsif ( $cgiparams{'ACTION'} eq "restore") {
my $upload = $a->param("UPLOAD");
open UPLOADFILE, ">/tmp/restore.ipf";
binmode $upload;
while ( <$upload> ) {
print UPLOADFILE;
}
close UPLOADFILE;
&General::system("/usr/local/bin/backupctrl", "restore");
}
elsif ( $cgiparams{'ACTION'} eq "restoreaddon" )
{
chomp($cgiparams{'UPLOAD'});
# we need to fix cause IE7 gives the full path and FF only the filename
my @temp = split(/\\/,$cgiparams{'UPLOAD'});
my $upload = $a->param("UPLOAD");
open UPLOADFILE, ">/tmp/".$temp[$#temp];
binmode $upload;
while ( <$upload> ) {
print UPLOADFILE;
}
close UPLOADFILE;
&General::system("/usr/local/bin/backupctrl", "restoreaddon", $temp[$#temp]);
}
&Header::showhttpheaders();
sub refreshpage{&Header::openbox( 'Waiting', 1, "<meta http-equiv='refresh' content='1;'>" );print "<center><img src='/images/clock.gif' alt='' /><br/><font color='red'>$Lang::tr{'pagerefresh'}</font></center>";&Header::closebox();}
&Header::openpage($Lang::tr{'backup'}, 1, "");
&Header::openbigbox('100%', 'left', '', $errormessage);
############################################################################################################################
################################################### Default System calls ###################################################
if ( $cgiparams{'ACTION'} eq "backup" )
{
if ( $cgiparams{'BACKUPLOGS'} eq "include" ) {
&General::system("/usr/local/bin/backupctrl", "include");
} elsif ( $cgiparams{'BACKUPLOGS'} eq "exclude" ) {
&General::system("/usr/local/bin/backupctrl", "exclude");
} elsif ( $cgiparams{'BACKUPLOGS'} eq "iso" ) {
&General::system_background("/usr/local/bin/backupctrl", "iso");
}
}
if ( $cgiparams{'ACTION'} eq "addonbackup" )
{
# Exit if there is any dots or slashes in the addon name
exit(1) if ($cgiparams{'ADDON'} =~ /(\.|\/)/);
# Check if the addon exists
exit(1) unless (-e "/var/ipfire/backup/addons/includes/$cgiparams{'ADDON'}");
&General::system("/usr/local/bin/backupctrl", "addonbackup", "$cgiparams{'ADDON'}");
}
elsif ( $cgiparams{'ACTION'} eq "delete" )
{
my $file = &sanitise_file($cgiparams{'FILE'});
exit(1) unless defined($file);
&General::system("/usr/local/bin/backupctrl", "$file");
}
############################################################################################################################
############################################ Backups des Systems erstellen #################################################
if ( $message ne "" ){
&Header::openbox('100%','left',$Lang::tr{'error messages'});
print "<font color='red'>$message</font>\n";
&Header::closebox();
}
if ( -e "/var/ipfire/backup/" ){
@backups = `cd /var/ipfire/backup/ && ls *.ipf 2>/dev/null`;
}
if ( -e "/var/tmp/backupiso/" ){
@backupisos = `cd /var/tmp/backupiso/ && ls *.iso 2>/dev/null`;
}
&Header::openbox('100%', 'center', );
print <<END
<form method='post' action='$ENV{'SCRIPT_NAME'}'>
<table width='95%' cellspacing='0'>
<tr>
<td align='left' width='40%'>$Lang::tr{'logs'}</td>
<td align='left'>
<input type='radio' name='BACKUPLOGS' value='include'/> $Lang::tr{'include logfiles'}<br/>
<input type='radio' name='BACKUPLOGS' value='exclude' checked='checked'/> $Lang::tr{'exclude logfiles'}<br/>
END
;
my $MACHINE=`uname -m`;
if ( ! ( $MACHINE =~ "arm" )) {
print" <input type='radio' name='BACKUPLOGS' value='iso' /> $Lang::tr{'generate iso'}<br/>"
}
print <<END
</td>
</tr>
<tr><td align='center' colspan='2'>
<input type='hidden' name='ACTION' value='backup' />
<input type='image' alt='$Lang::tr{'backup'}' title='$Lang::tr{'backup'}' src='/images/document-save.png' />
</td></tr>
</table>
</form>
END
;
&Header::closebox();
############################################################################################################################
############################################ Backups des Systems downloaden ################################################
&Header::openbox('100%', 'center', $Lang::tr{'backups'});
print <<END
<table width='95%' cellspacing='0'>
END
;
foreach (@backups){
if ( $_ !~ /ipf$/){next;}
chomp($_);
my $Datei = "/var/ipfire/backup/".$_;
my @Info = stat($Datei);
my $Size = $Info[7] / 1024 / 1024;
$Size = sprintf("%0.2f", $Size);
print "<tr><td align='center'>$Lang::tr{'backup from'} <b>$_</b> $Lang::tr{'size'} $Size MB</td><td width='5'><form method='post' action='$ENV{'SCRIPT_NAME'}'><input type='hidden' name='ACTION' value='download' /><input type='hidden' name='FILE' value='$_' /><input type='image' alt='$Lang::tr{'download'}' title='$Lang::tr{'download'}' src='/images/package-x-generic.png' /></form></td>";
print "<td width='5'><form method='post' action='$ENV{'SCRIPT_NAME'}'><input type='hidden' name='ACTION' value='delete' /><input type='hidden' name='FILE' value='$_' /><input type='image' alt='$Lang::tr{'delete'}' title='$Lang::tr{'delete'}' src='/images/user-trash.png' /></form></td></tr>";
}
foreach (@backupisos){
if ( $_ !~ /iso$/){next;}
chomp($_);
my $Datei = "/var/tmp/backupiso/".$_;
my @Info = stat($Datei);
my $Size = $Info[7] / 1024 / 1024;
$Size = sprintf("%0.2f", $Size);
print "<tr><td align='center'>$Lang::tr{'backup from'} <b>$_</b> $Lang::tr{'size'} $Size MB</td><td width='5'><form method='post' action='$ENV{'SCRIPT_NAME'}'><input type='hidden' name='ACTION' value='downloadiso' /><input type='hidden' name='FILE' value='$_' /><input type='image' alt='$Lang::tr{'download'}' title='$Lang::tr{'download'}' src='/images/package-x-generic.png' /></form></td>";
print "<td width='5'><form method='post' action='$ENV{'SCRIPT_NAME'}'><input type='hidden' name='ACTION' value='delete' /><input type='hidden' name='FILE' value='$_' /><input type='image' alt='$Lang::tr{'delete'}' title='$Lang::tr{'delete'}' src='/images/user-trash.png' /></form></td></tr>";
}
print <<END
</table>
END
;
&Header::closebox();
############################################################################################################################
############################################# Backups von Addons erstellen #################################################
&Header::openbox('100%', 'center', $Lang::tr{'addons'});
my @addonincluds = `ls /var/ipfire/backup/addons/includes/ 2>/dev/null`;
my @addons = `ls /var/ipfire/backup/addons/backup/ 2>/dev/null`;
my %addons;
foreach (@addons){
my $addon=substr($_,0,length($_)-5);
$addons{$addon}='';
}
print "<table width='95%' cellspacing='0'>";
foreach (@addonincluds){
chomp($_);
delete $addons{$_};
my $Datei = "/var/ipfire/backup/addons/backup/".$_.".ipf";
my @Info = stat($Datei);
my $Size = $Info[7] / 1024;
if ( -e $Datei ){
if ($Size < 1) {
$Size = sprintf("%.2f", $Size);
print "<tr><td align='center'>$Lang::tr{'backup from'} <b>$_</b> $Lang::tr{'size'} $Size KB $Lang::tr{'date'} ".localtime($Info[9])."</td>";
} else {
$Size = sprintf("%2d", $Size);
print "<tr><td align='center'>$Lang::tr{'backup from'} <b>$_</b> $Lang::tr{'size'} $Size KB $Lang::tr{'date'} ".localtime($Info[9])."</td>";
}
print <<END
<td align='right' width='5'>
<form method='post' action='$ENV{'SCRIPT_NAME'}'>
<input type='hidden' name='ACTION' value='downloadaddon' />
<input type='hidden' name='FILE' value='$_.ipf' />
<input type='image' alt='$Lang::tr{'download'}' title='$Lang::tr{'download'}' src='/images/package-x-generic.png' />
</form>
</td>
<td align='right' width='5'>
<form method='post' action='$ENV{'SCRIPT_NAME'}'>
<input type='hidden' name='ACTION' value='delete' />
<input type='hidden' name='FILE' value='$_.ipf' />
<input type='image' alt='$Lang::tr{'delete'}' title='$Lang::tr{'delete'}' src='/images/user-trash.png' />
</form>
</td>
END
;
}
else{
print "<tr><td align='center'>$Lang::tr{'backup from'} <b>$_</b> </td><td width='5' align='right'></td><td width='5' align='right'></td>";
}
print <<END
<td align='right' width='5'>
<form method='post' action='$ENV{'SCRIPT_NAME'}'>
<input type='hidden' name='ACTION' value='addonbackup' />
<input type='hidden' name='ADDON' value='$_' />
<input type='image' alt='$Lang::tr{'backup'}' title='$Lang::tr{'backup'}' src='/images/document-save.png' />
</form>
</td></tr>
END
;
}
foreach (keys(%addons)){
chomp($_);
my $Datei = "/var/ipfire/backup/addons/backup/".$_.".ipf";
my @Info = stat($Datei);
my $Size = $Info[7] / 1024;
$Size = sprintf("%2d", $Size);
print "<tr><td align='center'>$Lang::tr{'backup from'} <b>$_</b> $Lang::tr{'size'} $Size KB $Lang::tr{'date'} ".localtime($Info[9])."</td>";
print <<END
<td align='right' width='5'>
<form method='post' action='$ENV{'SCRIPT_NAME'}'>
<input type='hidden' name='ACTION' value='downloadaddon' />
<input type='hidden' name='FILE' value='$_.ipf' />
<input type='image' alt='$Lang::tr{'download'}' title='$Lang::tr{'download'}' src='/images/package-x-generic.png' />
</form>
</td>
<td align='right' width='5'>
<form method='post' action='$ENV{'SCRIPT_NAME'}'>
<input type='hidden' name='ACTION' value='delete' />
<input type='hidden' name='FILE' value='$_.ipf' />
<input type='image' alt='$Lang::tr{'delete'}' title='$Lang::tr{'delete'}' src='/images/user-trash.png' />
</form>
</td>
<td align='right' width='5'></td></tr>
END
;
}
print "</table>";
&Header::closebox();
############################################################################################################################
####################################### Backups des Systems wiederherstellen ###############################################
&Header::openbox('100%', 'center', $Lang::tr{'restore'});
print <<END
<table width='95%' cellspacing='0'>
<tr><td align='center' colspan='2'><font color='red'><br />$Lang::tr{'backupwarning'}</font><br /><br /></td></tr>
<tr><td align='left'>$Lang::tr{'backup'}</td><td align='left'><form method='post' enctype='multipart/form-data' action='$ENV{'SCRIPT_NAME'}'><input type="file" size='50' name="UPLOAD" /><input type='hidden' name='ACTION' value='restore' /><input type='hidden' name='FILE' /><input type='image' alt='$Lang::tr{'restore'}' title='$Lang::tr{'restore'}' src='/images/media-floppy.png' /></form></td></tr>
<tr><td align='left'>$Lang::tr{'backupaddon'}</td><td align='left'><form method='post' enctype='multipart/form-data' action='$ENV{'SCRIPT_NAME'}'><input type="file" size='50' name="UPLOAD" /><input type='hidden' name='ACTION' value='restoreaddon' /><input type='hidden' name='FILE' /><input type='image' alt='$Lang::tr{'restore'}' title='$Lang::tr{'restore'}' src='/images/media-floppy.png' /></form></td></tr>
</table>
END
;
&Header::closebox();
&Header::closebigbox();
&Header::closepage();
sub sanitise_file() {
my $file = shift;
# Filenames cannot contain any slashes
return undef if ($file =~ /\//);
# File must end with .ipf or .iso
return undef unless ($file =~ /\.(ipf|iso)$/);
# Convert to absolute path
if (-e "/var/ipfire/backup/$file") {
return "/var/ipfire/backup/$file";
} elsif (-e "/var/ipfire/backup/addons/backup/$file") {
return "/var/ipfire/backup/addons/backup/$file";
} elsif (-e "/var/tmp/backupiso/$file") {
return "/var/tmp/backupiso/$file";
}
# File does not seem to exist
return undef;
}
sub deliver_file() {
my $file = shift;
my @stat = stat($file);
# Print headers
print "Content-Disposition: attachment; filename=" . &File::Basename::basename($file) . "\n";
print "Content-Type: application/octet-stream\n";
print "Content-Length: $stat[7]\n";
print "\n";
# Deliver content
open(FILE, "<$file") or die "Unable to open $file: $!";
# Read the file line by line and send it
# to the client.
while (<FILE>) {
print $_;
}
# Close file handle.
close(FILE);
}