Imported Upstream version 3.3.2
[debian/amanda] / application-src / amsamba.pl
index ba45e274e190abd639a1039c194d52c9711cc50e..7a6ffd8bdb7554f1866e47eafd331ac8266e3cab 100644 (file)
@@ -1,5 +1,5 @@
 #!@PERL@ 
-# Copyright (c) 2005-2008 Zmanda Inc.  All Rights Reserved.
+# Copyright (c) 2008-2012 Zmanda, 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 version 2 as published
 # with this program; if not, write to the Free Software Foundation, Inc.,
 # 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
 #
-# Contact information: Zmanda Inc., 465 S Mathlida Ave, Suite 300
+# Contact information: Zmanda Inc., 465 S. Mathilda Ave., Suite 300
 # Sunnyvale, CA 94086, USA, or: http://www.zmanda.com
 
 use lib '@amperldir@';
 use strict;
+use warnings;
 use Getopt::Long;
 
 package Amanda::Application::Amsamba;
@@ -35,11 +36,12 @@ 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) = @_;
-    my $self = $class->SUPER::new();
+    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, $directory) = @_;
+    my $self = $class->SUPER::new($config);
 
     if (defined $gnutar_path) {
        $self->{gnutar}     = $gnutar_path;
@@ -52,15 +54,23 @@ sub new {
        $self->{smbclient}  = $Amanda::Constants::SAMBA_CLIENT;
     }
     if (defined $amandapass) {
-       $self->{amandapass}  = $amandapass;
+       $self->{amandapass}  = config_dir_relative($amandapass);
     } else {
-       $self->{amandapass}  = "$Amanda::Paths::sysconfdir/amandapass";
+       $self->{amandapass}  = "$Amanda::Paths::CONFIG_DIR/amandapass";
     }
 
     $self->{config}           = $config;
     $self->{host}             = $host;
-    $self->{disk}             = $disk;
-    $self->{device}           = $device;
+    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;
@@ -74,6 +84,8 @@ sub new {
     $self->{include_list}     = [ @{$include_list} ];
     $self->{include_optional} = $include_optional;
     $self->{recover_mode}     = $recover_mode;
+    $self->{allow_anonymous}  = $allow_anonymous;
+    $self->{directory}        = $directory;
 
     return $self;
 }
@@ -86,13 +98,13 @@ sub new {
 #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($self->{action},
-                                      "Can't have both include and exclude",
+       $self->print_to_server_and_die("Can't have both include and exclude",
                                       $Amanda::Script_App::ERROR);
     }
 
@@ -102,8 +114,7 @@ sub validate_inexclude {
     foreach my $file (@{$self->{exclude_list}}) {
        if (!open(FF, $file)) {
            if ($self->{action} eq 'check' && !$self->{exclude_optional}) {
-               $self->print_to_server($self->{action},
-                                      "Open of '$file' failed: $!",
+               $self->print_to_server("Open of '$file' failed: $!",
                                       $Amanda::Script_App::ERROR);
            }
            next;
@@ -114,53 +125,133 @@ sub validate_inexclude {
        }
        close(FF);
     }
-    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->print_to_server($self->{action},
-                                      "Open of '$file' failed: $!",
-                                      $Amanda::Script_App::ERROR);
+
+    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;
            }
-           next;
+           while (<FF>) {
+               if (defined $self->{'subdir'}) {
+                   $_ =~ s/^\./$self->{'subdir'}/;
+               }
+               print INC_FILE;
+           }
+           close(FF);
        }
-       while (<FF>) {
-           chomp;
-           push @{$self->{include}}, $_;
+
+       # 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'}/;
+           }
+           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;
+           }
        }
-       close(FF);
     }
 }
 
 # on entry:
-#   $self->disk == //host/share/subdir
+#   $self->{directory} == //host/share/subdir           \\host\share\subdir
+#   or
+#   $self->{device}    == //host/share/subdir          \\host\share\subdir
 # on exit:
-#   self->{cifshost} = //host
-#   $self->{share} = //host/share
-#   $self->{sambashare} = \\host\share
-#   $self->{subdir} = subdir
+#   $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->{directory};
+    $to_parse = $self->{device} if !defined $to_parse;;
 
-    return if !defined $self->{disk};
+    return if !defined $to_parse;
+    if ($to_parse =~ /^\\\\/) {
+       $self->{unc}          = 1;
+    } else {
+       $self->{unc}          = 0;
+    }
 
-    if ($self->{disk} =~ m,^(//[^/]+/[^/]+)/(.*)$,) {
-       $self->{share} = $1;
-       $self->{subdir} = $2
+    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 {
-       $self->{share} = $self->{disk};
+       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;
     }
-    $self->{sambashare} = $self->{share};
-    $self->{sambashare} =~ s,/,\\,g;
-    $self->{disk} =~ 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.
@@ -172,27 +263,60 @@ sub findpass {
     my $amandapass;
     my $line;
 
-    open($amandapass, $self->{amandapass});
+    $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, $userdomain) = Amanda::Util::skip_quoted_string($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 '*' ||
-            ($diskname =~ m,^(//[^/]+)/\*$, && $1 eq $self->{cifshost}) ||
+            ($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})) {
-           my ($userpasswd, $domain) = split ' ', $userdomain;
-           $self->{domain} = $domain;
-           my ($username, $password) = split('%', $userpasswd);
-           $self->{username} = $username;
-           $self->{password} = $password;
+           if (defined $userpasswd && $userpasswd ne "") {
+               $self->{domain} = $domain if defined $domain && $domain ne "";
+               my ($username, $password) = split('%', $userpasswd, 2);
+               $self->{username} = $username;
+               $self->{password} = $password;
+               $self->{password} = undef if (defined $password && $password eq "");
+            } else {
+               $self->{username} = "guest";
+            }
            close($amandapass);
            return;
        }
     }
     close($amandapass);
-    $self->print_to_server_and_die($self->{action},"Cannot find password for share $self->{share} in $self->{amandapass}", $Amanda::Script_App::ERROR);
+    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 {
@@ -210,6 +334,7 @@ sub command_support {
     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";
@@ -222,33 +347,75 @@ sub command_support {
 sub command_selfcheck {
     my $self = shift;
 
-    $self->{action} = 'check';
+    $self->print_to_server("disk " . quote_string($self->{disk}));
+
+    $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->{disk} . "\n";
     print "OK " . $self->{device} . "\n";
-
-    my ($child_rdr, $parent_wtr);
-    $^F=10;
-    pipe($child_rdr,  $parent_wtr);
-    $parent_wtr->autoflush(1);
+    print "OK " . $self->{directory} . "\n" if defined $self->{directory};
+
+    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
-       my $ff = $child_rdr->fileno;
-       debug("child_rdr $ff");
-       $parent_wtr->close();
-       $ENV{PASSWD_FD} = $child_rdr->fileno;
+        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},
-                   "-U", $self->{username},
+       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},
@@ -262,24 +429,28 @@ sub command_selfcheck {
        exec {$self->{smbclient}} @ARGV;
     }
     #parent
-    my $ff = $parent_wtr->fileno;
-    debug("parent_wtr $ff");
-    debug("password $self->{password}");
-    $parent_wtr->print($self->{password});
-    $parent_wtr->close();
-    $child_rdr->close();
+    if (defined $self->{password}) {
+        my $ff = $password_wtr->fileno;
+        debug("password_wtr $ff");
+        $password_wtr->print($self->{password});
+        $password_wtr->close();
+        $password_rdr->close();
+    } else {
+       debug("No password");
+    }
     close($wtr);
     close($rdr);
     while (<$err>) {
        chomp;
        debug("stderr: " . $_);
        next if /^Domain=/;
-       $self->print_to_server($self->{action}, "smbclient: $_",
+       # 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 binary
     #check statefile
     #check amdevice
 }
@@ -287,30 +458,36 @@ sub command_selfcheck {
 sub command_estimate {
     my $self = shift;
 
-    $self->{action} = 'estimate';
     $self->parsesharename();
     $self->findpass();
     $self->validate_inexclude();
 
     my $level = $self->{level}[0];
-    my ($child_rdr, $parent_wtr);
-    $^F=10;
-    pipe($child_rdr,  $parent_wtr);
-    $parent_wtr->autoflush(1);
+    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
-       my $ff = $child_rdr->fileno;
-       debug("child_rdr $ff");
-       $parent_wtr->close();
-       $ENV{PASSWD_FD} = $child_rdr->fileno;
+        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},
-                   "-d", "0",
+       push @ARGV, $self->{smbclient}, $self->{share};
+       push @ARGV, "" if (!defined($self->{password}));
+       push @ARGV, "-d", "0",
                    "-U", $self->{username},
                    "-E";
        if (defined $self->{domain}) {
@@ -329,12 +506,14 @@ sub command_estimate {
        exec {$self->{smbclient}} @ARGV;
     }
     #parent
-    my $ff = $parent_wtr->fileno;
-    debug("parent_wtr $ff");
-    debug("password $self->{password}");
-    $parent_wtr->print($self->{password});
-    $parent_wtr->close();
-    $child_rdr->close();
+    if (defined $self->{password}) {
+        my $ff = $password_wtr->fileno;
+        debug("password_wtr $ff");
+        debug("password $self->{password}");
+        $password_wtr->print($self->{password});
+        $password_wtr->close();
+        $password_rdr->close();
+    }
     close($wtr);
     close($rdr);
     my $size = $self->parse_estimate($err);
@@ -355,12 +534,14 @@ sub parse_estimate {
        next if /^\s*$/;
        next if /^Domain=/;
        next if /dumped \d+ files and directories/;
+       # 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($self->{action}, "smbclient: $_",
+           $self->print_to_server("smbclient: $_",
                                   $Amanda::Script_App::ERROR);
        }
     }
@@ -381,49 +562,54 @@ sub output_size {
    }
 }
 
+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;
 
-    $self->{action} = 'backup';
+    my $level = $self->{level}[0];
+
     $self->parsesharename();
     $self->findpass();
     $self->validate_inexclude();
 
-    my $level = $self->{level}[0];
-    my $mesgout_fd;
-    open($mesgout_fd, '>&=3') || die();
-    $self->{mesgout} = $mesgout_fd;
-
-    my $pid_tee = open3(\*INDEX_IN, '>&STDOUT', \*INDEX_TEE, "-");
-    if ($pid_tee == 0) {
-       close(INDEX_IN);
-       close(INDEX_TEE);
-       my $buf;
-       my $size = -1;
-       while (($size = POSIX::read(0, $buf, 32768)) > 0) {
-           POSIX::write(1, $buf, $size);
-           POSIX::write(2, $buf, $size);
-       }
-       exit 0;
-    }
-    my ($child_rdr, $parent_wtr);
-    $^F=10;
-    pipe($child_rdr,  $parent_wtr);
-    $^F=2;
-    $parent_wtr->autoflush(1);
-    my($wtr, $err);
-    $err = Symbol::gensym;
-    my $pid = open3($wtr, ">&INDEX_IN", $err, "-");
+    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
-       my $ff = $child_rdr->fileno;
-       debug("child_rdr $ff");
-       $parent_wtr->close();
-       $ENV{PASSWD_FD} = $child_rdr->fileno;
-       close(0);
+       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},
-                   "-d", "0",
+       push @ARGV, $self->{smbclient}, $self->{share};
+       push @ARGV, "" if (!defined($self->{password}));
+       push @ARGV, "-d", "0",
                    "-U", $self->{username},
                    "-E";
        if (defined $self->{domain}) {
@@ -432,82 +618,166 @@ sub command_backup {
        if (defined $self->{subdir}) {
            push @ARGV, "-D", $self->{subdir},
        }
+
        my $comm ;
        if ($level == 0) {
-           $comm = "-Tqca";
+           $comm = "tarmode full reset hidden system quiet;";
        } else {
-           $comm = "-Tqcg";
+           $comm = "tarmode inc noreset hidden system quiet;";
        }
+       $comm .= " tar c";
        if ($#{$self->{exclude}} >= 0) {
            $comm .= "X";
        }
        if ($#{$self->{include}} >= 0) {
            $comm .= "I";
        }
-       push @ARGV, $comm, "-";
+       $comm .= " -";
        if ($#{$self->{exclude}} >= 0) {
-           push @ARGV, @{$self->{exclude}};
+           $comm .= " " . join(" ", @{$self->{exclude}});
        }
        if ($#{$self->{include}} >= 0) {
-           push @ARGV, @{$self->{include}};
+           $comm .= " " . join(" ", @{$self->{include}});
        }
+       push @ARGV, "-c", $comm;
        debug("execute: " . $self->{smbclient} . " " .
              join(" ", @ARGV));
        exec {$self->{smbclient}} @ARGV;
     }
 
-    my $ff = $parent_wtr->fileno;
-    debug("parent_wtr $ff");
-    debug("password $self->{password}");
-    $parent_wtr->print($self->{password});
-    $parent_wtr->close();
-    $child_rdr->close();
-    close($wtr);
+    if (defined $self->{password}) {
+        my $ff = $password_wtr->fileno;
+        debug("password_wtr $ff");
+        $password_wtr->print($self->{password});
+        $password_wtr->close();
+        $password_rdr->close();
+    } else {
+       debug("No password");
+    }
+    close($smbclient_wtr);
 
     #index process 
-    my $index;
+    my $index_rdr;
+    my $index_wtr;
     debug("$self->{gnutar} -tf -");
-    my $pid_index1 = open2($index, '<&INDEX_TEE', $self->{gnutar}, "-tf", "-");
-    close(INDEX_IN);
+    my $pid_index1 = open2($index_rdr, $index_wtr, $self->{gnutar}, "-tf", "-");
     my $size = -1;
-    my $index_fd = $index->fileno;
+    my $index_fd = $index_rdr->fileno;
     debug("index $index_fd");
+    my $indexout_fd;
     if (defined($self->{index})) {
-       my $indexout_fd;
-       open($indexout_fd, '>&=4') || die();
-       $self->parse_backup($index, $mesgout_fd, $indexout_fd);
-       close($indexout_fd);
+       open($indexout_fd, '>&=4') ||
+           $self->print_to_server_and_die("Can't open indexout_fd: $!",
+                                          $Amanda::Script_App::ERROR);
     }
-    else {
-       $self->parse_backup($index_fd, $mesgout_fd, undef);
-    }
-    close($index);
 
-    while (<$err>) {
-       chomp;
-       debug("stderr: " . $_);
-       next if /^Domain=/;
-       next if /dumped (\d+) files and directories/;
-       if (/^Total bytes written: (\d*)/) {
+    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 $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 /;
+       if ($line =~ /dumped (\d+) files and directories/) {
+           $nb_files = $1;
+           return;
+       }
+       # message if samba server is configured with 'security = share'
+       return if $line =~$line =~  /Server not using user level security and no password supplied./;
+       if ($line =~ /^Total bytes written: (\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 {
-           $self->print_to_server($self->{action}, "smbclient: $_",
-                                  $Amanda::Script_App::ERROR);
+           chomp $line;
+           $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 $mesgout_fd "sendbackup: size $ksize\n";
-       print $mesgout_fd "sendbackup: end\n";
+       print {$self->{mesgout}} "sendbackup: size $ksize\n";
+       print {$self->{mesgout}} "sendbackup: end\n";
     }
 
     waitpid $pid, 0;
     if ($? != 0) {
-       $self->print_to_server_and_die($self->{action},
-                                      "smbclient returned error",
+       $self->print_to_server_and_die("smbclient returned error",
                                       $Amanda::Script_App::ERROR);
     }
     exit 0;
@@ -551,7 +821,9 @@ sub index_from_output {
 sub command_index_from_image {
    my $self = shift;
    my $index_fd;
-   open($index_fd, "$self->{gnutar} --list --file - |") || die();
+   open($index_fd, "$self->{gnutar} --list --file - |") ||
+      $self->print_to_server_and_die("Can't run $self->{gnutar}: $!",
+                                    $Amanda::Script_App::ERROR);
    index_from_output($index_fd, 1);
 }
 
@@ -559,29 +831,42 @@ sub command_restore {
     my $self = shift;
     my @cmd = ();
 
-    $self->{restore} = 'backup';
     $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},
-                  "-d", "0",
+       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};
        }
-       push @cmd, "-Tx", "-";
-       for(my $i=1;defined $ARGV[$i]; $i++) {
-           my $param = $ARGV[$i];
-           $param =~ /^(.*)$/;
-           push @cmd, $1;
+       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);
-       $^F=10;
-       pipe($parent_rdr,  $child_wtr);
-       $child_wtr->autoflush(1);
+       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, "-");
@@ -590,13 +875,34 @@ sub command_restore {
            $child_wtr->close();
            exit 0;
        }
-       $child_wtr->close();
-       $ENV{PASSWD_FD} = $parent_rdr->fileno;
+       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->{directory}) {
+           if (!-d $self->{directory}) {
+               $self->print_to_server_and_die(
+                                      "Directory $self->{directory}: $!",
+                                      $Amanda::Script_App::ERROR);
+           }
+           if (!-w $self->{directory}) {
+               $self->print_to_server_and_die(
+                                      "Directory $self->{directory}: $!",
+                                      $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 =~ /^(.*)$/;
@@ -611,13 +917,19 @@ sub command_restore {
 sub command_validate {
    my $self = shift;
 
-   $self->{validate} = 'backup';
+   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) || die("validate", "Unable to run @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 ){
-       die("validate", "$self->{gnutar} returned error");
+       $self->print_to_server_and_die("$self->{gnutar} returned error",
+                                      $Amanda::Script_App::ERROR);
    }
    exit(0);
 }
@@ -655,6 +967,8 @@ my @opt_include_file;
 my @opt_include_list;
 my $opt_include_optional;
 my $opt_recover_mode;
+my $opt_allow_anonymous;
+my $opt_directory;
 
 Getopt::Long::Configure(qw{bundling});
 GetOptions(
@@ -669,9 +983,9 @@ GetOptions(
     'collection=s'       => \$opt_collection,
     'record'             => \$opt_record,
     'calcsize'           => \$opt_calcsize,
-    'gnutar_path'        => \$opt_gnutar_path,
-    'smbclient_path'     => \$opt_smbclient_path,
-    'amandapass'         => \$opt_amandapass,
+    '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,
@@ -679,6 +993,8 @@ GetOptions(
     'include-list=s'     => \@opt_include_list,
     'include-optional=s' => \$opt_include_optional,
     'recover-mode=s'     => \$opt_recover_mode,
+    'allow-anonymous=s'  => \$opt_allow_anonymous,
+    'directory=s'        => \$opt_directory,
 ) or usage();
 
 if (defined $opt_version) {
@@ -686,6 +1002,6 @@ if (defined $opt_version) {
     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);
+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_directory);
 
 $application->do($ARGV[0]);