Blob Blame History Raw
#!@PERL@ 
# Copyright (c) 2008-2012 Zmanda, Inc.  All Rights Reserved.
# Copyright (c) 2013-2016 Carbonite, Inc.  All Rights Reserved.
#
# 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
#
# Contact information: Carbonite Inc., 756 N Pastoria Ave
# Sunnyvale, CA 94086, USA, or: http://www.zmanda.com

use lib '@amperldir@';
use strict;
use warnings;
use Getopt::Long;

package Amanda::Application::Amsamba;
use base qw(Amanda::Application);
use File::Copy;
use File::Path;
use IPC::Open2;
use IPC::Open3;
use Sys::Hostname;
use Symbol;
use IO::Handle;
use MIME::Base64 ();
use Amanda::Constants;
use Amanda::Config qw( :init :getconf  config_dir_relative );
use Amanda::Debug qw( :logging );
use Amanda::Paths;
use Amanda::Util qw( :constants :quoting);
use Amanda::MainLoop qw( :GIOCondition );

sub new {
    my $class = shift;
    my ($config, $host, $disk, $device, $level, $index, $message, $collection, $record, $calcsize, $gnutar_path, $smbclient_path, $amandapass, $exclude_file, $exclude_list, $exclude_optional, $include_file, $include_list, $include_optional, $recover_mode, $allow_anonymous, $target, $regex_match) = @_;
    my $self = $class->SUPER::new($config);

    if (defined $gnutar_path) {
	$self->{gnutar}     = $gnutar_path;
    } else {
	$self->{gnutar}     = $Amanda::Constants::GNUTAR;
    }
    if (defined $smbclient_path) {
	$self->{smbclient}  = $smbclient_path;
    } else {
	$self->{smbclient}  = $Amanda::Constants::SAMBA_CLIENT;
    }
    if (defined $amandapass) {
	$self->{amandapass}  = config_dir_relative($amandapass);
    } else {
	$self->{amandapass}  = "$Amanda::Paths::CONFIG_DIR/amandapass";
    }

    $self->{config}           = $config;
    $self->{host}             = $host;
    if (defined $disk) {
	$self->{disk}         = $disk;
    } else {
	$self->{disk}         = $device;
    }
    if (defined $device) {
	$self->{device}       = $device;
    } else {
	$self->{device}       = $disk;
    }
    $self->{level}            = [ @{$level} ];
    $self->{index}            = $index;
    $self->{message}          = $message;
    $self->{collection}       = $collection;
    $self->{record}           = $record;
    $self->{calcsize}         = $calcsize;
    $self->{exclude_file}     = [ @{$exclude_file} ];
    $self->{exclude_list}     = [ @{$exclude_list} ];
    $self->{exclude_optional} = $exclude_optional;
    $self->{include_file}     = [ @{$include_file} ];
    $self->{include_list}     = [ @{$include_list} ];
    $self->{include_optional} = $include_optional;
    $self->{recover_mode}     = $recover_mode;
    $self->{allow_anonymous}  = $allow_anonymous;
    $self->{target}           = $target;
    if ($regex_match =~ /^yes$/i) {
	$self->{'regex_match'} = 1;
    } else {
	$self->{'regex_match'} = 0;
    }

    return $self;
}

# on entry:
#   $self->{exclude_file}
#   $self->{exclude_list}
#   $self->{include_file}
#   $self->{include_list}
#on exit:
#  $self->{exclude}
#  $self->{include}
#  $self->{include_filename}
sub validate_inexclude {
    my $self = shift;

    if ($#{$self->{exclude_file}} + $#{$self->{exclude_list}} >= -1 &&
	$#{$self->{include_file}} + $#{$self->{include_list}} >= -1) {
	$self->print_to_server_and_die("Can't have both include and exclude",
				       $Amanda::Script_App::ERROR);
    }

    if ($#{$self->{exclude_file}} >= 0) {
	$self->{exclude} = [ @{$self->{exclude_file}} ];
    }
    foreach my $file (@{$self->{exclude_list}}) {
	if (!open(FF, $file)) {
	    if ($self->{action} eq 'check' && !$self->{exclude_optional}) {
		$self->print_to_server("Open of '$file' failed: $!",
				       $Amanda::Script_App::ERROR);
	    }
	    next;
	}
	while (<FF>) {
	    chomp;
	    push @{$self->{exclude}}, $_;
	}
	close(FF);
    }

    if ($self->{action} eq "restore" and defined $self->{'include_list'}) {
	# put all include in a single file $self->{'include_filename'}
	$self->{'include_filename'} = "$AMANDA_TMPDIR/amsamba.$$.include";
	open INC_FILE, ">$self->{'include_filename'}";
	if ($#{$self->{include_file}} >= 0) {
	    print INC_FILE "$self->{include_file}\n";
	}
	foreach my $file (@{$self->{include_list}}) {
	    if (!open(FF, $file)) {
		if ($self->{action} eq 'check' && !$self->{include_optional}) {
		    $self->print_to_server("Open of '$file' failed: $!",
					   $Amanda::Script_App::ERROR);
		}
		next;
	    }
	    while (<FF>) {
		if (defined $self->{'subdir'}) {
		    $_ =~ s/^\./$self->{'subdir'}/;
		}
		print INC_FILE;
	    }
	    close(FF);
	}

	# add command line include for amrestore
	for(my $i=1;defined $ARGV[$i]; $i++) {
	    my $param = $ARGV[$i];
	    $param =~ /^(.*)$/;
	    $_ = $1;
	    if (defined $self->{'subdir'}) {
		$_ =~ s/^\./$self->{'subdir'}/;
	    }
	    $_ =~ s/\\([0-7]{3})/chr oct $1/eg;
	    print INC_FILE "$_\n";
	}

	close INC_FILE;
    } else {
	# put all include in $self->{'include'} they will be added on
	# command line.
	if ($#{$self->{include_file}} >= 0) {
	    $self->{include} = [ @{$self->{include_file}} ];
	}

	foreach my $file (@{$self->{include_list}}) {
	    if (!open(FF, $file)) {
		if ($self->{action} eq 'check' && !$self->{include_optional}) {
		    $self->print_to_server("Open of '$file' failed: $!",
					   $Amanda::Script_App::ERROR);
		}
		next;
	    }
	    while (<FF>) {
		chomp;
		if ($self->{action} eq "restore" and
		    defined $self->{'subdir'}) {
		    $_ =~ s/^\./$self->{'subdir'}/;
		}
		push @{$self->{include}}, $_;
	    }
	    close(FF);
	}

	# add command line include for amrestore
	if ($self->{action} eq "restore") {
		for(my $i=1;defined $ARGV[$i]; $i++) {
		my $param = $ARGV[$i];
		$param =~ /^(.*)$/;
		$_ = $1;
		if (defined $self->{'subdir'}) {
		    $_ =~ s/^\./$self->{'subdir'}/;
		}
		push @{$self->{include}}, $1;
	    }
	}
    }
}

# on entry:
#   $self->{target} == //host/share/subdir           \\host\share\subdir
#   or
#   $self->{device}    == //host/share/subdir		\\host\share\subdir
# on exit:
#   $self->{cifshost}   = //host			\\host
#   $self->{share}      = //host/share			\\host\share
#   $self->{sambashare} = \\host\share			\\host\share
#   $self->{subdir}     = subdir			subdir
sub parsesharename {
    my $self = shift;
    my $to_parse = $self->{target};
    $to_parse = $self->{device} if !defined $to_parse;;

    return if !defined $to_parse;
    if ($to_parse =~ /^\\\\/) {
	$self->{unc}          = 1;
    } else {
	$self->{unc}          = 0;
    }

    if ($self->{unc}) {
	if ($to_parse =~ m,^(\\\\[^\\]+\\[^\\]+)\\(.*)$,) {
	    $self->{share} = $1;
	    $self->{subdir} = $2
	} else {
	    $self->{share} = $to_parse
	}
	$self->{sambashare} = $self->{share};
	$to_parse =~ m,^(\\\\[^\\]+)\\[^\\]+,;
	$self->{cifshost} = $1;
    } else {
	if ($to_parse =~ m,^(//[^/]+/[^/]+)/(.*)$,) {
	    $self->{share} = $1;
	    $self->{subdir} = $2
	} else {
	    $self->{share} = $to_parse
	}
	$self->{sambashare} = $self->{share};
	$self->{sambashare} =~ s,/,\\,g;
	$to_parse =~ m,^(//[^/]+)/[^/]+,;
	$self->{cifshost} = $1;
    }
}


# Read $self->{amandapass} file.
# on entry:
#   $self->{cifshost} == //host/share
#   $self->{share} == //host/share
# on exit:
#   $self->{domain}   = domain to connect to.
#   $self->{username} = username (-U)
#   $self->{password} = password
sub findpass {
    my $self = shift;

    my $amandapass;
    my $line;

    $self->{domain} = undef;
    $self->{username} = undef;
    $self->{password} = undef;

    debug("amandapass: $self->{amandapass}");
    if (!open($amandapass, $self->{amandapass})) {
	if ($self->{allow_anonymous}) {
	    $self->{username} = $self->{allow_anonymous};
	    debug("cannot open password file '$self->{amandapass}': $!\n");
	    debug("Using anonymous user: $self->{username}");
	    return;
	} else {
	    $self->print_to_server_and_die(
			"cannot open password file '$self->{amandapass}': $!",
			$Amanda::Script_App::ERROR);
	}
    }

    while ($line = <$amandapass>) {
	chomp $line;
	next if $line =~ /^#/;
	my ($diskname, $userpasswd, $domain, $extra) = Amanda::Util::split_quoted_string_friendly($line);
	if ($extra) {
	    debug("Trailling characters ignored in amandapass line");
	}
	if (defined $diskname &&
	    ($diskname eq '*' ||
	     ($self->{unc}==0 && $diskname =~ m,^(//[^/]+)/\*$, && $1 eq $self->{cifshost}) ||
	     ($self->{unc}==1 && $diskname =~ m,^(\\\\[^\\]+)\\\*$, && $1 eq $self->{cifshost}) ||
	     $diskname eq $self->{share} ||
	     $diskname eq $self->{sambashare})) {
	    if (defined $userpasswd && $userpasswd ne "") {
	        $self->{domain} = $domain if defined $domain && $domain ne "";
	        my ($username, $password) = split('%', $userpasswd, 2);
	        $self->{username} = $username;
		if ($password =~ /^6G\!dr(.*)/) {
		    my $base64 = $1;
		    $password = MIME::Base64::decode($base64);
		    chomp($password);
		}
	        $self->{password} = $password;
		$self->{password} = undef if (defined $password && $password eq "");
            } else {
	        $self->{username} = "guest";
            }
	    close($amandapass);
	    return;
	}
    }
    close($amandapass);
    if ($self->{allow_anonymous}) {
	$self->{username} = $self->{allow_anonymous};
	debug("Cannot find password for share $self->{share} in $self->{amandapass}");
	debug("Using anonymous user: $self->{username}");
	return;
    }
    $self->print_to_server_and_die(
	"Cannot find password for share $self->{share} in $self->{amandapass}",
	$Amanda::Script_App::ERROR);
}

sub command_support {
    my $self = shift;

    print "CONFIG YES\n";
    print "HOST YES\n";
    print "DISK YES\n";
    print "MAX-LEVEL 1\n";
    print "INDEX-LINE YES\n";
    print "INDEX-XML NO\n";
    print "MESSAGE-LINE YES\n";
    print "MESSAGE-XML NO\n";
    print "RECORD YES\n";
    print "COLLECTION NO\n";
    print "MULTI-ESTIMATE NO\n";
    print "CALCSIZE NO\n";
    print "CLIENT-ESTIMATE YES\n";
    print "EXCLUDE-FILE YES\n";
    print "EXCLUDE-LIST YES\n";
    print "EXCLUDE-OPTIONAL YES\n";
    print "INCLUDE-FILE YES\n";
    print "INCLUDE-LIST YES\n";
    print "INCLUDE-OPTIONAL YES\n";
    print "RECOVER-MODE SMB\n";
}

sub command_selfcheck {
    my $self = shift;

    $self->print_to_server("disk " . quote_string($self->{disk}),
			   $Amanda::Script_App::GOOD);

    $self->print_to_server("amsamba version " . $Amanda::Constants::VERSION,
			   $Amanda::Script_App::GOOD);
    #check binary
    if (!defined($self->{smbclient}) || $self->{smbclient} eq "") {
	$self->print_to_server(
	    "smbclient not set; you must define the SMBCLIENT-PATH property",
	    $Amanda::Script_App::ERROR);
    }
    elsif (! -e $self->{smbclient}) {
	$self->print_to_server("$self->{smbclient} doesn't exist",
			       $Amanda::Script_App::ERROR);
    }
    elsif (! -x $self->{smbclient}) {
	$self->print_to_server("$self->{smbclient} is not executable",
			       $Amanda::Script_App::ERROR);
    } else {
	my @sv = `$self->{smbclient} --version`;
	if ($? >> 8 == 0) {
	    $sv[0] =~ /^[^0-9]*(.*)$/;
	    my $sv = $1;
	    $self->print_to_server("amsamba smbclient-version $sv",
				   $Amanda::Script_App::GOOD);
	} else {
	    $self->print_to_server(
		"[Can't get " . $self->{smbclient} . " version]\n",
		$Amanda::Script_App::ERROR);
	}
    }

    $self->print_to_server("$self->{smbclient}",
			   $Amanda::Script_App::GOOD);
    if (!defined $self->{disk} || !defined $self->{device}) {
	return;
    }
    $self->parsesharename();
    $self->findpass();
    $self->validate_inexclude();

    print "OK " . $self->{share} . "\n";
    print "OK " . $self->{device} . "\n";
    print "OK " . $self->{target} . "\n" if defined $self->{target};

    my ($password_rdr, $password_wtr);
    if (defined $self->{password}) {
	# Don't set close-on-exec
        $^F=10;
        pipe($password_rdr, $password_wtr);
        $^F=2;
        $password_wtr->autoflush(1);
    }
    my($wtr, $rdr, $err);
    $err = Symbol::gensym;
    my $pid = open3($wtr, $rdr, $err, "-");
    if ($pid == 0) {
	#child
        if (defined $self->{password}) {
	    my $ff = $password_rdr->fileno;
	    debug("password_rdr $ff");
	    $password_wtr->close();
	    $ENV{PASSWD_FD} = $password_rdr->fileno;
	}
	close(1);
	close(2);
	my @ARGV = ();
	push @ARGV, $self->{smbclient}, $self->{share};
	push @ARGV, "" if (!defined $self->{password});
	push @ARGV, "-U", $self->{username},
		    "-E";
	if (defined $self->{domain}) {
	    push @ARGV, "-W", $self->{domain},
	}
	if (defined $self->{subdir}) {
	    push @ARGV, "-D", $self->{subdir},
	}
	push @ARGV, "-c", "quit";
	debug("execute: " . $self->{smbclient} . " " .
	      join(" ", @ARGV));
	$ENV{'LC_CTYPE'} = 'en_US.UTF-8';
	exec {$self->{smbclient}} @ARGV;
    }
    #parent
    if (defined $self->{password}) {
        my $ff = $password_wtr->fileno;
        debug("password_wtr $ff");
        $password_wtr->print("$self->{password}\n");
        $password_wtr->print("$self->{password}\n");
        $password_wtr->close();
        $password_rdr->close();
    } else {
	debug("No password");
    }
    close($wtr);
    close($rdr);
    while (<$err>) {
	chomp;
	debug("stderr: " . $_);
	next if /^Domain=/;
	next if /^WARNING/g;
	# message if samba server is configured with 'security = share'
	next if /Server not using user level security and no password supplied./;
	$self->print_to_server("smbclient: $_",
			       $Amanda::Script_App::ERROR);
    }
    close($err);
    waitpid($pid, 0);
    #check statefile
    #check amdevice
}

sub command_estimate {
    my $self = shift;

    $self->parsesharename();
    $self->findpass();
    $self->validate_inexclude();

    my $level = $self->{level}[0];
    my ($password_rdr, $password_wtr);
    if (defined $self->{password}) {
	# Don't set close-on-exec
        $^F=10;
        pipe($password_rdr,  $password_wtr);
        $^F=2;
        $password_wtr->autoflush(1);
    }
    my($wtr, $rdr, $err);
    $err = Symbol::gensym;
    my $pid = open3($wtr, $rdr, $err, "-");
    if ($pid == 0) {
	#child
        if (defined $self->{password}) {
	    my $ff = $password_rdr->fileno;
	    debug("password_rdr $ff");
	    $password_wtr->close();
	    $ENV{PASSWD_FD} = $password_rdr->fileno;
	}
	close(0);
	close(1);
	my @ARGV = ();
	push @ARGV, $self->{smbclient}, $self->{share};
	push @ARGV, "" if (!defined($self->{password}));
	push @ARGV, "-d", "0",
		    "-U", $self->{username},
		    "-E";
	if (defined $self->{domain}) {
	    push @ARGV, "-W", $self->{domain},
	}
	if (defined $self->{subdir}) {
	    push @ARGV, "-D", $self->{subdir},
	}
	if ($level == 0) {
	    push @ARGV, "-c", "archive 0;recurse;du";
	} else {
	    push @ARGV, "-c", "archive 1;recurse;du";
	}
	debug("execute: " . $self->{smbclient} . " " .
	      join(" ", @ARGV));
	$ENV{'LC_CTYPE'} = 'en_US.UTF-8';
	exec {$self->{smbclient}} @ARGV;
    }
    #parent
    if (defined $self->{password}) {
        my $ff = $password_wtr->fileno;
        debug("password_wtr $ff");
        $password_wtr->print("$self->{password}\n");
        $password_wtr->print("$self->{password}\n");
        $password_wtr->close();
        $password_rdr->close();
    }
    close($wtr);
    close($rdr);
    my $size = $self->parse_estimate($err);
    close($err);
    output_size($level, $size);
    waitpid($pid, 0);
}

sub parse_estimate {
    my $self = shift;
    my($fh)  = shift;
    my($size) = -1;
    while(<$fh>) {
	chomp;
	next if /^\s*$/;
	next if /blocks of size/;
	next if /blocks available/;
	next if /^\s*$/;
	next if /^Domain=/;
	next if /dumped \d+ files and directories/;
	next if /^WARNING/g;
	# message if samba server is configured with 'security = share'
	next if /Server not using user level security and no password supplied./;
	debug("stderr: $_");
	if ($_ =~ /^Total number of bytes: (\d*)/) {
	    $size = $1;
	    last;
	} else {
	    $self->print_to_server("smbclient: $_",
				   $Amanda::Script_App::ERROR);
	}
    }
    return $size;
}

sub output_size {
   my($level) = shift;
   my($size) = shift;
   if($size == -1) {
      print "$level -1 -1\n";
      #exit 2;
   }
   else {
      my($ksize) = int $size / (1024);
      $ksize=32 if ($ksize<32);
      print "$level $ksize 1\n";
   }
}

sub send_empty_tar_file {
    my $self = shift;
    my ($out1, $out2) = @_;
    my $out;
    my $buf;
    my $size;

    Amanda::Debug::debug("Create empty archive with: tar --create --file=- --files-from=/dev/null");
    open2($out, undef, "tar", "--create", "--file=-", "--files-from=/dev/null");

    while(($size = sysread($out, $buf, 32768))) {
	syswrite($out1, $buf, $size);
	syswrite($out2, $buf, $size);
    }
}

sub command_backup {
    my $self = shift;

    my $level = $self->{level}[0];

    $self->parsesharename();
    $self->findpass();
    $self->validate_inexclude();

    my ($password_rdr, $password_wtr);
    if (defined $self->{password}) {
	# Don't set close-on-exec
        $^F=10;
        pipe($password_rdr,  $password_wtr);
        $^F=2;
        $password_wtr->autoflush(1);
    }
    my($smbclient_wtr, $smbclient_rdr, $smbclient_err);
    $smbclient_err = Symbol::gensym;
    my $pid = open3($smbclient_wtr, $smbclient_rdr, $smbclient_err, "-");
    if ($pid == 0) {
	#child
	if (defined $self->{password}) {
	    my $ff = $password_rdr->fileno;
	    debug("password_rdr $ff");
	    $password_wtr->close();
	    $ENV{PASSWD_FD} = $password_rdr->fileno;
	}
	my @ARGV = ();
	push @ARGV, $self->{smbclient}, $self->{share};
	push @ARGV, "" if (!defined($self->{password}));
	push @ARGV, "-d", "0",
		    "-U", $self->{username},
		    "-E";
	if (defined $self->{domain}) {
	    push @ARGV, "-W", $self->{domain},
	}
	if (defined $self->{subdir}) {
	    push @ARGV, "-D", $self->{subdir},
	}

	my $comm ;
	if ($level == 0) {
	    $comm = "tarmode full reset hidden system quiet;";
	} else {
	    $comm = "tarmode inc noreset hidden system quiet;";
	}
	$comm .= " tar c";
	if ($self->{'regex_match'}) {
	    $comm .= "r";
	}
	if ($#{$self->{exclude}} >= 0) {
	    $comm .= "X";
	}
	if ($#{$self->{include}} >= 0) {
	    $comm .= "I";
	}
	$comm .= " -";
	if ($#{$self->{exclude}} >= 0) {
	    $comm .= " " . join(" ", @{$self->{exclude}});
	}
	if ($#{$self->{include}} >= 0) {
	    $comm .= " " . join(" ", @{$self->{include}});
	}
	push @ARGV, "-c", $comm;
	debug("execute: " . $self->{smbclient} . " " .
	      join(" ", @ARGV));
	$ENV{'LC_CTYPE'} = 'en_US.UTF-8';
	exec {$self->{smbclient}} @ARGV;
    }

    if (defined $self->{password}) {
        my $ff = $password_wtr->fileno;
        debug("password_wtr $ff");
        $password_wtr->print("$self->{password}\n");
        $password_wtr->print("$self->{password}\n");
        $password_wtr->close();
        $password_rdr->close();
    } else {
	debug("No password");
    }
    close($smbclient_wtr);

    #index process
    my $index_wtr;
    my $index_rdr;
    my $index_err = Symbol::gensym;;
    debug("$self->{gnutar} -tf -");
    my $pid_index1 = open3($index_wtr, $index_rdr, $index_err, $self->{gnutar}, "-tf", "-");
    my $size = -1;
    my $index_fd = $index_rdr->fileno;
    debug("index $index_fd");
    my $indexout_fd;
    if (defined($self->{index})) {
	open($indexout_fd, '>&=4') ||
	    $self->print_to_server_and_die("Can't open indexout_fd: $!",
					   $Amanda::Script_App::ERROR);
    }

    my $file_to_close = 3;
    my $smbclient_stdout_src = Amanda::MainLoop::fd_source($smbclient_rdr,
				$G_IO_IN|$G_IO_HUP|$G_IO_ERR);
    my $smbclient_stderr_src = Amanda::MainLoop::fd_source($smbclient_err,
				$G_IO_IN|$G_IO_HUP|$G_IO_ERR);
    my $index_tar_stdout_src = Amanda::MainLoop::fd_source($index_rdr,
				$G_IO_IN|$G_IO_HUP|$G_IO_ERR);
    my $index_tar_stderr_src = Amanda::MainLoop::fd_source($index_err,
				$G_IO_IN|$G_IO_HUP|$G_IO_ERR);

    my $smbclient_stdout_done = 0;
    my $smbclient_stderr_done = 0;
    my $data_size = 0;
    my $nb_files = 0;
    $smbclient_stdout_src->set_callback(sub {
	my $buf;
	my $blocksize = -1;
	$blocksize = sysread($smbclient_rdr, $buf, 32768);
	if (!$blocksize) {
	    $file_to_close--;
	    $smbclient_stdout_src->remove();
	    $smbclient_stdout_done = 1;
	    if ($smbclient_stderr_done) {
		if ($data_size == 0 and $nb_files == 0 and $size == 0) {
		    $self->send_empty_tar_file(*STDOUT, $index_wtr);
		}
		close($index_wtr);
		close(STDOUT);
	    }
	    close($smbclient_rdr);
	    Amanda::MainLoop::quit() if $file_to_close == 0;
	    return;
	}
	$data_size += $blocksize;
	syswrite(STDOUT, $buf, $blocksize);
	syswrite($index_wtr, $buf, $blocksize);
    });

    $smbclient_stderr_src->set_callback(sub {
	my $line = <$smbclient_err>;
	if (!defined $line) {
	    $file_to_close--;
	    $smbclient_stderr_src->remove();
	    $smbclient_stderr_done = 1;
	    if ($smbclient_stdout_done) {
		if ($data_size == 0 and $nb_files == 0 and $size == 0) {
		    $self->send_empty_tar_file(*STDOUT, $index_wtr);
		}
		close($index_wtr);
		close(STDOUT);
	    }
	    close ($smbclient_err);
	    Amanda::MainLoop::quit() if $file_to_close == 0;
	    return;
	}
	chomp $line;
	debug("stderr: " . $line);
	return if $line =~ /Domain=/;
	return if $line =~ /tarmode is now /;
	return if $line =~ /tar_re_search set/;
	return if $line =~ /WARNING/g;
	if ($line =~ /dumped (\d+) files and directories/) {
	    $nb_files = $1;
	    return;
	}
	# message if samba server is configured with 'security = share'
	return if $line =~  /Server not using user level security and no password supplied./;
	if ($line =~ /Total bytes written: (\d*)/) {
	    $size = $1;
	    return;
	} elsif ($line =~ /Total bytes received: (\d*)/) {
	    $size = $1;
	    return;
	}
	$self->print_to_server("smbclient: $line", $Amanda::Script_App::ERROR);
    });

    $index_tar_stdout_src->set_callback(sub {
	my $line = <$index_rdr>;
	if (!defined $line) {
	    $file_to_close--;
	    $index_tar_stdout_src->remove();
	    close($index_rdr);
	    close($indexout_fd);
	    Amanda::MainLoop::quit() if $file_to_close == 0;
	    return;
	}
	if ($line =~ /^\.\//) {
	    if(defined($indexout_fd)) {
		if(defined($self->{index})) {
		    $line =~ s/^\.//;
		    print $indexout_fd $line;
		}
	    }
	} else {
	    chomp $line;
	    if ($line =~ /Ignoring unknown extended header keyword/) {
		debug("tar stderr: $line");
	    } else {
		$self->print_to_server($line, $Amanda::Script_App::ERROR);
	    }
	}
    });

    $index_tar_stderr_src->set_callback(sub {
	my $line = <$index_err>;
	if (!defined $line) {
	    $file_to_close--;
	    $index_tar_stderr_src->remove();
	    close($index_err);
	    Amanda::MainLoop::quit() if $file_to_close == 0;
	    return;
	}
	chomp $line;
	if ($line =~ /Ignoring unknown extended header keyword/) {
	    debug("tar stderr: $line");
	} else {
	    $self->print_to_server($line, $Amanda::Script_App::ERROR);
	}
    });

    Amanda::MainLoop::run();

    if ($size >= 0) {
	my $ksize = $size / 1024;
	if ($ksize < 32) {
	    $ksize = 32;
	}
	print {$self->{mesgout}} "sendbackup: size $ksize\n";
    }

    waitpid $pid, 0;
    if ($? != 0) {
	$self->print_to_server_and_die("smbclient returned error",
				       $Amanda::Script_App::ERROR);
    }
}

sub parse_backup {
    my $self = shift;
    my($fhin, $fhout, $indexout) = @_;
    my $size  = -1;
    while(<$fhin>) {
	if ( /^\.\//) {
	    if(defined($indexout)) {
		if(defined($self->{index})) {
		    s/^\.//;
		    print $indexout $_;
		}
	    }
	}
	else {
	    print $fhout "? $_";
	}
    }
}

sub command_index_from_output {
   index_from_output(0, 1);
}

sub index_from_output {
   my($fhin, $fhout) = @_;
   my($size) = -1;
   while(<$fhin>) {
      next if /^Total bytes written:/;
      next if !/^\.\//;
      s/^\.//;
      print $fhout $_;
   }
}

sub command_index {
    my $self = shift;
    my $index_fd;
    open2($index_fd, ">&0", $self->{gnutar}, "--list", "--file", "-") ||
	$self->print_to_server_and_die("Can't run $self->{gnutar}: $!",
				       $Amanda::Script_App::ERROR);
    index_from_output($index_fd, \*STDOUT);
}

sub command_restore {
    my $self = shift;
    my @cmd = ();

    $self->parsesharename();
    chdir(Amanda::Util::get_original_cwd());

    if ($self->{recover_mode} eq "smb") {
	$self->validate_inexclude();
	$self->findpass();
	push @cmd, $self->{smbclient}, $self->{share};
	push @cmd, "-D", $self->{'subdir'} if defined $self->{'subdir'};
	push @cmd, "" if (!defined $self->{password});
	push @cmd, "-d", "0",
		   "-U", $self->{username};
	
	if (defined $self->{domain}) {
	    push @cmd, "-W", $self->{domain};
	}
	if (defined $self->{'include_filename'}) {
	    push @cmd, "-TFx", "-", "$self->{'include_filename'}";
	} else {
	    push @cmd, "-Tx", "-";
	    if ($#{$self->{include}} >= 0) {
		push @cmd, @{$self->{include}};
            }
	    for(my $i=1;defined $ARGV[$i]; $i++) {
		my $param = $ARGV[$i];
		$param =~ /^(.*)$/;
		push @cmd, $1;
	    }
	}
	my ($parent_rdr, $child_wtr);
	if (defined $self->{password}) {
	    # Don't set close-on-exec
	    $^F=10;
	    pipe($parent_rdr,  $child_wtr);
            $^F=2;
	    $child_wtr->autoflush(1);
	}
	my($wtr, $rdr, $err);
	$err = Symbol::gensym;
	my $pid = open3($wtr, $rdr, $err, "-");
	if ($pid == 0) {
	    $child_wtr->print($self->{password});
	    $child_wtr->close();
	    exit 0;
	}
	if (defined $self->{password}) {
	    $child_wtr->close();
	    $ENV{PASSWD_FD} = $parent_rdr->fileno;
	}
	debug("cmd:" . join(" ", @cmd));
	exec { $cmd[0] } @cmd;
	die("Can't exec '", $cmd[0], "'");
    } else {
	push @cmd, $self->{gnutar}, "-xpvf", "-";
	if (defined $self->{target}) {
	    if (!-d $self->{target}) {
		$self->print_to_server_and_die(
				       "Directory $self->{target}: $!",
				       $Amanda::Script_App::ERROR);
	    }
	    if (!-w $self->{target}) {
		$self->print_to_server_and_die(
				       "Directory $self->{target}: $!",
				       $Amanda::Script_App::ERROR);
	    }
	    push @cmd, "--directory", $self->{directory};
	}
	if ($#{$self->{include_list}} == 0) {
	    push @cmd, "--files-from", $self->{include_list}[0];
	}
	if ($#{$self->{exclude_list}} == 0) {
	    push @cmd, "--exclude-from", $self->{exclude_list}[0];
	}
	for(my $i=1;defined $ARGV[$i]; $i++) {
	    my $param = $ARGV[$i];
	    $param =~ /^(.*)$/;
	    push @cmd, $1;
	}
	debug("cmd:" . join(" ", @cmd));
	exec { $cmd[0] } @cmd;
	die("Can't exec '", $cmd[0], "'");
    }
}

sub command_validate {
   my $self = shift;

   if (!defined($self->{gnutar}) || !-x $self->{gnutar}) {
      return $self->default_validate();
   }

   my(@cmd) = ($self->{gnutar}, "-tf", "-");
   debug("cmd:" . join(" ", @cmd));
   my $pid = open3('>&STDIN', '>&STDOUT', '>&STDERR', @cmd) ||
	$self->print_to_server_and_die("Unable to run @cmd: $!",
				       $Amanda::Script_App::ERROR);
   waitpid $pid, 0;
   if( $? != 0 ){
	$self->print_to_server_and_die("$self->{gnutar} returned error",
				       $Amanda::Script_App::ERROR);
   }
}

sub command_print_command {
}

package main;

sub usage {
    print <<EOF;
Usage: amsamba <command> --config=<config> --host=<host> --disk=<disk> --device=<device> --level=<level> --index=<yes|no> --message=<text> --collection=<no> --record=<yes|no> --calcsize.
EOF
    exit(1);
}

my $opt_version;
my $opt_config;
my $opt_host;
my $opt_disk;
my $opt_device;
my @opt_level;
my $opt_index;
my $opt_message;
my $opt_collection;
my $opt_record;
my $opt_calcsize;
my $opt_gnutar_path;
my $opt_smbclient_path;
my $opt_amandapass;
my @opt_exclude_file;
my @opt_exclude_list;
my $opt_exclude_optional;
my @opt_include_file;
my @opt_include_list;
my $opt_include_optional;
my $opt_recover_mode;
my $opt_allow_anonymous;
my $opt_target;
my $opt_regex_match;

my @orig_argv = @ARGV;

Getopt::Long::Configure(qw{bundling});
GetOptions(
    'version'            => \$opt_version,
    'config=s'           => \$opt_config,
    'host=s'             => \$opt_host,
    'disk=s'             => \$opt_disk,
    'device=s'           => \$opt_device,
    'level=s'            => \@opt_level,
    'index=s'            => \$opt_index,
    'message=s'          => \$opt_message,
    'collection=s'       => \$opt_collection,
    'record'             => \$opt_record,
    'calcsize'           => \$opt_calcsize,
    'gnutar-path=s'      => \$opt_gnutar_path,
    'smbclient-path=s'   => \$opt_smbclient_path,
    'amandapass=s'       => \$opt_amandapass,
    'exclude-file=s'     => \@opt_exclude_file,
    'exclude-list=s'     => \@opt_exclude_list,
    'exclude-optional=s' => \$opt_exclude_optional,
    'include-file=s'     => \@opt_include_file,
    'include-list=s'     => \@opt_include_list,
    'include-optional=s' => \$opt_include_optional,
    'recover-mode=s'     => \$opt_recover_mode,
    'allow-anonymous=s'  => \$opt_allow_anonymous,
    'target|directory=s' => \$opt_target,
    'regex-match=s'      => \$opt_regex_match,
) or usage();

if (defined $opt_version) {
    print "amsamba-" . $Amanda::Constants::VERSION , "\n";
    exit(0);
}

my $application = Amanda::Application::Amsamba->new($opt_config, $opt_host, $opt_disk, $opt_device, \@opt_level, $opt_index, $opt_message, $opt_collection, $opt_record, $opt_calcsize, $opt_gnutar_path, $opt_smbclient_path, $opt_amandapass, \@opt_exclude_file, \@opt_exclude_list, $opt_exclude_optional, \@opt_include_file, \@opt_include_list, $opt_include_optional, $opt_recover_mode, $opt_allow_anonymous, $opt_target, $opt_regex_match);

Amanda::Debug::debug("Arguments: " . join(' ', @orig_argv));

$application->do($ARGV[0]);
# NOTREACHED