Imported Upstream version 3.3.3
[debian/amanda] / application-src / amsamba.pl
1 #!@PERL@ 
2 # Copyright (c) 2008-2012 Zmanda, Inc.  All Rights Reserved.
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful, but
10 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11 # or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 # for more details.
13 #
14 # You should have received a copy of the GNU General Public License along
15 # with this program; if not, write to the Free Software Foundation, Inc.,
16 # 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
17 #
18 # Contact information: Zmanda Inc., 465 S. Mathilda Ave., Suite 300
19 # Sunnyvale, CA 94086, USA, or: http://www.zmanda.com
20
21 use lib '@amperldir@';
22 use strict;
23 use warnings;
24 use Getopt::Long;
25
26 package Amanda::Application::Amsamba;
27 use base qw(Amanda::Application);
28 use File::Copy;
29 use File::Path;
30 use IPC::Open2;
31 use IPC::Open3;
32 use Sys::Hostname;
33 use Symbol;
34 use IO::Handle;
35 use MIME::Base64 ();
36 use Amanda::Constants;
37 use Amanda::Config qw( :init :getconf  config_dir_relative );
38 use Amanda::Debug qw( :logging );
39 use Amanda::Paths;
40 use Amanda::Util qw( :constants :quoting);
41 use Amanda::MainLoop qw( :GIOCondition );
42
43 sub new {
44     my $class = shift;
45     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) = @_;
46     my $self = $class->SUPER::new($config);
47
48     if (defined $gnutar_path) {
49         $self->{gnutar}     = $gnutar_path;
50     } else {
51         $self->{gnutar}     = $Amanda::Constants::GNUTAR;
52     }
53     if (defined $smbclient_path) {
54         $self->{smbclient}  = $smbclient_path;
55     } else {
56         $self->{smbclient}  = $Amanda::Constants::SAMBA_CLIENT;
57     }
58     if (defined $amandapass) {
59         $self->{amandapass}  = config_dir_relative($amandapass);
60     } else {
61         $self->{amandapass}  = "$Amanda::Paths::CONFIG_DIR/amandapass";
62     }
63
64     $self->{config}           = $config;
65     $self->{host}             = $host;
66     if (defined $disk) {
67         $self->{disk}         = $disk;
68     } else {
69         $self->{disk}         = $device;
70     }
71     if (defined $device) {
72         $self->{device}       = $device;
73     } else {
74         $self->{device}       = $disk;
75     }
76     $self->{level}            = [ @{$level} ];
77     $self->{index}            = $index;
78     $self->{message}          = $message;
79     $self->{collection}       = $collection;
80     $self->{record}           = $record;
81     $self->{calcsize}         = $calcsize;
82     $self->{exclude_file}     = [ @{$exclude_file} ];
83     $self->{exclude_list}     = [ @{$exclude_list} ];
84     $self->{exclude_optional} = $exclude_optional;
85     $self->{include_file}     = [ @{$include_file} ];
86     $self->{include_list}     = [ @{$include_list} ];
87     $self->{include_optional} = $include_optional;
88     $self->{recover_mode}     = $recover_mode;
89     $self->{allow_anonymous}  = $allow_anonymous;
90     $self->{directory}        = $directory;
91
92     return $self;
93 }
94
95 # on entry:
96 #   $self->{exclude_file}
97 #   $self->{exclude_list}
98 #   $self->{include_file}
99 #   $self->{include_list}
100 #on exit:
101 #  $self->{exclude}
102 #  $self->{include}
103 #  $self->{include_filename}
104 sub validate_inexclude {
105     my $self = shift;
106
107     if ($#{$self->{exclude_file}} + $#{$self->{exclude_list}} >= -1 &&
108         $#{$self->{include_file}} + $#{$self->{include_list}} >= -1) {
109         $self->print_to_server_and_die("Can't have both include and exclude",
110                                        $Amanda::Script_App::ERROR);
111     }
112
113     if ($#{$self->{exclude_file}} >= 0) {
114         $self->{exclude} = [ @{$self->{exclude_file}} ];
115     }
116     foreach my $file (@{$self->{exclude_list}}) {
117         if (!open(FF, $file)) {
118             if ($self->{action} eq 'check' && !$self->{exclude_optional}) {
119                 $self->print_to_server("Open of '$file' failed: $!",
120                                        $Amanda::Script_App::ERROR);
121             }
122             next;
123         }
124         while (<FF>) {
125             chomp;
126             push @{$self->{exclude}}, $_;
127         }
128         close(FF);
129     }
130
131     if ($self->{action} eq "restore" and defined $self->{'include_list'}) {
132         # put all include in a single file $self->{'include_filename'}
133         $self->{'include_filename'} = "$AMANDA_TMPDIR/amsamba.$$.include";
134         open INC_FILE, ">$self->{'include_filename'}";
135         if ($#{$self->{include_file}} >= 0) {
136             print INC_FILE "$self->{include_file}\n";
137         }
138         foreach my $file (@{$self->{include_list}}) {
139             if (!open(FF, $file)) {
140                 if ($self->{action} eq 'check' && !$self->{include_optional}) {
141                     $self->print_to_server("Open of '$file' failed: $!",
142                                            $Amanda::Script_App::ERROR);
143                 }
144                 next;
145             }
146             while (<FF>) {
147                 if (defined $self->{'subdir'}) {
148                     $_ =~ s/^\./$self->{'subdir'}/;
149                 }
150                 print INC_FILE;
151             }
152             close(FF);
153         }
154
155         # add command line include for amrestore
156         for(my $i=1;defined $ARGV[$i]; $i++) {
157             my $param = $ARGV[$i];
158             $param =~ /^(.*)$/;
159             $_ = $1;
160             if (defined $self->{'subdir'}) {
161                 $_ =~ s/^\./$self->{'subdir'}/;
162             }
163             print INC_FILE "$_\n";
164         }
165
166         close INC_FILE;
167     } else {
168         # put all include in $self->{'include'} they will be added on
169         # command line.
170         if ($#{$self->{include_file}} >= 0) {
171             $self->{include} = [ @{$self->{include_file}} ];
172         }
173
174         foreach my $file (@{$self->{include_list}}) {
175             if (!open(FF, $file)) {
176                 if ($self->{action} eq 'check' && !$self->{include_optional}) {
177                     $self->print_to_server("Open of '$file' failed: $!",
178                                            $Amanda::Script_App::ERROR);
179                 }
180                 next;
181             }
182             while (<FF>) {
183                 chomp;
184                 if ($self->{action} eq "restore" and
185                     defined $self->{'subdir'}) {
186                     $_ =~ s/^\./$self->{'subdir'}/;
187                 }
188                 push @{$self->{include}}, $_;
189             }
190             close(FF);
191         }
192
193         # add command line include for amrestore
194         if ($self->{action} eq "restore") {
195                 for(my $i=1;defined $ARGV[$i]; $i++) {
196                 my $param = $ARGV[$i];
197                 $param =~ /^(.*)$/;
198                 $_ = $1;
199                 if (defined $self->{'subdir'}) {
200                     $_ =~ s/^\./$self->{'subdir'}/;
201                 }
202                 push @{$self->{include}}, $1;
203             }
204         }
205     }
206 }
207
208 # on entry:
209 #   $self->{directory} == //host/share/subdir           \\host\share\subdir
210 #   or
211 #   $self->{device}    == //host/share/subdir           \\host\share\subdir
212 # on exit:
213 #   $self->{cifshost}   = //host                        \\host
214 #   $self->{share}      = //host/share                  \\host\share
215 #   $self->{sambashare} = \\host\share                  \\host\share
216 #   $self->{subdir}     = subdir                        subdir
217 sub parsesharename {
218     my $self = shift;
219     my $to_parse = $self->{directory};
220     $to_parse = $self->{device} if !defined $to_parse;;
221
222     return if !defined $to_parse;
223     if ($to_parse =~ /^\\\\/) {
224         $self->{unc}          = 1;
225     } else {
226         $self->{unc}          = 0;
227     }
228
229     if ($self->{unc}) {
230         if ($to_parse =~ m,^(\\\\[^\\]+\\[^\\]+)\\(.*)$,) {
231             $self->{share} = $1;
232             $self->{subdir} = $2
233         } else {
234             $self->{share} = $to_parse
235         }
236         $self->{sambashare} = $self->{share};
237         $to_parse =~ m,^(\\\\[^\\]+)\\[^\\]+,;
238         $self->{cifshost} = $1;
239     } else {
240         if ($to_parse =~ m,^(//[^/]+/[^/]+)/(.*)$,) {
241             $self->{share} = $1;
242             $self->{subdir} = $2
243         } else {
244             $self->{share} = $to_parse
245         }
246         $self->{sambashare} = $self->{share};
247         $self->{sambashare} =~ s,/,\\,g;
248         $to_parse =~ m,^(//[^/]+)/[^/]+,;
249         $self->{cifshost} = $1;
250     }
251 }
252
253
254 # Read $self->{amandapass} file.
255 # on entry:
256 #   $self->{cifshost} == //host/share
257 #   $self->{share} == //host/share
258 # on exit:
259 #   $self->{domain}   = domain to connect to.
260 #   $self->{username} = username (-U)
261 #   $self->{password} = password
262 sub findpass {
263     my $self = shift;
264
265     my $amandapass;
266     my $line;
267
268     $self->{domain} = undef;
269     $self->{username} = undef;
270     $self->{password} = undef;
271
272     debug("amandapass: $self->{amandapass}");
273     if (!open($amandapass, $self->{amandapass})) {
274         if ($self->{allow_anonymous}) {
275             $self->{username} = $self->{allow_anonymous};
276             debug("cannot open password file '$self->{amandapass}': $!\n");
277             debug("Using anonymous user: $self->{username}");
278             return;
279         } else {
280             $self->print_to_server_and_die(
281                         "cannot open password file '$self->{amandapass}': $!",
282                         $Amanda::Script_App::ERROR);
283         }
284     }
285
286     while ($line = <$amandapass>) {
287         chomp $line;
288         next if $line =~ /^#/;
289         my ($diskname, $userpasswd, $domain, $extra) = Amanda::Util::split_quoted_string_friendly($line);
290         if ($extra) {
291             debug("Trailling characters ignored in amandapass line");
292         }
293         if (defined $diskname &&
294             ($diskname eq '*' ||
295              ($self->{unc}==0 && $diskname =~ m,^(//[^/]+)/\*$, && $1 eq $self->{cifshost}) ||
296              ($self->{unc}==1 && $diskname =~ m,^(\\\\[^\\]+)\\\*$, && $1 eq $self->{cifshost}) ||
297              $diskname eq $self->{share} ||
298              $diskname eq $self->{sambashare})) {
299             if (defined $userpasswd && $userpasswd ne "") {
300                 $self->{domain} = $domain if defined $domain && $domain ne "";
301                 my ($username, $password) = split('%', $userpasswd, 2);
302                 $self->{username} = $username;
303                 if ($password =~ /^6G\!dr(.*)/) {
304                     my $base64 = $1;
305                     $password = MIME::Base64::decode($base64);
306                 }
307                 $self->{password} = $password;
308                 $self->{password} = undef if (defined $password && $password eq "");
309             } else {
310                 $self->{username} = "guest";
311             }
312             close($amandapass);
313             return;
314         }
315     }
316     close($amandapass);
317     if ($self->{allow_anonymous}) {
318         $self->{username} = $self->{allow_anonymous};
319         debug("Cannot find password for share $self->{share} in $self->{amandapass}");
320         debug("Using anonymous user: $self->{username}");
321         return;
322     }
323     $self->print_to_server_and_die(
324         "Cannot find password for share $self->{share} in $self->{amandapass}",
325         $Amanda::Script_App::ERROR);
326 }
327
328 sub command_support {
329     my $self = shift;
330
331     print "CONFIG YES\n";
332     print "HOST YES\n";
333     print "DISK YES\n";
334     print "MAX-LEVEL 1\n";
335     print "INDEX-LINE YES\n";
336     print "INDEX-XML NO\n";
337     print "MESSAGE-LINE YES\n";
338     print "MESSAGE-XML NO\n";
339     print "RECORD YES\n";
340     print "COLLECTION NO\n";
341     print "MULTI-ESTIMATE NO\n";
342     print "CALCSIZE NO\n";
343     print "CLIENT-ESTIMATE YES\n";
344     print "EXCLUDE-FILE YES\n";
345     print "EXCLUDE-LIST YES\n";
346     print "EXCLUDE-OPTIONAL YES\n";
347     print "INCLUDE-FILE YES\n";
348     print "INCLUDE-LIST YES\n";
349     print "INCLUDE-OPTIONAL YES\n";
350     print "RECOVER-MODE SMB\n";
351 }
352
353 sub command_selfcheck {
354     my $self = shift;
355
356     $self->print_to_server("disk " . quote_string($self->{disk}),
357                            $Amanda::Script_App::GOOD);
358
359     $self->print_to_server("amsamba version " . $Amanda::Constants::VERSION,
360                            $Amanda::Script_App::GOOD);
361     #check binary
362     if (!defined($self->{smbclient}) || $self->{smbclient} eq "") {
363         $self->print_to_server(
364             "smbclient not set; you must define the SMBCLIENT-PATH property",
365             $Amanda::Script_App::ERROR);
366     }
367     elsif (! -e $self->{smbclient}) {
368         $self->print_to_server("$self->{smbclient} doesn't exist",
369                                $Amanda::Script_App::ERROR);
370     }
371     elsif (! -x $self->{smbclient}) {
372         $self->print_to_server("$self->{smbclient} is not executable",
373                                $Amanda::Script_App::ERROR);
374     } else {
375         my @sv = `$self->{smbclient} --version`;
376         if ($? >> 8 == 0) {
377             $sv[0] =~ /^[^0-9]*(.*)$/;
378             my $sv = $1;
379             $self->print_to_server("amsamba smbclient-version $sv",
380                                    $Amanda::Script_App::GOOD);
381         } else {
382             $self->print_to_server(
383                 "[Can't get " . $self->{smbclient} . " version]\n",
384                 $Amanda::Script_App::ERROR);
385         }
386     }
387
388     $self->print_to_server("$self->{smbclient}",
389                            $Amanda::Script_App::GOOD);
390     if (!defined $self->{disk} || !defined $self->{device}) {
391         return;
392     }
393     $self->parsesharename();
394     $self->findpass();
395     $self->validate_inexclude();
396
397     print "OK " . $self->{share} . "\n";
398     print "OK " . $self->{device} . "\n";
399     print "OK " . $self->{directory} . "\n" if defined $self->{directory};
400
401     my ($password_rdr, $password_wtr);
402     if (defined $self->{password}) {
403         # Don't set close-on-exec
404         $^F=10;
405         pipe($password_rdr, $password_wtr);
406         $^F=2;
407         $password_wtr->autoflush(1);
408     }
409     my($wtr, $rdr, $err);
410     $err = Symbol::gensym;
411     my $pid = open3($wtr, $rdr, $err, "-");
412     if ($pid == 0) {
413         #child
414         if (defined $self->{password}) {
415             my $ff = $password_rdr->fileno;
416             debug("password_rdr $ff");
417             $password_wtr->close();
418             $ENV{PASSWD_FD} = $password_rdr->fileno;
419         }
420         close(1);
421         close(2);
422         my @ARGV = ();
423         push @ARGV, $self->{smbclient}, $self->{share};
424         push @ARGV, "" if (!defined $self->{password});
425         push @ARGV, "-U", $self->{username},
426                     "-E";
427         if (defined $self->{domain}) {
428             push @ARGV, "-W", $self->{domain},
429         }
430         if (defined $self->{subdir}) {
431             push @ARGV, "-D", $self->{subdir},
432         }
433         push @ARGV, "-c", "quit";
434         debug("execute: " . $self->{smbclient} . " " .
435               join(" ", @ARGV));
436         exec {$self->{smbclient}} @ARGV;
437     }
438     #parent
439     if (defined $self->{password}) {
440         my $ff = $password_wtr->fileno;
441         debug("password_wtr $ff");
442         $password_wtr->print($self->{password});
443         $password_wtr->close();
444         $password_rdr->close();
445     } else {
446         debug("No password");
447     }
448     close($wtr);
449     close($rdr);
450     while (<$err>) {
451         chomp;
452         debug("stderr: " . $_);
453         next if /^Domain=/;
454         # message if samba server is configured with 'security = share'
455         next if /Server not using user level security and no password supplied./;
456         $self->print_to_server("smbclient: $_",
457                                $Amanda::Script_App::ERROR);
458     }
459     close($err);
460     waitpid($pid, 0);
461     #check statefile
462     #check amdevice
463 }
464
465 sub command_estimate {
466     my $self = shift;
467
468     $self->parsesharename();
469     $self->findpass();
470     $self->validate_inexclude();
471
472     my $level = $self->{level}[0];
473     my ($password_rdr, $password_wtr);
474     if (defined $self->{password}) {
475         # Don't set close-on-exec
476         $^F=10;
477         pipe($password_rdr,  $password_wtr);
478         $^F=2;
479         $password_wtr->autoflush(1);
480     }
481     my($wtr, $rdr, $err);
482     $err = Symbol::gensym;
483     my $pid = open3($wtr, $rdr, $err, "-");
484     if ($pid == 0) {
485         #child
486         if (defined $self->{password}) {
487             my $ff = $password_rdr->fileno;
488             debug("password_rdr $ff");
489             $password_wtr->close();
490             $ENV{PASSWD_FD} = $password_rdr->fileno;
491         }
492         close(0);
493         close(1);
494         my @ARGV = ();
495         push @ARGV, $self->{smbclient}, $self->{share};
496         push @ARGV, "" if (!defined($self->{password}));
497         push @ARGV, "-d", "0",
498                     "-U", $self->{username},
499                     "-E";
500         if (defined $self->{domain}) {
501             push @ARGV, "-W", $self->{domain},
502         }
503         if (defined $self->{subdir}) {
504             push @ARGV, "-D", $self->{subdir},
505         }
506         if ($level == 0) {
507             push @ARGV, "-c", "archive 0;recurse;du";
508         } else {
509             push @ARGV, "-c", "archive 1;recurse;du";
510         }
511         debug("execute: " . $self->{smbclient} . " " .
512               join(" ", @ARGV));
513         exec {$self->{smbclient}} @ARGV;
514     }
515     #parent
516     if (defined $self->{password}) {
517         my $ff = $password_wtr->fileno;
518         debug("password_wtr $ff");
519         debug("password $self->{password}");
520         $password_wtr->print($self->{password});
521         $password_wtr->close();
522         $password_rdr->close();
523     }
524     close($wtr);
525     close($rdr);
526     my $size = $self->parse_estimate($err);
527     close($err);
528     output_size($level, $size);
529     waitpid($pid, 0);
530 }
531
532 sub parse_estimate {
533     my $self = shift;
534     my($fh)  = shift;
535     my($size) = -1;
536     while(<$fh>) {
537         chomp;
538         next if /^\s*$/;
539         next if /blocks of size/;
540         next if /blocks available/;
541         next if /^\s*$/;
542         next if /^Domain=/;
543         next if /dumped \d+ files and directories/;
544         # message if samba server is configured with 'security = share'
545         next if /Server not using user level security and no password supplied./;
546         debug("stderr: $_");
547         if ($_ =~ /^Total number of bytes: (\d*)/) {
548             $size = $1;
549             last;
550         } else {
551             $self->print_to_server("smbclient: $_",
552                                    $Amanda::Script_App::ERROR);
553         }
554     }
555     return $size;
556 }
557
558 sub output_size {
559    my($level) = shift;
560    my($size) = shift;
561    if($size == -1) {
562       print "$level -1 -1\n";
563       #exit 2;
564    }
565    else {
566       my($ksize) = int $size / (1024);
567       $ksize=32 if ($ksize<32);
568       print "$level $ksize 1\n";
569    }
570 }
571
572 sub send_empty_tar_file {
573     my $self = shift;
574     my ($out1, $out2) = @_;
575     my $out;
576     my $buf;
577     my $size;
578
579     Amanda::Debug::debug("Create empty archive with: tar --create --file=- --files-from=/dev/null");
580     open2($out, undef, "tar", "--create", "--file=-", "--files-from=/dev/null");
581
582     while(($size = sysread($out, $buf, 32768))) {
583         syswrite($out1, $buf, $size);
584         syswrite($out2, $buf, $size);
585     }
586 }
587
588 sub command_backup {
589     my $self = shift;
590
591     my $level = $self->{level}[0];
592
593     $self->parsesharename();
594     $self->findpass();
595     $self->validate_inexclude();
596
597     my ($password_rdr, $password_wtr);
598     if (defined $self->{password}) {
599         # Don't set close-on-exec
600         $^F=10;
601         pipe($password_rdr,  $password_wtr);
602         $^F=2;
603         $password_wtr->autoflush(1);
604     }
605     my($smbclient_wtr, $smbclient_rdr, $smbclient_err);
606     $smbclient_err = Symbol::gensym;
607     my $pid = open3($smbclient_wtr, $smbclient_rdr, $smbclient_err, "-");
608     if ($pid == 0) {
609         #child
610         if (defined $self->{password}) {
611             my $ff = $password_rdr->fileno;
612             debug("password_rdr $ff");
613             $password_wtr->close();
614             $ENV{PASSWD_FD} = $password_rdr->fileno;
615         }
616         my @ARGV = ();
617         push @ARGV, $self->{smbclient}, $self->{share};
618         push @ARGV, "" if (!defined($self->{password}));
619         push @ARGV, "-d", "0",
620                     "-U", $self->{username},
621                     "-E";
622         if (defined $self->{domain}) {
623             push @ARGV, "-W", $self->{domain},
624         }
625         if (defined $self->{subdir}) {
626             push @ARGV, "-D", $self->{subdir},
627         }
628
629         my $comm ;
630         if ($level == 0) {
631             $comm = "tarmode full reset hidden system quiet;";
632         } else {
633             $comm = "tarmode inc noreset hidden system quiet;";
634         }
635         $comm .= " tar c";
636         if ($#{$self->{exclude}} >= 0) {
637             $comm .= "X";
638         }
639         if ($#{$self->{include}} >= 0) {
640             $comm .= "I";
641         }
642         $comm .= " -";
643         if ($#{$self->{exclude}} >= 0) {
644             $comm .= " " . join(" ", @{$self->{exclude}});
645         }
646         if ($#{$self->{include}} >= 0) {
647             $comm .= " " . join(" ", @{$self->{include}});
648         }
649         push @ARGV, "-c", $comm;
650         debug("execute: " . $self->{smbclient} . " " .
651               join(" ", @ARGV));
652         exec {$self->{smbclient}} @ARGV;
653     }
654
655     if (defined $self->{password}) {
656         my $ff = $password_wtr->fileno;
657         debug("password_wtr $ff");
658         $password_wtr->print($self->{password});
659         $password_wtr->close();
660         $password_rdr->close();
661     } else {
662         debug("No password");
663     }
664     close($smbclient_wtr);
665
666     #index process 
667     my $index_rdr;
668     my $index_wtr;
669     debug("$self->{gnutar} -tf -");
670     my $pid_index1 = open2($index_rdr, $index_wtr, $self->{gnutar}, "-tf", "-");
671     my $size = -1;
672     my $index_fd = $index_rdr->fileno;
673     debug("index $index_fd");
674     my $indexout_fd;
675     if (defined($self->{index})) {
676         open($indexout_fd, '>&=4') ||
677             $self->print_to_server_and_die("Can't open indexout_fd: $!",
678                                            $Amanda::Script_App::ERROR);
679     }
680
681     my $file_to_close = 3;
682     my $smbclient_stdout_src = Amanda::MainLoop::fd_source($smbclient_rdr,
683                                 $G_IO_IN|$G_IO_HUP|$G_IO_ERR);
684     my $smbclient_stderr_src = Amanda::MainLoop::fd_source($smbclient_err,
685                                 $G_IO_IN|$G_IO_HUP|$G_IO_ERR);
686     my $index_tar_stdout_src = Amanda::MainLoop::fd_source($index_rdr,
687                                 $G_IO_IN|$G_IO_HUP|$G_IO_ERR);
688
689     my $smbclient_stdout_done = 0;
690     my $smbclient_stderr_done = 0;
691     my $data_size = 0;
692     my $nb_files = 0;
693     $smbclient_stdout_src->set_callback(sub {
694         my $buf;
695         my $blocksize = -1;
696         $blocksize = sysread($smbclient_rdr, $buf, 32768);
697         if (!$blocksize) {
698             $file_to_close--;
699             $smbclient_stdout_src->remove();
700             $smbclient_stdout_done = 1;
701             if ($smbclient_stderr_done) {
702                 if ($data_size == 0 and $nb_files == 0 and $size == 0) {
703                     $self->send_empty_tar_file(*STDOUT, $index_wtr);
704                 }
705                 close($index_wtr);
706                 close(STDOUT);
707             }
708             close($smbclient_rdr);
709             Amanda::MainLoop::quit() if $file_to_close == 0;
710             return;
711         }
712         $data_size += $blocksize;
713         syswrite(STDOUT, $buf, $blocksize);
714         syswrite($index_wtr, $buf, $blocksize);
715     });
716
717     $smbclient_stderr_src->set_callback(sub {
718         my $line = <$smbclient_err>;
719         if (!defined $line) {
720             $file_to_close--;
721             $smbclient_stderr_src->remove();
722             $smbclient_stderr_done = 1;
723             if ($smbclient_stdout_done) {
724                 if ($data_size == 0 and $nb_files == 0 and $size == 0) {
725                     $self->send_empty_tar_file(*STDOUT, $index_wtr);
726                 }
727                 close($index_wtr);
728                 close(STDOUT);
729             }
730             close ($smbclient_err);
731             Amanda::MainLoop::quit() if $file_to_close == 0;
732             return;
733         }
734         chomp $line;
735         debug("stderr: " . $line);
736         return if $line =~ /^Domain=/;
737         return if $line =~ /^tarmode is now /;
738         if ($line =~ /dumped (\d+) files and directories/) {
739             $nb_files = $1;
740             return;
741         }
742         # message if samba server is configured with 'security = share'
743         return if $line =~$line =~  /Server not using user level security and no password supplied./;
744         if ($line =~ /^Total bytes written: (\d*)/) {
745             $size = $1;
746             return;
747         }
748         $self->print_to_server("smbclient: $line", $Amanda::Script_App::ERROR);
749     });
750
751     $index_tar_stdout_src->set_callback(sub {
752         my $line = <$index_rdr>;
753         if (!defined $line) {
754             $file_to_close--;
755             $index_tar_stdout_src->remove();
756             close($index_rdr);
757             close($indexout_fd);
758             Amanda::MainLoop::quit() if $file_to_close == 0;
759             return;
760         }
761         if ($line =~ /^\.\//) {
762             if(defined($indexout_fd)) {
763                 if(defined($self->{index})) {
764                     $line =~ s/^\.//;
765                     print $indexout_fd $line;
766                 }
767             }
768         } else {
769             chomp $line;
770             $self->print_to_server($line, $Amanda::Script_App::ERROR);
771         }
772     });
773
774     Amanda::MainLoop::run();
775
776     if ($size >= 0) {
777         my $ksize = $size / 1024;
778         if ($ksize < 32) {
779             $ksize = 32;
780         }
781         print {$self->{mesgout}} "sendbackup: size $ksize\n";
782         print {$self->{mesgout}} "sendbackup: end\n";
783     }
784
785     waitpid $pid, 0;
786     if ($? != 0) {
787         $self->print_to_server_and_die("smbclient returned error",
788                                        $Amanda::Script_App::ERROR);
789     }
790     exit 0;
791 }
792
793 sub parse_backup {
794     my $self = shift;
795     my($fhin, $fhout, $indexout) = @_;
796     my $size  = -1;
797     while(<$fhin>) {
798         if ( /^\.\//) {
799             if(defined($indexout)) {
800                 if(defined($self->{index})) {
801                     s/^\.//;
802                     print $indexout $_;
803                 }
804             }
805         }
806         else {
807             print $fhout "? $_";
808         }
809     }
810 }
811
812 sub command_index_from_output {
813    index_from_output(0, 1);
814    exit 0;
815 }
816
817 sub index_from_output {
818    my($fhin, $fhout) = @_;
819    my($size) = -1;
820    while(<$fhin>) {
821       next if /^Total bytes written:/;
822       next if !/^\.\//;
823       s/^\.//;
824       print $fhout $_;
825    }
826 }
827
828 sub command_index_from_image {
829    my $self = shift;
830    my $index_fd;
831    open($index_fd, "$self->{gnutar} --list --file - |") ||
832       $self->print_to_server_and_die("Can't run $self->{gnutar}: $!",
833                                      $Amanda::Script_App::ERROR);
834    index_from_output($index_fd, 1);
835 }
836
837 sub command_restore {
838     my $self = shift;
839     my @cmd = ();
840
841     $self->parsesharename();
842     chdir(Amanda::Util::get_original_cwd());
843
844     if ($self->{recover_mode} eq "smb") {
845         $self->validate_inexclude();
846         $self->findpass();
847         push @cmd, $self->{smbclient}, $self->{share};
848         push @cmd, "-D", $self->{'subdir'} if defined $self->{'subdir'};
849         push @cmd, "" if (!defined $self->{password});
850         push @cmd, "-d", "0",
851                    "-U", $self->{username};
852         
853         if (defined $self->{domain}) {
854             push @cmd, "-W", $self->{domain};
855         }
856         if (defined $self->{'include_filename'}) {
857             push @cmd, "-TFx", "-", "$self->{'include_filename'}";
858         } else {
859             push @cmd, "-Tx", "-";
860             if ($#{$self->{include}} >= 0) {
861                 push @cmd, @{$self->{include}};
862             }
863             for(my $i=1;defined $ARGV[$i]; $i++) {
864                 my $param = $ARGV[$i];
865                 $param =~ /^(.*)$/;
866                 push @cmd, $1;
867             }
868         }
869         my ($parent_rdr, $child_wtr);
870         if (defined $self->{password}) {
871             # Don't set close-on-exec
872             $^F=10;
873             pipe($parent_rdr,  $child_wtr);
874             $^F=2;
875             $child_wtr->autoflush(1);
876         }
877         my($wtr, $rdr, $err);
878         $err = Symbol::gensym;
879         my $pid = open3($wtr, $rdr, $err, "-");
880         if ($pid == 0) {
881             $child_wtr->print($self->{password});
882             $child_wtr->close();
883             exit 0;
884         }
885         if (defined $self->{password}) {
886             $child_wtr->close();
887             $ENV{PASSWD_FD} = $parent_rdr->fileno;
888         }
889         debug("cmd:" . join(" ", @cmd));
890         exec { $cmd[0] } @cmd;
891         die("Can't exec '", $cmd[0], "'");
892     } else {
893         push @cmd, $self->{gnutar}, "-xpvf", "-";
894         if (defined $self->{directory}) {
895             if (!-d $self->{directory}) {
896                 $self->print_to_server_and_die(
897                                        "Directory $self->{directory}: $!",
898                                        $Amanda::Script_App::ERROR);
899             }
900             if (!-w $self->{directory}) {
901                 $self->print_to_server_and_die(
902                                        "Directory $self->{directory}: $!",
903                                        $Amanda::Script_App::ERROR);
904             }
905             push @cmd, "--directory", $self->{directory};
906         }
907         if ($#{$self->{include_list}} == 0) {
908             push @cmd, "--files-from", $self->{include_list}[0];
909         }
910         if ($#{$self->{exclude_list}} == 0) {
911             push @cmd, "--exclude-from", $self->{exclude_list}[0];
912         }
913         for(my $i=1;defined $ARGV[$i]; $i++) {
914             my $param = $ARGV[$i];
915             $param =~ /^(.*)$/;
916             push @cmd, $1;
917         }
918         debug("cmd:" . join(" ", @cmd));
919         exec { $cmd[0] } @cmd;
920         die("Can't exec '", $cmd[0], "'");
921     }
922 }
923
924 sub command_validate {
925    my $self = shift;
926
927    if (!defined($self->{gnutar}) || !-x $self->{gnutar}) {
928       return $self->default_validate();
929    }
930
931    my(@cmd) = ($self->{gnutar}, "-tf", "-");
932    debug("cmd:" . join(" ", @cmd));
933    my $pid = open3('>&STDIN', '>&STDOUT', '>&STDERR', @cmd) ||
934         $self->print_to_server_and_die("Unable to run @cmd: $!",
935                                        $Amanda::Script_App::ERROR);
936    waitpid $pid, 0;
937    if( $? != 0 ){
938         $self->print_to_server_and_die("$self->{gnutar} returned error",
939                                        $Amanda::Script_App::ERROR);
940    }
941    exit(0);
942 }
943
944 sub command_print_command {
945 }
946
947 package main;
948
949 sub usage {
950     print <<EOF;
951 Usage: amsamba <command> --config=<config> --host=<host> --disk=<disk> --device=<device> --level=<level> --index=<yes|no> --message=<text> --collection=<no> --record=<yes|no> --calcsize.
952 EOF
953     exit(1);
954 }
955
956 my $opt_version;
957 my $opt_config;
958 my $opt_host;
959 my $opt_disk;
960 my $opt_device;
961 my @opt_level;
962 my $opt_index;
963 my $opt_message;
964 my $opt_collection;
965 my $opt_record;
966 my $opt_calcsize;
967 my $opt_gnutar_path;
968 my $opt_smbclient_path;
969 my $opt_amandapass;
970 my @opt_exclude_file;
971 my @opt_exclude_list;
972 my $opt_exclude_optional;
973 my @opt_include_file;
974 my @opt_include_list;
975 my $opt_include_optional;
976 my $opt_recover_mode;
977 my $opt_allow_anonymous;
978 my $opt_directory;
979
980 Getopt::Long::Configure(qw{bundling});
981 GetOptions(
982     'version'            => \$opt_version,
983     'config=s'           => \$opt_config,
984     'host=s'             => \$opt_host,
985     'disk=s'             => \$opt_disk,
986     'device=s'           => \$opt_device,
987     'level=s'            => \@opt_level,
988     'index=s'            => \$opt_index,
989     'message=s'          => \$opt_message,
990     'collection=s'       => \$opt_collection,
991     'record'             => \$opt_record,
992     'calcsize'           => \$opt_calcsize,
993     'gnutar-path=s'      => \$opt_gnutar_path,
994     'smbclient-path=s'   => \$opt_smbclient_path,
995     'amandapass=s'       => \$opt_amandapass,
996     'exclude-file=s'     => \@opt_exclude_file,
997     'exclude-list=s'     => \@opt_exclude_list,
998     'exclude-optional=s' => \$opt_exclude_optional,
999     'include-file=s'     => \@opt_include_file,
1000     'include-list=s'     => \@opt_include_list,
1001     'include-optional=s' => \$opt_include_optional,
1002     'recover-mode=s'     => \$opt_recover_mode,
1003     'allow-anonymous=s'  => \$opt_allow_anonymous,
1004     'directory=s'        => \$opt_directory,
1005 ) or usage();
1006
1007 if (defined $opt_version) {
1008     print "amsamba-" . $Amanda::Constants::VERSION , "\n";
1009     exit(0);
1010 }
1011
1012 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);
1013
1014 $application->do($ARGV[0]);