2 # Copyright (c) 2008,2009 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@';
24 package Amanda::Application::Amsamba;
25 use base qw(Amanda::Application);
33 use Amanda::Constants;
34 use Amanda::Config qw( :init :getconf config_dir_relative );
35 use Amanda::Debug qw( :logging );
37 use Amanda::Util qw( :constants :quoting);
41 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) = @_;
42 my $self = $class->SUPER::new($config);
44 if (defined $gnutar_path) {
45 $self->{gnutar} = $gnutar_path;
47 $self->{gnutar} = $Amanda::Constants::GNUTAR;
49 if (defined $smbclient_path) {
50 $self->{smbclient} = $smbclient_path;
52 $self->{smbclient} = $Amanda::Constants::SAMBA_CLIENT;
54 if (defined $amandapass) {
55 $self->{amandapass} = config_dir_relative($amandapass);
57 $self->{amandapass} = "$Amanda::Paths::CONFIG_DIR/amandapass";
60 $self->{config} = $config;
61 $self->{host} = $host;
63 $self->{disk} = $disk;
65 $self->{disk} = $device;
67 if (defined $device) {
68 $self->{device} = $device;
70 $self->{device} = $disk;
72 if ($self->{disk} =~ /^\\\\/) {
77 $self->{level} = [ @{$level} ];
78 $self->{index} = $index;
79 $self->{message} = $message;
80 $self->{collection} = $collection;
81 $self->{record} = $record;
82 $self->{calcsize} = $calcsize;
83 $self->{exclude_file} = [ @{$exclude_file} ];
84 $self->{exclude_list} = [ @{$exclude_list} ];
85 $self->{exclude_optional} = $exclude_optional;
86 $self->{include_file} = [ @{$include_file} ];
87 $self->{include_list} = [ @{$include_list} ];
88 $self->{include_optional} = $include_optional;
89 $self->{recover_mode} = $recover_mode;
90 $self->{allow_anonymous} = $allow_anonymous;
91 $self->{directory} = $directory;
97 # $self->{exclude_file}
98 # $self->{exclude_list}
99 # $self->{include_file}
100 # $self->{include_list}
104 sub validate_inexclude {
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);
113 if ($#{$self->{exclude_file}} >= 0) {
114 $self->{exclude} = [ @{$self->{exclude_file}} ];
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);
126 push @{$self->{exclude}}, $_;
130 if ($#{$self->{include_file}} >= 0) {
131 $self->{include} = [ @{$self->{include_file}} ];
133 foreach my $file (@{$self->{include_list}}) {
134 if (!open(FF, $file)) {
135 if ($self->{action} eq 'check' && !$self->{include_optional}) {
136 $self->print_to_server("Open of '$file' failed: $!",
137 $Amanda::Script_App::ERROR);
143 push @{$self->{include}}, $_;
150 # $self->{directory} == //host/share/subdir \\host\share\subdir
152 # $self->{device} == //host/share/subdir \\host\share\subdir
154 # $self->{cifshost} = //host \\host
155 # $self->{share} = //host/share \\host\share
156 # $self->{sambashare} = \\host\share \\host\share
157 # $self->{subdir} = subdir subdir
160 my $to_parse = $self->{directory};
161 $to_parse = $self->{device} if !defined $to_parse;;
163 return if !defined $to_parse;
166 if ($to_parse =~ m,^(\\\\[^\\]+\\[^\\]+)\\(.*)$,) {
170 $self->{share} = $to_parse
172 $self->{sambashare} = $self->{share};
173 $to_parse =~ m,^(\\\\[^\\]+)\\[^\\]+,;
174 $self->{cifshost} = $1;
176 if ($to_parse =~ m,^(//[^/]+/[^/]+)/(.*)$,) {
180 $self->{share} = $to_parse
182 $self->{sambashare} = $self->{share};
183 $self->{sambashare} =~ s,/,\\,g;
184 $to_parse =~ m,^(//[^/]+)/[^/]+,;
185 $self->{cifshost} = $1;
190 # Read $self->{amandapass} file.
192 # $self->{cifshost} == //host/share
193 # $self->{share} == //host/share
195 # $self->{domain} = domain to connect to.
196 # $self->{username} = username (-U)
197 # $self->{password} = password
204 $self->{domain} = undef;
205 $self->{username} = undef;
206 $self->{password} = undef;
208 debug("amandapass: $self->{amandapass}");
209 if (!open($amandapass, $self->{amandapass})) {
210 if ($self->{allow_anonymous}) {
211 $self->{username} = $self->{allow_anonymous};
212 debug("cannot open password file '$self->{amandapass}': $!\n");
213 debug("Using anonymous user: $self->{username}");
216 $self->print_to_server_and_die(
217 "cannot open password file '$self->{amandapass}': $!",
218 $Amanda::Script_App::ERROR);
222 while ($line = <$amandapass>) {
224 next if $line =~ /^#/;
225 my ($diskname, $userpasswd, $domain, $extra);
226 ($diskname, $userpasswd) = Amanda::Util::skip_quoted_string($line);
228 ($userpasswd, $domain) =
229 Amanda::Util::skip_quoted_string($userpasswd);
233 Amanda::Util::skip_quoted_string($domain);
236 debug("Trailling characters ignored in amandapass line");
238 $diskname = Amanda::Util::unquote_string($diskname);
239 $userpasswd = Amanda::Util::unquote_string($userpasswd);
240 $domain = Amanda::Util::unquote_string($domain);
241 if (defined $diskname &&
243 ($self->{unc}==0 && $diskname =~ m,^(//[^/]+)/\*$, && $1 eq $self->{cifshost}) ||
244 ($self->{unc}==1 && $diskname =~ m,^(\\\\[^\\]+)\\\*$, && $1 eq $self->{cifshost}) ||
245 $diskname eq $self->{share} ||
246 $diskname eq $self->{sambashare})) {
247 if (defined $userpasswd && $userpasswd ne "") {
248 $self->{domain} = $domain if ($domain ne "");
249 my ($username, $password) = split('%', $userpasswd, 2);
250 $self->{username} = $username;
251 $self->{password} = $password;
252 $self->{password} = undef if (defined $password && $password eq "");
254 $self->{username} = "guest";
261 if ($self->{allow_anonymous}) {
262 $self->{username} = $self->{allow_anonymous};
263 debug("Cannot find password for share $self->{share} in $self->{amandapass}");
264 debug("Using anonymous user: $self->{username}");
267 $self->print_to_server_and_die(
268 "Cannot find password for share $self->{share} in $self->{amandapass}",
269 $Amanda::Script_App::ERROR);
272 sub command_support {
275 print "CONFIG YES\n";
278 print "MAX-LEVEL 1\n";
279 print "INDEX-LINE YES\n";
280 print "INDEX-XML NO\n";
281 print "MESSAGE-LINE YES\n";
282 print "MESSAGE-XML NO\n";
283 print "RECORD YES\n";
284 print "COLLECTION NO\n";
285 print "MULTI-ESTIMATE NO\n";
286 print "CALCSIZE NO\n";
287 print "CLIENT-ESTIMATE YES\n";
288 print "EXCLUDE-FILE YES\n";
289 print "EXCLUDE-LIST YES\n";
290 print "EXCLUDE-OPTIONAL YES\n";
291 print "INCLUDE-FILE YES\n";
292 print "INCLUDE-LIST YES\n";
293 print "INCLUDE-OPTIONAL YES\n";
294 print "RECOVER-MODE SMB\n";
297 sub command_selfcheck {
301 if (!defined($self->{smbclient}) || $self->{smbclient} eq "") {
302 $self->print_to_server(
303 "smbclient not set; you must define the SMBCLIENT-PATH property",
304 $Amanda::Script_App::ERROR);
306 elsif (! -e $self->{smbclient}) {
307 $self->print_to_server("$self->{smbclient} doesn't exist",
308 $Amanda::Script_App::ERROR);
310 elsif (! -x $self->{smbclient}) {
311 $self->print_to_server("$self->{smbclient} is not executable",
312 $Amanda::Script_App::ERROR);
314 $self->print_to_server("$self->{smbclient}",
315 $Amanda::Script_App::GOOD);
316 if (!defined $self->{disk} || !defined $self->{device}) {
319 $self->parsesharename();
321 $self->validate_inexclude();
323 print "OK " . $self->{share} . "\n";
324 print "OK " . $self->{disk} . "\n";
325 print "OK " . $self->{device} . "\n";
326 print "OK " . $self->{directory} . "\n" if defined $self->{directory};
328 my ($child_rdr, $parent_wtr);
329 if (defined $self->{password}) {
330 # Don't set close-on-exec
332 pipe($child_rdr, $parent_wtr);
334 $parent_wtr->autoflush(1);
336 my($wtr, $rdr, $err);
337 $err = Symbol::gensym;
338 my $pid = open3($wtr, $rdr, $err, "-");
341 if (defined $self->{password}) {
342 my $ff = $child_rdr->fileno;
343 debug("child_rdr $ff");
344 $parent_wtr->close();
345 $ENV{PASSWD_FD} = $child_rdr->fileno;
350 push @ARGV, $self->{smbclient}, $self->{share};
351 push @ARGV, "" if (!defined $self->{password});
352 push @ARGV, "-U", $self->{username},
354 if (defined $self->{domain}) {
355 push @ARGV, "-W", $self->{domain},
357 if (defined $self->{subdir}) {
358 push @ARGV, "-D", $self->{subdir},
360 push @ARGV, "-c", "quit";
361 debug("execute: " . $self->{smbclient} . " " .
363 exec {$self->{smbclient}} @ARGV;
366 if (defined $self->{password}) {
367 my $ff = $parent_wtr->fileno;
368 debug("parent_wtr $ff");
369 $parent_wtr->print($self->{password});
370 $parent_wtr->close();
373 debug("No password");
379 debug("stderr: " . $_);
381 # message if samba server is configured with 'security = share'
382 next if /Server not using user level security and no password supplied./;
383 $self->print_to_server("smbclient: $_",
384 $Amanda::Script_App::ERROR);
392 sub command_estimate {
395 $self->parsesharename();
397 $self->validate_inexclude();
399 my $level = $self->{level}[0];
400 my ($child_rdr, $parent_wtr);
401 if (defined $self->{password}) {
402 # Don't set close-on-exec
404 pipe($child_rdr, $parent_wtr);
406 $parent_wtr->autoflush(1);
408 my($wtr, $rdr, $err);
409 $err = Symbol::gensym;
410 my $pid = open3($wtr, $rdr, $err, "-");
413 if (defined $self->{password}) {
414 my $ff = $child_rdr->fileno;
415 debug("child_rdr $ff");
416 $parent_wtr->close();
417 $ENV{PASSWD_FD} = $child_rdr->fileno;
422 push @ARGV, $self->{smbclient}, $self->{share};
423 push @ARGV, "" if (!defined($self->{password}));
424 push @ARGV, "-d", "0",
425 "-U", $self->{username},
427 if (defined $self->{domain}) {
428 push @ARGV, "-W", $self->{domain},
430 if (defined $self->{subdir}) {
431 push @ARGV, "-D", $self->{subdir},
434 push @ARGV, "-c", "archive 0;recurse;du";
436 push @ARGV, "-c", "archive 1;recurse;du";
438 debug("execute: " . $self->{smbclient} . " " .
440 exec {$self->{smbclient}} @ARGV;
443 if (defined $self->{password}) {
444 my $ff = $parent_wtr->fileno;
445 debug("parent_wtr $ff");
446 debug("password $self->{password}");
447 $parent_wtr->print($self->{password});
448 $parent_wtr->close();
453 my $size = $self->parse_estimate($err);
455 output_size($level, $size);
466 next if /blocks of size/;
467 next if /blocks available/;
470 next if /dumped \d+ files and directories/;
471 # message if samba server is configured with 'security = share'
472 next if /Server not using user level security and no password supplied./;
474 if ($_ =~ /^Total number of bytes: (\d*)/) {
478 $self->print_to_server("smbclient: $_",
479 $Amanda::Script_App::ERROR);
489 print "$level -1 -1\n";
493 my($ksize) = int $size / (1024);
494 $ksize=32 if ($ksize<32);
495 print "$level $ksize 1\n";
502 my $level = $self->{level}[0];
504 open($mesgout_fd, '>&=3') ||
505 $self->print_to_server_and_die("Can't open mesgout_fd: $!",
506 $Amanda::Script_App::ERROR);
507 $self->{mesgout} = $mesgout_fd;
509 $self->parsesharename();
511 $self->validate_inexclude();
513 my $pid_tee = open3(\*INDEX_IN, '>&STDOUT', \*INDEX_TEE, "-");
519 while (($size = POSIX::read(0, $buf, 32768)) > 0) {
520 POSIX::write(1, $buf, $size);
521 POSIX::write(2, $buf, $size);
525 my ($child_rdr, $parent_wtr);
526 if (defined $self->{password}) {
527 # Don't set close-on-exec
529 pipe($child_rdr, $parent_wtr);
531 $parent_wtr->autoflush(1);
534 $err = Symbol::gensym;
535 my $pid = open3($wtr, ">&INDEX_IN", $err, "-");
538 if (defined $self->{password}) {
539 my $ff = $child_rdr->fileno;
540 debug("child_rdr $ff");
541 $parent_wtr->close();
542 $ENV{PASSWD_FD} = $child_rdr->fileno;
546 push @ARGV, $self->{smbclient}, $self->{share};
547 push @ARGV, "" if (!defined($self->{password}));
548 push @ARGV, "-d", "0",
549 "-U", $self->{username},
551 if (defined $self->{domain}) {
552 push @ARGV, "-W", $self->{domain},
554 if (defined $self->{subdir}) {
555 push @ARGV, "-D", $self->{subdir},
560 $comm = "tarmode full reset hidden system quiet;";
562 $comm = "tarmode inc noreset hidden system quiet;";
565 if ($#{$self->{exclude}} >= 0) {
568 if ($#{$self->{include}} >= 0) {
572 if ($#{$self->{exclude}} >= 0) {
573 $comm .= " " . join(" ", @{$self->{exclude}});
575 if ($#{$self->{include}} >= 0) {
576 $comm .= " " . join(" ", @{$self->{include}});
578 push @ARGV, "-c", $comm;
579 debug("execute: " . $self->{smbclient} . " " .
581 exec {$self->{smbclient}} @ARGV;
584 if (defined $self->{password}) {
585 my $ff = $parent_wtr->fileno;
586 debug("parent_wtr $ff");
587 debug("password $self->{password}");
588 $parent_wtr->print($self->{password});
589 $parent_wtr->close();
592 debug("No password");
598 debug("$self->{gnutar} -tf -");
599 my $pid_index1 = open2($index, '<&INDEX_TEE', $self->{gnutar}, "-tf", "-");
602 my $index_fd = $index->fileno;
603 debug("index $index_fd");
604 if (defined($self->{index})) {
606 open($indexout_fd, '>&=4') ||
607 $self->print_to_server_and_die("Can't open indexout_fd: $!",
608 $Amanda::Script_App::ERROR);
609 $self->parse_backup($index, $mesgout_fd, $indexout_fd);
613 $self->parse_backup($index_fd, $mesgout_fd, undef);
619 debug("stderr: " . $_);
621 next if /^tarmode is now /;
622 next if /dumped (\d+) files and directories/;
623 # message if samba server is configured with 'security = share'
624 next if /Server not using user level security and no password supplied./;
625 if (/^Total bytes written: (\d*)/) {
628 $self->print_to_server("smbclient: $_",
629 $Amanda::Script_App::ERROR);
633 my $ksize = $size / 1024;
637 print $mesgout_fd "sendbackup: size $ksize\n";
638 print $mesgout_fd "sendbackup: end\n";
643 $self->print_to_server_and_die("smbclient returned error",
644 $Amanda::Script_App::ERROR);
651 my($fhin, $fhout, $indexout) = @_;
655 if(defined($indexout)) {
656 if(defined($self->{index})) {
668 sub command_index_from_output {
669 index_from_output(0, 1);
673 sub index_from_output {
674 my($fhin, $fhout) = @_;
677 next if /^Total bytes written:/;
684 sub command_index_from_image {
687 open($index_fd, "$self->{gnutar} --list --file - |") ||
688 $self->print_to_server_and_die("Can't run $self->{gnutar}: $!",
689 $Amanda::Script_App::ERROR);
690 index_from_output($index_fd, 1);
693 sub command_restore {
697 $self->parsesharename();
698 chdir(Amanda::Util::get_original_cwd());
700 if ($self->{recover_mode} eq "smb") {
701 $self->validate_inexclude();
703 push @cmd, $self->{smbclient}, $self->{share};
704 push @cmd, "" if (!defined $self->{password});
705 push @cmd, "-d", "0",
706 "-U", $self->{username};
708 if (defined $self->{domain}) {
709 push @cmd, "-W", $self->{domain};
711 push @cmd, "-Tx", "-";
712 if ($#{$self->{include}} >= 0) {
713 push @cmd, @{$self->{include}};
715 for(my $i=1;defined $ARGV[$i]; $i++) {
716 my $param = $ARGV[$i];
720 my ($parent_rdr, $child_wtr);
721 if (defined $self->{password}) {
722 # Don't set close-on-exec
724 pipe($parent_rdr, $child_wtr);
726 $child_wtr->autoflush(1);
728 my($wtr, $rdr, $err);
729 $err = Symbol::gensym;
730 my $pid = open3($wtr, $rdr, $err, "-");
732 $child_wtr->print($self->{password});
736 if (defined $self->{password}) {
738 $ENV{PASSWD_FD} = $parent_rdr->fileno;
740 debug("cmd:" . join(" ", @cmd));
741 exec { $cmd[0] } @cmd;
742 die("Can't exec '", $cmd[0], "'");
744 push @cmd, $self->{gnutar}, "-xpvf", "-";
745 if (defined $self->{directory}) {
746 if (!-d $self->{directory}) {
747 $self->print_to_server_and_die(
748 "Directory $self->{directory}: $!",
749 $Amanda::Script_App::ERROR);
751 if (!-w $self->{directory}) {
752 $self->print_to_server_and_die(
753 "Directory $self->{directory}: $!",
754 $Amanda::Script_App::ERROR);
756 push @cmd, "--directory", $self->{directory};
758 if ($#{$self->{include_list}} == 0) {
759 push @cmd, "--files-from", $self->{include_list}[0];
761 if ($#{$self->{exclude_list}} == 0) {
762 push @cmd, "--exclude-from", $self->{exclude_list}[0];
764 for(my $i=1;defined $ARGV[$i]; $i++) {
765 my $param = $ARGV[$i];
769 debug("cmd:" . join(" ", @cmd));
770 exec { $cmd[0] } @cmd;
771 die("Can't exec '", $cmd[0], "'");
775 sub command_validate {
778 if (!defined($self->{gnutar}) || !-x $self->{gnutar}) {
779 return $self->default_validate();
782 my(@cmd) = ($self->{gnutar}, "-tf", "-");
783 debug("cmd:" . join(" ", @cmd));
784 my $pid = open3('>&STDIN', '>&STDOUT', '>&STDERR', @cmd) ||
785 $self->print_to_server_and_die("Unable to run @cmd: $!",
786 $Amanda::Script_App::ERROR);
789 $self->print_to_server_and_die("$self->{gnutar} returned error",
790 $Amanda::Script_App::ERROR);
795 sub command_print_command {
802 Usage: amsamba <command> --config=<config> --host=<host> --disk=<disk> --device=<device> --level=<level> --index=<yes|no> --message=<text> --collection=<no> --record=<yes|no> --calcsize.
819 my $opt_smbclient_path;
821 my @opt_exclude_file;
822 my @opt_exclude_list;
823 my $opt_exclude_optional;
824 my @opt_include_file;
825 my @opt_include_list;
826 my $opt_include_optional;
827 my $opt_recover_mode;
828 my $opt_allow_anonymous;
831 Getopt::Long::Configure(qw{bundling});
833 'version' => \$opt_version,
834 'config=s' => \$opt_config,
835 'host=s' => \$opt_host,
836 'disk=s' => \$opt_disk,
837 'device=s' => \$opt_device,
838 'level=s' => \@opt_level,
839 'index=s' => \$opt_index,
840 'message=s' => \$opt_message,
841 'collection=s' => \$opt_collection,
842 'record' => \$opt_record,
843 'calcsize' => \$opt_calcsize,
844 'gnutar-path=s' => \$opt_gnutar_path,
845 'smbclient-path=s' => \$opt_smbclient_path,
846 'amandapass=s' => \$opt_amandapass,
847 'exclude-file=s' => \@opt_exclude_file,
848 'exclude-list=s' => \@opt_exclude_list,
849 'exclude-optional=s' => \$opt_exclude_optional,
850 'include-file=s' => \@opt_include_file,
851 'include-list=s' => \@opt_include_list,
852 'include-optional=s' => \$opt_include_optional,
853 'recover-mode=s' => \$opt_recover_mode,
854 'allow-anonymous=s' => \$opt_allow_anonymous,
855 'directory=s' => \$opt_directory,
858 if (defined $opt_version) {
859 print "amsamba-" . $Amanda::Constants::VERSION , "\n";
863 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);
865 $application->do($ARGV[0]);