#!/usr/bin/perl -wT
#----------------------------------------------------------------------
# heading     : Your Settings
# description : Data Backup
# longdesc    : backup user data
# navigation  : 100 200
#
# Copyright (c) 2001 Mitel Networks Corporation
#               2002-5 Stephen Noble <support@dungog.net>
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
#----------------------------------------------------------------------

package esmith;

use strict;
use CGI ':all';
use CGI::Carp qw(fatalsToBrowser);

use esmith::cgi;
use esmith::config;
use esmith::util;
use esmith::db;
use esmith::lockfile;

sub showInitial ($$);
sub performAndShowResult ($);
sub desktopBackup ();
sub desktopRestore ();
sub desktopVerify ();
sub performDesktopRestore ($);
sub performDesktopVerify ($);
sub performDiskUsage ($);

BEGIN
{
    # Clear PATH and related environment variables so that calls to
    # external programs do not cause results to be tainted. See
    # "perlsec" manual page for details.

    $ENV {'PATH'} = '';
    $ENV {'SHELL'} = '/bin/bash';
    delete $ENV {'ENV'};
}

esmith::util::setRealToEffective ();

    my %conf;
    tie %conf, 'esmith::config';

    my %accounts;
    tie %accounts, 'esmith::config', '/home/e-smith/db/accounts';

    my $acctName = $ENV{'REMOTE_USER'};

    if ($acctName =~ /^([a-z][\-\_\.a-z0-9]*)$/)
    {
        $acctName = $1;
    }

    my @directories = ("/home/e-smith/files/users/$acctName/");


# Unbuffer standard output so that files and directories are listed as
# they are restored
$| = 1;

# Store away current gid of 'www' group.
my $www_gid = getgrnam("www");

#------------------------------------------------------------
# examine state parameter and display the appropriate form
#------------------------------------------------------------

my $q = new CGI;

if (! grep (/^state$/, $q->param))
{
    showInitial ($q, '');
}
elsif ($q->param ('state') eq "perform")
{
    performAndShowResult ($q);
}
elsif ($q->param ('state') eq "desktop-restore")
{
    performDesktopRestore($q);
}
elsif ($q->param ('state') eq "desktop-verify")
{
    performDesktopVerify($q);
}
elsif ($q->param ('state') eq "diskUsage")
{
    performDiskUsage ($q);
}
else
{
    esmith::cgi::genStateError ($q, \%conf);
}

exit (0);

#------------------------------------------------------------
# subroutine to display initial form
#------------------------------------------------------------

sub showInitial ($$)
{
    my ($q, $msg) = @_;

    if ($msg eq '')
    {
    	esmith::cgi::genHeaderNonCacheable ($q, \%conf,
	  	'Backup or restore user data');
    }
    else
    {
	    esmith::cgi::genHeaderNonCacheable
	    ($q, \%conf, 'Operation status report');

	    print $q->p ($msg);
	    print $q->hr;
    }


    print $q->p (
	    "This page allows you to create a copy of <b>your "
	    . "own</b> user data files, from your home directory "
	    . "and downloads it to your local desktop via your web "
	    . "browser. The Verify desktop backup file option can be used to\n"
	    . "view a list of files in a desktop backup file.\n");

    print $q->p ($q->a ({href => $q->url (-absolute => 1) . "?state=diskUsage"},
                    'Click here'),
                    'to check/refresh the disk usage for this user shown below, it may take
                     a minute the first time but should be faster afterwards.');

    #show usage
    if ( -e "/tmp/du-$acctName" )
    {
     my $du = '0';

     open (INF,"/tmp/du-$acctName")
      or die ("can't open du to read: $1. ");

     while (<INF>)
     {
       ($du, undef) = split(/\t/, $_);
     }
     close INF;

     print $q->p ("When last checked with the link above your configuration "
	     . "and data files totaled <b>$du</b>. Your backup will be compressed to approx. "
       . "half that size.\n");
    }

    my %labels = (
	    "desktop-backup"    =>      "Backup to desktop",
	    "desktop-restore"   =>      "Restore from desktop",
	    "desktop-verify"    =>      "Verify desktop backup file",
	);

    my @labels = (
	    'desktop-backup',
	    'desktop-restore',
	    'desktop-verify',
	);

    my $default_action = 'desktop-backup';

    print $q->startform (-method => 'POST',
                     -action => $q->url (-absolute => 1));

    print $q->table ({border => 0, cellspacing => 0, cellpadding => 4});

    print $q->Tr (esmith::cgi::genWidgetRow($q,"Select an action",
		    $q->popup_menu (-name => 'function',
			                  -values => [ @labels ],
			                  -default => $default_action,
			                  -labels => \%labels)));

    print "</table>\n";

    print '<p>';

    print $q->Tr (
	    esmith::cgi::genButtonRow(
		    $q,
		    $q->submit (-name => 'action', -value => 'Perform')
		)
	);

    print $q->hidden (
	    -name => 'state',
	    -override => 1,
	    -default => 'perform'
	);

    print $q->endform;

    #    print $q->p ($q->hr, $q->font ({size => "-1"}, "www.dungog.net/sme"));
    esmith::cgi::genFooter($q);


    print '</FONT>';
    print '</DIV>';
    print $q->end_html;
}

sub performAndShowResult ($)
{
    my ($q) = @_;

    my $function = $q->param ('function');

    if ($function eq 'refresh')
    {
	    showInitial ($q, '');
    }
    elsif ($function eq 'desktop-backup')
    {
	    desktopBackup();
    }
    elsif ($function eq 'desktop-restore')
    {
	    desktopRestore();
    }
    elsif ($function eq 'desktop-verify')
    {
	    desktopVerify();
    }
    else
    {
	    # Unknown function - refresh the screen anyway
	    showInitial ($q, 'unknown');
    }

    return;

    esmith::cgi::genHeaderNonCacheable ($q, \%conf,
	'X Backup or restore server data');
    print $q->p ( $function );
    esmith::cgi::genFooter ($q);
}

sub desktopBackup ()
{

    my $directories = $q->param ('directories');

    # Generate a header that will trigger a download and send data as
    # an octet stream.

    print "Expires: 0\n";
    print "Content-type: application/octet-stream\n";
    print "Content-disposition: inline; filename=$acctName.tgz\n";
    print "\n";

    open(RD,
	    "/bin/tar --directory / --create @directories --file=-"
	    . " | /usr/bin/gzip |");

    while (<RD>)
    {
	    print;
    }

    close RD;
}

sub desktopRestore ()
{
    esmith::cgi::genHeaderNonCacheable ($q, \%conf,
	'Restore server configuration');

    print $q->p (
	    "This process will upload a SME Server "
	    . " backup file from your "
	    . "local desktop to your server and "
	    . "restore the configuration and user data files.\n");

    print $q->p (
	    "Be very sure you are uploading the correct file, "
	    . "files with the same name will be overwritten.\n");

    print $q->start_multipart_form(
	    -method => 'POST',
	    -action => $q->url (-absolute => 1));

    print $q->table (
	    {border => 0, cellspacing => 0, cellpadding => 4},

	esmith::cgi::genWidgetRow(
		$q,
		"Backup file to restore from",
		$q->filefield(
			-name    => 'backupFile',
			-default => "$acctName.tgz",
			-size    => 32)),

	    esmith::cgi::genButtonRow(
		    $q,
		    $q->submit(
			    -name => 'action',
			    -value => 'Restore')));

    print $q->hidden(
	    -name => 'state',
	    -override => 1,
	    -default => 'desktop-restore');

    print $q->endform;

    esmith::cgi::genFooter ($q);
}

sub desktopVerify ()
{
    esmith::cgi::genHeaderNonCacheable ($q, \%conf,
	                         'Verify desktop backup file');

    print $q->p (
	    "This option will display the names of all files\n"
	    . "in a previously created desktop backup file. You\n"
	    . "can use this option to verify the contents of the\n"
	    . "backup file.\n"
	);

    print $q->start_multipart_form(
	    -method => 'POST',
	    -action => $q->url (-absolute => 1)
	);

    print $q->table (
	    {border => 0, cellspacing => 0, cellpadding => 4},

	esmith::cgi::genWidgetRow(
		$q,
		"Select backup file",
		$q->filefield(
			-name    => 'backupFile',
			-default => "$acctName.tgz",
			-size    => 32
		    )
		),

	    esmith::cgi::genButtonRow(
		    $q,
		    $q->submit(
			    -name => 'action',
			    -value => 'Verify'
			)
		)
	);

    print $q->hidden(
	    -name => 'state',
	    -override => 1,
	    -default => 'desktop-verify'
	);

    print $q->endform;

    esmith::cgi::genFooter ($q);
}

sub performDesktopRestore ($)
{
    my ($q) = @_;

    # Need to validate this here: $q->param ('backupFile');
    my $lock_file = "/var/lock/subsys/e-smith-restore";
    my $file_handle = &esmith::lockfile::LockFileOrReturn($lock_file);

    unless ($file_handle)
    {
	    esmith::cgi::genHeaderNonCacheable(
	  	$q,
	  	\%conf,
		  "Unable to proceed with restore of server configuration"
	    );

	    print $q->p (
	      $q->b (
		    "Another restore is in progress.",
		    "Please try again later."
		   )
	    );

	esmith::cgi::genFooter ($q);
	return;
    }

    # no need to reboot after restoring users data
    #db_set_prop(\%restore, 'restore', 'state', 'running');
    #db_set_prop(\%restore, 'restore', 'start', time);
    #db_set_prop(\%conf, 'bootstrap-console', 'Run', 'yes');

    if (open(RD, "-|"))
    {

	#----------------------------------------
	# restore system from uploaded backup file
	#----------------------------------------

	esmith::cgi::genHeaderNonCacheable ($q, \%conf, "Restore in progress");

	print $q->p ("The following files and directories have been restored:");

	print "<UL>";
	my $complete = 0;
	while (<RD>)
	{
	    print "<li>$_</li>\n";
	}

	print "</UL>";
	my $message;
	if (!close RD)
	{
	    $message = "Restore failed! " .
		"There was an error in reading the backup file";
	}
	else
	{
	    $message = "Restore complete";
	}

	#db_set_prop(\%restore, 'restore', 'state', 'complete');
	#db_set_prop(\%restore, 'restore', 'finish', time);

	&esmith::lockfile::UnlockFile($file_handle);

	print $q->p ($q->b ($message));

	esmith::cgi::genFooter ($q);
    }
    else
    {
	select(STDOUT);
	$| = 1;

	my $backupFile = $q->param ('backupFile');

	my $decodeCommand = ( $backupFile =~ /\.bak$/ ) ?
	    "|/usr/bin/uudecode -o /dev/stdout" :
	    "|/usr/bin/gzip -d";

	open(WR,
	         $decodeCommand
		. " | /bin/tar --directory / --extract --verbose --file=-"
	    ) || die ("Could not execute backup pipeline: $!\n");

	while (<$backupFile>)
	{
	    print WR;
	}

	close WR || die("Could not decode backup file: $!\n");
	exit(0);
    }
    return;
}

sub performDesktopVerify ($)
{
    my ($q) = @_;

    if (open(RD, "-|"))
    {
	esmith::cgi::genHeaderNonCacheable ($q,
	    \%conf, "Verify desktop backup file");

	print $q->p
	    ("The following files are contained in the backup file:");

	print "<UL>";

	my $complete = 0;
	while (<RD>)
	{
	    #$complete++ if /etc\/smbpasswd/;
	    print "<li>$_</li>\n";
	}

	print "</UL>";
	my $status = close RD ?
			($complete ?
			    "Verify complete" :
			    "End of list")
			: "There was an error in reading the backup file";
	print $q->p ($q->b ($status));

	esmith::cgi::genFooter ($q);

    }
    else
    {
	select(STDOUT);
	$| = 1;

	my $backupFile = $q->param ('backupFile');

	my $decodeCommand = ( $backupFile =~ /\.bak$/ ) ?
	    "|/usr/bin/uudecode -o /dev/stdout" :
	    "|/usr/bin/gzip -d";

	open(WR,
		$decodeCommand
		. " | /bin/tar --directory / --list --file=-"
	    ) || die ("Could not decode backup file: $!\n");

	while (<$backupFile>)
	{
	    print WR;
	}

	close WR;
	exit 0;
    }

    return;
}

sub performDiskUsage ($)
{
    my ($q) = @_;
    my $acctName = $ENV{'REMOTE_USER'};

    if ($acctName =~ /^([a-z][\-a-z0-9]*)$/)
    {
	   $acctName = $1;
    }

    system ("/sbin/e-smith/signal-event", "calc-du", "$acctName") == 0
        or die ("Error occurred while calculating disk usage.\n");

    showInitial ($q, "");
}
