2 # Copyright (c) 2008, 2009, 2010 Zmanda, Inc. All Rights Reserved.
4 # This program is free software; you can redistribute it and/or modify it
5 # under the terms of the GNU General Public License version 2 as published
6 # by the Free Software Foundation.
8 # This program is distributed in the hope that it will be useful, but
9 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13 # You should have received a copy of the GNU General Public License along
14 # with this program; if not, write to the Free Software Foundation, Inc.,
15 # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 # Contact information: Zmanda Inc., 465 S. Mathilda Ave., Suite 300
18 # Sunnyvale, CA 94086, USA, or: http://www.zmanda.com
20 use lib '@amperldir@';
25 package Amanda::Application::Amsamba;
26 use base qw(Amanda::Application);
34 use Amanda::Constants;
35 use Amanda::Config qw( :init :getconf config_dir_relative );
36 use Amanda::Debug qw( :logging );
38 use Amanda::Util qw( :constants :quoting);
42 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) = @_;
43 my $self = $class->SUPER::new($config);
45 if (defined $gnutar_path) {
46 $self->{gnutar} = $gnutar_path;
48 $self->{gnutar} = $Amanda::Constants::GNUTAR;
50 if (defined $smbclient_path) {
51 $self->{smbclient} = $smbclient_path;
53 $self->{smbclient} = $Amanda::Constants::SAMBA_CLIENT;
55 if (defined $amandapass) {
56 $self->{amandapass} = config_dir_relative($amandapass);
58 $self->{amandapass} = "$Amanda::Paths::CONFIG_DIR/amandapass";
61 $self->{config} = $config;
62 $self->{host} = $host;
64 $self->{disk} = $disk;
66 $self->{disk} = $device;
68 if (defined $device) {
69 $self->{device} = $device;
71 $self->{device} = $disk;
73 if ($self->{disk} =~ /^\\\\/) {
78 $self->{level} = [ @{$level} ];
79 $self->{index} = $index;
80 $self->{message} = $message;
81 $self->{collection} = $collection;
82 $self->{record} = $record;
83 $self->{calcsize} = $calcsize;
84 $self->{exclude_file} = [ @{$exclude_file} ];
85 $self->{exclude_list} = [ @{$exclude_list} ];
86 $self->{exclude_optional} = $exclude_optional;
87 $self->{include_file} = [ @{$include_file} ];
88 $self->{include_list} = [ @{$include_list} ];
89 $self->{include_optional} = $include_optional;
90 $self->{recover_mode} = $recover_mode;
91 $self->{allow_anonymous} = $allow_anonymous;
92 $self->{directory} = $directory;
98 # $self->{exclude_file}
99 # $self->{exclude_list}
100 # $self->{include_file}
101 # $self->{include_list}
105 sub validate_inexclude {
108 if ($#{$self->{exclude_file}} + $#{$self->{exclude_list}} >= -1 &&
109 $#{$self->{include_file}} + $#{$self->{include_list}} >= -1) {
110 $self->print_to_server_and_die("Can't have both include and exclude",
111 $Amanda::Script_App::ERROR);
114 if ($#{$self->{exclude_file}} >= 0) {
115 $self->{exclude} = [ @{$self->{exclude_file}} ];
117 foreach my $file (@{$self->{exclude_list}}) {
118 if (!open(FF, $file)) {
119 if ($self->{action} eq 'check' && !$self->{exclude_optional}) {
120 $self->print_to_server("Open of '$file' failed: $!",
121 $Amanda::Script_App::ERROR);
127 push @{$self->{exclude}}, $_;
131 if ($#{$self->{include_file}} >= 0) {
132 $self->{include} = [ @{$self->{include_file}} ];
134 foreach my $file (@{$self->{include_list}}) {
135 if (!open(FF, $file)) {
136 if ($self->{action} eq 'check' && !$self->{include_optional}) {
137 $self->print_to_server("Open of '$file' failed: $!",
138 $Amanda::Script_App::ERROR);
144 push @{$self->{include}}, $_;
151 # $self->{directory} == //host/share/subdir \\host\share\subdir
153 # $self->{device} == //host/share/subdir \\host\share\subdir
155 # $self->{cifshost} = //host \\host
156 # $self->{share} = //host/share \\host\share
157 # $self->{sambashare} = \\host\share \\host\share
158 # $self->{subdir} = subdir subdir
161 my $to_parse = $self->{directory};
162 $to_parse = $self->{device} if !defined $to_parse;;
164 return if !defined $to_parse;
167 if ($to_parse =~ m,^(\\\\[^\\]+\\[^\\]+)\\(.*)$,) {
171 $self->{share} = $to_parse
173 $self->{sambashare} = $self->{share};
174 $to_parse =~ m,^(\\\\[^\\]+)\\[^\\]+,;
175 $self->{cifshost} = $1;
177 if ($to_parse =~ m,^(//[^/]+/[^/]+)/(.*)$,) {
181 $self->{share} = $to_parse
183 $self->{sambashare} = $self->{share};
184 $self->{sambashare} =~ s,/,\\,g;
185 $to_parse =~ m,^(//[^/]+)/[^/]+,;
186 $self->{cifshost} = $1;
191 # Read $self->{amandapass} file.
193 # $self->{cifshost} == //host/share
194 # $self->{share} == //host/share
196 # $self->{domain} = domain to connect to.
197 # $self->{username} = username (-U)
198 # $self->{password} = password
205 $self->{domain} = undef;
206 $self->{username} = undef;
207 $self->{password} = undef;
209 debug("amandapass: $self->{amandapass}");
210 if (!open($amandapass, $self->{amandapass})) {
211 if ($self->{allow_anonymous}) {
212 $self->{username} = $self->{allow_anonymous};
213 debug("cannot open password file '$self->{amandapass}': $!\n");
214 debug("Using anonymous user: $self->{username}");
217 $self->print_to_server_and_die(
218 "cannot open password file '$self->{amandapass}': $!",
219 $Amanda::Script_App::ERROR);
223 while ($line = <$amandapass>) {
225 next if $line =~ /^#/;
226 my ($diskname, $userpasswd, $domain, $extra) = Amanda::Util::split_quoted_string_friendly($line);
228 debug("Trailling characters ignored in amandapass line");
230 if (defined $diskname &&
232 ($self->{unc}==0 && $diskname =~ m,^(//[^/]+)/\*$, && $1 eq $self->{cifshost}) ||
233 ($self->{unc}==1 && $diskname =~ m,^(\\\\[^\\]+)\\\*$, && $1 eq $self->{cifshost}) ||
234 $diskname eq $self->{share} ||
235 $diskname eq $self->{sambashare})) {
236 if (defined $userpasswd && $userpasswd ne "") {
237 $self->{domain} = $domain if ($domain ne "");
238 my ($username, $password) = split('%', $userpasswd, 2);
239 $self->{username} = $username;
240 $self->{password} = $password;
241 $self->{password} = undef if (defined $password && $password eq "");
243 $self->{username} = "guest";
250 if ($self->{allow_anonymous}) {
251 $self->{username} = $self->{allow_anonymous};
252 debug("Cannot find password for share $self->{share} in $self->{amandapass}");
253 debug("Using anonymous user: $self->{username}");
256 $self->print_to_server_and_die(
257 "Cannot find password for share $self->{share} in $self->{amandapass}",
258 $Amanda::Script_App::ERROR);
261 sub command_support {
264 print "CONFIG YES\n";
267 print "MAX-LEVEL 1\n";
268 print "INDEX-LINE YES\n";
269 print "INDEX-XML NO\n";
270 print "MESSAGE-LINE YES\n";
271 print "MESSAGE-XML NO\n";
272 print "RECORD YES\n";
273 print "COLLECTION NO\n";
274 print "MULTI-ESTIMATE NO\n";
275 print "CALCSIZE NO\n";
276 print "CLIENT-ESTIMATE YES\n";
277 print "EXCLUDE-FILE YES\n";
278 print "EXCLUDE-LIST YES\n";
279 print "EXCLUDE-OPTIONAL YES\n";
280 print "INCLUDE-FILE YES\n";
281 print "INCLUDE-LIST YES\n";
282 print "INCLUDE-OPTIONAL YES\n";
283 print "RECOVER-MODE SMB\n";
286 sub command_selfcheck {
290 if (!defined($self->{smbclient}) || $self->{smbclient} eq "") {
291 $self->print_to_server(
292 "smbclient not set; you must define the SMBCLIENT-PATH property",
293 $Amanda::Script_App::ERROR);
295 elsif (! -e $self->{smbclient}) {
296 $self->print_to_server("$self->{smbclient} doesn't exist",
297 $Amanda::Script_App::ERROR);
299 elsif (! -x $self->{smbclient}) {
300 $self->print_to_server("$self->{smbclient} is not executable",
301 $Amanda::Script_App::ERROR);
303 $self->print_to_server("$self->{smbclient}",
304 $Amanda::Script_App::GOOD);
305 if (!defined $self->{disk} || !defined $self->{device}) {
308 $self->parsesharename();
310 $self->validate_inexclude();
312 print "OK " . $self->{share} . "\n";
313 print "OK " . $self->{disk} . "\n";
314 print "OK " . $self->{device} . "\n";
315 print "OK " . $self->{directory} . "\n" if defined $self->{directory};
317 my ($child_rdr, $parent_wtr);
318 if (defined $self->{password}) {
319 # Don't set close-on-exec
321 pipe($child_rdr, $parent_wtr);
323 $parent_wtr->autoflush(1);
325 my($wtr, $rdr, $err);
326 $err = Symbol::gensym;
327 my $pid = open3($wtr, $rdr, $err, "-");
330 if (defined $self->{password}) {
331 my $ff = $child_rdr->fileno;
332 debug("child_rdr $ff");
333 $parent_wtr->close();
334 $ENV{PASSWD_FD} = $child_rdr->fileno;
339 push @ARGV, $self->{smbclient}, $self->{share};
340 push @ARGV, "" if (!defined $self->{password});
341 push @ARGV, "-U", $self->{username},
343 if (defined $self->{domain}) {
344 push @ARGV, "-W", $self->{domain},
346 if (defined $self->{subdir}) {
347 push @ARGV, "-D", $self->{subdir},
349 push @ARGV, "-c", "quit";
350 debug("execute: " . $self->{smbclient} . " " .
352 exec {$self->{smbclient}} @ARGV;
355 if (defined $self->{password}) {
356 my $ff = $parent_wtr->fileno;
357 debug("parent_wtr $ff");
358 $parent_wtr->print($self->{password});
359 $parent_wtr->close();
362 debug("No password");
368 debug("stderr: " . $_);
370 # message if samba server is configured with 'security = share'
371 next if /Server not using user level security and no password supplied./;
372 $self->print_to_server("smbclient: $_",
373 $Amanda::Script_App::ERROR);
381 sub command_estimate {
384 $self->parsesharename();
386 $self->validate_inexclude();
388 my $level = $self->{level}[0];
389 my ($child_rdr, $parent_wtr);
390 if (defined $self->{password}) {
391 # Don't set close-on-exec
393 pipe($child_rdr, $parent_wtr);
395 $parent_wtr->autoflush(1);
397 my($wtr, $rdr, $err);
398 $err = Symbol::gensym;
399 my $pid = open3($wtr, $rdr, $err, "-");
402 if (defined $self->{password}) {
403 my $ff = $child_rdr->fileno;
404 debug("child_rdr $ff");
405 $parent_wtr->close();
406 $ENV{PASSWD_FD} = $child_rdr->fileno;
411 push @ARGV, $self->{smbclient}, $self->{share};
412 push @ARGV, "" if (!defined($self->{password}));
413 push @ARGV, "-d", "0",
414 "-U", $self->{username},
416 if (defined $self->{domain}) {
417 push @ARGV, "-W", $self->{domain},
419 if (defined $self->{subdir}) {
420 push @ARGV, "-D", $self->{subdir},
423 push @ARGV, "-c", "archive 0;recurse;du";
425 push @ARGV, "-c", "archive 1;recurse;du";
427 debug("execute: " . $self->{smbclient} . " " .
429 exec {$self->{smbclient}} @ARGV;
432 if (defined $self->{password}) {
433 my $ff = $parent_wtr->fileno;
434 debug("parent_wtr $ff");
435 debug("password $self->{password}");
436 $parent_wtr->print($self->{password});
437 $parent_wtr->close();
442 my $size = $self->parse_estimate($err);
444 output_size($level, $size);
455 next if /blocks of size/;
456 next if /blocks available/;
459 next if /dumped \d+ files and directories/;
460 # message if samba server is configured with 'security = share'
461 next if /Server not using user level security and no password supplied./;
463 if ($_ =~ /^Total number of bytes: (\d*)/) {
467 $self->print_to_server("smbclient: $_",
468 $Amanda::Script_App::ERROR);
478 print "$level -1 -1\n";
482 my($ksize) = int $size / (1024);
483 $ksize=32 if ($ksize<32);
484 print "$level $ksize 1\n";
491 my $level = $self->{level}[0];
493 $self->parsesharename();
495 $self->validate_inexclude();
497 my $pid_tee = open3(\*INDEX_IN, '>&STDOUT', \*INDEX_TEE, "-");
503 while (($size = POSIX::read(0, $buf, 32768)) > 0) {
504 POSIX::write(1, $buf, $size);
505 POSIX::write(2, $buf, $size);
509 my ($child_rdr, $parent_wtr);
510 if (defined $self->{password}) {
511 # Don't set close-on-exec
513 pipe($child_rdr, $parent_wtr);
515 $parent_wtr->autoflush(1);
518 $err = Symbol::gensym;
519 my $pid = open3($wtr, ">&INDEX_IN", $err, "-");
522 if (defined $self->{password}) {
523 my $ff = $child_rdr->fileno;
524 debug("child_rdr $ff");
525 $parent_wtr->close();
526 $ENV{PASSWD_FD} = $child_rdr->fileno;
530 push @ARGV, $self->{smbclient}, $self->{share};
531 push @ARGV, "" if (!defined($self->{password}));
532 push @ARGV, "-d", "0",
533 "-U", $self->{username},
535 if (defined $self->{domain}) {
536 push @ARGV, "-W", $self->{domain},
538 if (defined $self->{subdir}) {
539 push @ARGV, "-D", $self->{subdir},
544 $comm = "tarmode full reset hidden system quiet;";
546 $comm = "tarmode inc noreset hidden system quiet;";
549 if ($#{$self->{exclude}} >= 0) {
552 if ($#{$self->{include}} >= 0) {
556 if ($#{$self->{exclude}} >= 0) {
557 $comm .= " " . join(" ", @{$self->{exclude}});
559 if ($#{$self->{include}} >= 0) {
560 $comm .= " " . join(" ", @{$self->{include}});
562 push @ARGV, "-c", $comm;
563 debug("execute: " . $self->{smbclient} . " " .
565 exec {$self->{smbclient}} @ARGV;
568 if (defined $self->{password}) {
569 my $ff = $parent_wtr->fileno;
570 debug("parent_wtr $ff");
571 debug("password $self->{password}");
572 $parent_wtr->print($self->{password});
573 $parent_wtr->close();
576 debug("No password");
582 debug("$self->{gnutar} -tf -");
583 my $pid_index1 = open2($index, '<&INDEX_TEE', $self->{gnutar}, "-tf", "-");
586 my $index_fd = $index->fileno;
587 debug("index $index_fd");
588 if (defined($self->{index})) {
590 open($indexout_fd, '>&=4') ||
591 $self->print_to_server_and_die("Can't open indexout_fd: $!",
592 $Amanda::Script_App::ERROR);
593 $self->parse_backup($index, $self->{mesgout}, $indexout_fd);
597 $self->parse_backup($index_fd, $self->{mesgout}, undef);
603 debug("stderr: " . $_);
605 next if /^tarmode is now /;
606 next if /dumped (\d+) files and directories/;
607 # message if samba server is configured with 'security = share'
608 next if /Server not using user level security and no password supplied./;
609 if (/^Total bytes written: (\d*)/) {
612 $self->print_to_server("smbclient: $_",
613 $Amanda::Script_App::ERROR);
617 my $ksize = $size / 1024;
621 print {$self->{mesgout}} "sendbackup: size $ksize\n";
622 print {$self->{mesgout}} "sendbackup: end\n";
627 $self->print_to_server_and_die("smbclient returned error",
628 $Amanda::Script_App::ERROR);
635 my($fhin, $fhout, $indexout) = @_;
639 if(defined($indexout)) {
640 if(defined($self->{index})) {
652 sub command_index_from_output {
653 index_from_output(0, 1);
657 sub index_from_output {
658 my($fhin, $fhout) = @_;
661 next if /^Total bytes written:/;
668 sub command_index_from_image {
671 open($index_fd, "$self->{gnutar} --list --file - |") ||
672 $self->print_to_server_and_die("Can't run $self->{gnutar}: $!",
673 $Amanda::Script_App::ERROR);
674 index_from_output($index_fd, 1);
677 sub command_restore {
681 $self->parsesharename();
682 chdir(Amanda::Util::get_original_cwd());
684 if ($self->{recover_mode} eq "smb") {
685 $self->validate_inexclude();
687 push @cmd, $self->{smbclient}, $self->{share};
688 push @cmd, "" if (!defined $self->{password});
689 push @cmd, "-d", "0",
690 "-U", $self->{username};
692 if (defined $self->{domain}) {
693 push @cmd, "-W", $self->{domain};
695 push @cmd, "-Tx", "-";
696 if ($#{$self->{include}} >= 0) {
697 push @cmd, @{$self->{include}};
699 for(my $i=1;defined $ARGV[$i]; $i++) {
700 my $param = $ARGV[$i];
704 my ($parent_rdr, $child_wtr);
705 if (defined $self->{password}) {
706 # Don't set close-on-exec
708 pipe($parent_rdr, $child_wtr);
710 $child_wtr->autoflush(1);
712 my($wtr, $rdr, $err);
713 $err = Symbol::gensym;
714 my $pid = open3($wtr, $rdr, $err, "-");
716 $child_wtr->print($self->{password});
720 if (defined $self->{password}) {
722 $ENV{PASSWD_FD} = $parent_rdr->fileno;
724 debug("cmd:" . join(" ", @cmd));
725 exec { $cmd[0] } @cmd;
726 die("Can't exec '", $cmd[0], "'");
728 push @cmd, $self->{gnutar}, "-xpvf", "-";
729 if (defined $self->{directory}) {
730 if (!-d $self->{directory}) {
731 $self->print_to_server_and_die(
732 "Directory $self->{directory}: $!",
733 $Amanda::Script_App::ERROR);
735 if (!-w $self->{directory}) {
736 $self->print_to_server_and_die(
737 "Directory $self->{directory}: $!",
738 $Amanda::Script_App::ERROR);
740 push @cmd, "--directory", $self->{directory};
742 if ($#{$self->{include_list}} == 0) {
743 push @cmd, "--files-from", $self->{include_list}[0];
745 if ($#{$self->{exclude_list}} == 0) {
746 push @cmd, "--exclude-from", $self->{exclude_list}[0];
748 for(my $i=1;defined $ARGV[$i]; $i++) {
749 my $param = $ARGV[$i];
753 debug("cmd:" . join(" ", @cmd));
754 exec { $cmd[0] } @cmd;
755 die("Can't exec '", $cmd[0], "'");
759 sub command_validate {
762 if (!defined($self->{gnutar}) || !-x $self->{gnutar}) {
763 return $self->default_validate();
766 my(@cmd) = ($self->{gnutar}, "-tf", "-");
767 debug("cmd:" . join(" ", @cmd));
768 my $pid = open3('>&STDIN', '>&STDOUT', '>&STDERR', @cmd) ||
769 $self->print_to_server_and_die("Unable to run @cmd: $!",
770 $Amanda::Script_App::ERROR);
773 $self->print_to_server_and_die("$self->{gnutar} returned error",
774 $Amanda::Script_App::ERROR);
779 sub command_print_command {
786 Usage: amsamba <command> --config=<config> --host=<host> --disk=<disk> --device=<device> --level=<level> --index=<yes|no> --message=<text> --collection=<no> --record=<yes|no> --calcsize.
803 my $opt_smbclient_path;
805 my @opt_exclude_file;
806 my @opt_exclude_list;
807 my $opt_exclude_optional;
808 my @opt_include_file;
809 my @opt_include_list;
810 my $opt_include_optional;
811 my $opt_recover_mode;
812 my $opt_allow_anonymous;
815 Getopt::Long::Configure(qw{bundling});
817 'version' => \$opt_version,
818 'config=s' => \$opt_config,
819 'host=s' => \$opt_host,
820 'disk=s' => \$opt_disk,
821 'device=s' => \$opt_device,
822 'level=s' => \@opt_level,
823 'index=s' => \$opt_index,
824 'message=s' => \$opt_message,
825 'collection=s' => \$opt_collection,
826 'record' => \$opt_record,
827 'calcsize' => \$opt_calcsize,
828 'gnutar-path=s' => \$opt_gnutar_path,
829 'smbclient-path=s' => \$opt_smbclient_path,
830 'amandapass=s' => \$opt_amandapass,
831 'exclude-file=s' => \@opt_exclude_file,
832 'exclude-list=s' => \@opt_exclude_list,
833 'exclude-optional=s' => \$opt_exclude_optional,
834 'include-file=s' => \@opt_include_file,
835 'include-list=s' => \@opt_include_list,
836 'include-optional=s' => \$opt_include_optional,
837 'recover-mode=s' => \$opt_recover_mode,
838 'allow-anonymous=s' => \$opt_allow_anonymous,
839 'directory=s' => \$opt_directory,
842 if (defined $opt_version) {
843 print "amsamba-" . $Amanda::Constants::VERSION , "\n";
847 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);
849 $application->do($ARGV[0]);