#!/usr/local/bin/perl -w
use strict;

use XML::Parser;
use IPC::Open2;

my $tmpdir;
my %trustedkeys;
my $passphrase;
my $account;
my $idxfile;
my @patterns;

my $configfile = shift;
readconfigfile();

my($rdrfh, $wtrfh);
my $pid = open2($rdrfh, $wtrfh,
		 'ssh', $account, 'dsbsrv');

my $parser = new XML::Parser(
    Handlers => {
	Start => \&check_file
    });
$parser->parsefile($idxfile);

use Data::Dumper;

sub check_file {
    my ($expat, $element, %args) = @_;

    if (my $name = $args{name}) {
	my $restore_ok = 0;
	for my $pat (@patterns) {
	    if ($name =~ $pat->[0]) {
		$restore_ok = $pat->[1] eq '+';
		last;
	    }
	}
	if ($restore_ok) {
	    my $key = $args{key};
	    print "$name $key\n";

	    if ($element eq "file") {

		print $wtrfh "RETRIEVE $key [0]\n";
		my $result = <$rdrfh>;
		chomp($result);
		print "\t$result\n";
		my $tmpname = "$tmpdir/$$";
		if ($result =~ m/RETRIEVING $key \[(\d+)\]/) {
		    my $length = $1;
		    open(P, ">$tmpname.gpg");

		    my $nread = 0;
		    while ($nread < $length) {
			my $chunk = ($length - $nread > 0x1_0000) 
					? 0x1_0000
					: $length - $nread;
			my $buf;
			my $rc = read($rdrfh, $buf, $chunk);
			if ($rc <= 0) {
			    # something happened to the server - abort
			    last;
			}
			unless (print P $buf) {
			    # error writing to output file. Record the fact
			    # but keep going
			}
			$nread += $rc;
		    }
		    close(P);

		    system("gpg --passphrase-fd=0 $tmpname.gpg < $passphrase >$tmpname.out 2>$tmpname.err");
		    my ($signkey, $signgood);
		    open (F, "< $tmpname.err");
		    while (<F>) {
			$signkey = $1 if (/^gpg: Signature made .* using .* key ID (\w+)/);
			$signgood = 1 if (/^gpg: Good signature from /);
		    }
		    close (F);

		    unless ($trustedkeys{$signkey} && $signgood) {
			print "$0: bad signature ($signkey/$signgood) for $key, skipping\n";
			unlink("$tmpname", "$tmpname.gpg", "$tmpname.out", "$tmpname.err");
			return;
		    }
		    open(T, "<$tmpname");
		    my $desc = <T>; # this should be the same as the
				    # info we already have.
				    # Should we check?
		    open(F, ">$name");
		    while(<T>) {
			print F;
		    }
		    close(T);
		    close(F);
		    setperm(\%args);

		    unlink("$tmpname", "$tmpname.gpg", "$tmpname.out", "$tmpname.err");
		}
	    } elsif ($element eq "directory") {
		mkdir($name, 0777);
		setperm(\%args);
	    } elsif ($element eq "symlink") {
		symlink($args{target}, $name);
	    } else {
		print STDERR "$0: ignoring unkown file type $element\n";
	    }

	}
    }
}

sub setperm {
    # set permissions.
    my ($param) = @_;

    my $uid;
    if ($param->{owner}) {
	$uid = getpwnam($param->{owner});
    }
    $uid = -1 unless defined($uid);

    my $gid;
    if ($param->{group}) {
	$gid = getgrnam($param->{group});
    }
    $gid = -1 unless defined($gid);
    chown($uid, $gid, $param->{name});
    my $mode = 0;
    for (split(/ /, $param->{acl})) {
	if (/^user::(.*)/) {
	    my $p = $1;
	    my $pp = 0;
	    $pp |= 4 if $p =~ /r/;
	    $pp |= 2 if $p =~ /w/;
	    $pp |= 1 if $p =~ /x/;
	    $mode |= ($pp << 6);
	}
	if (/^group::(.*)/) {
	    my $p = $1;
	    my $pp = 0;
	    $pp |= 4 if $p =~ /r/;
	    $pp |= 2 if $p =~ /w/;
	    $pp |= 1 if $p =~ /x/;
	    $mode |= ($pp << 3);
	}
	if (/^other:(.*)/) {
	    my $p = $1;
	    my $pp = 0;
	    $pp |= 4 if $p =~ /r/;
	    $pp |= 2 if $p =~ /w/;
	    $pp |= 1 if $p =~ /x/;
	    $mode |= ($pp << 0);
	}
    }
    $mode |= 04000 if ($param->{setuid});
    $mode |= 02000 if ($param->{setgid});
    $mode |= 01000 if ($param->{sticky});
    chmod($mode, $param->{name});
    if (defined($param->{mtime})) {
	utime($param->{mtime}, $param->{mtime}, $param->{name})
	    or print STDERR "$0: utime ($param->{mtime}, $param->{mtime}, $param->{name}) failed: $!\n";
    }
}

sub readconfigfile {
    open(F, "<$configfile") or die "cannot open $configfile: $!";
    while (<F>) {
	chomp;
	my ($key, $val) = split(/\s*=\s*/);
	if ($key eq 'tmpdir') {$tmpdir=$val;}
	elsif ($key eq 'logdir') {}
	elsif ($key eq 'passphrase') {$passphrase = $val;}
	elsif ($key eq 'encrkey') {}
	elsif ($key eq 'signkey') {}
	elsif ($key eq 'account') {$account = $val;}
	elsif ($key eq 'startdir') {}
	elsif ($key eq 'indexfile') {$idxfile = $val}
	elsif ($key eq 'trustedkey') {$trustedkeys{$val} = 1;}
	elsif ($key eq 'include') {push (@patterns, [$val, '+']);}
	elsif ($key eq 'exclude') {push (@patterns, [$val, '-']);}
	else {
	    die "unknown keyword $key in $configfile:$.\n";
	}
    }
    close(F);
}
