Imported Upstream version 3.3.2
[debian/amanda] / server-src / amaddclient.pl
1 #!@PERL@
2 #
3 # Copyright (c) 2007-2012 Zmanda, Inc.  All Rights Reserved.
4 #
5 # This program is free software; you can redistribute it and/or modify it
6 # under the terms of the GNU General Public License version 2 as published
7 # by the Free Software Foundation.
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
22
23 use Getopt::Long;
24 use Time::Local;
25 use File::Copy;
26 use Socket;   # for gethostbyname
27
28 my $confdir="@CONFIG_DIR@";
29 my $tmpdir="@AMANDA_DBGDIR@";
30
31 my $prefix="@prefix@";
32 my $localstatedir="@localstatedir@";
33 my $amandahomedir="$localstatedir/lib/amanda";
34
35 my $amanda_user="@CLIENT_LOGIN@";
36 my $amanda_group="disk";
37 my $def_root_user="root";
38 my $def_dumptype="user-tar";
39
40 my $sp_diskfile=0;
41
42 sub usage {
43         print "$0\n";      
44         print "\t\t--config <config>         Required. Ex: DailySet1\n";       
45         print "\t\t--client <FQDN-name>      Required. Ex: server.zmanda.com\n";
46         print "\t\t--diskdev <directory>     Required. Ex: /home\n";
47         print "\t\t--m                       Modify exisiting entry\n";
48         print "\t\t[--dumptype <dumptype>    Default: user-tar]\n";
49         print "\t\t[--includefile <string>   glob expression of file(s) to include]\n";
50         print "\t\t[--includelist <file>     file contains glob expressions to include]\n";
51         print "\t\t[ specify either --includefile or --includelist ]\n";
52         print "\t\t[--excludefile <string>   glob expression of file(s) to exclude]\n";
53         print "\t\t[--excludelist <file>     file contains glob expressions to exclude]\n";
54         print "\t\t[ specify either --excludefile or --excludelist ]\n";
55         print "\t\t[--user <username>          name of user running amrecover on the client]\n";
56         print "\t\t[--auth <string>            authentication used when running amrecover]\n";
57         print "\t\t[--gnutar_list_dir <string> directory where gnutar keep its state file on the client]\n";
58         print "\t\t[--amandates <string>       file where amanda keep the date of dumplevel on the client]\n";
59         print "\t\t[--batch                    batch mode used when copying file to client]\n";
60         print "\t\t[--no-client-update         do not update files on the client";
61         print "\t\t[--help]\n";
62 }
63
64 sub mprint {
65     for $fh ( STDOUT, LOG ) {
66         print $fh @_;
67     }
68 }
69
70 sub log_and_die {
71     print LOG @_;
72     die @_;
73 }
74
75 sub is_user_right {
76     my $user = `whoami`;
77     chomp($user);
78     ( $user eq $amanda_user ) ||
79         die ("ERROR: $0 must be run by $amanda_user\n");
80 }
81
82
83 # alphabetics, numerics, and underscores, 
84 # hyphen, at sign, dot and "/" are ok
85 sub is_tainted { 
86  local($arg) = @_;
87  if ( $arg  =~ /^([-\@\/\w.]+)$/ ) {
88         return 0; # ok
89     } else {
90         return 1; # bad, tainted input
91     }
92 }
93
94
95 # modify existing entry. 
96 # only got here if -m is used and entry is found.
97 sub dle_mod {
98     my $open_seen=0;     # '{' is seen
99     my $include_done=0;  # original include line is parsed
100     my $exclude_done=0;  # original exclude line is parsed
101     my $ok=0;            # 1 if target entry is found
102
103     @ARGV = ("$confdir/$config/disklist");
104     $^I = ".tmp"; # allow inplace editing
105     while (<>) {
106         my ($one, $two, $three ) = split(/\s+/, $_);
107
108         # if include or exclude is not previously there, 
109         # take care of them here
110         if ( $one eq "}" ) {
111             $open_seen=0;
112                 if ( $include_done==0 && $ok ) {
113                    print "include list \"$includelist\"\n" if ( $includelist );
114                    print "include file \"$includefile\"\n" if ( $includefile );
115                     }
116                 if ( $exclude_done==0 && $ok ) {
117                    print "exclude list \"$excludelist\"\n" if ( $excludelist );
118                    print "exclude file \"$excludefile\"\n" if ( $excludefile );
119                     }
120             $ok=0; # reset, done with one entry
121         }   
122         
123         # take care of entry that has '{'
124         if ( $open_seen==1 ) {
125             if ( !$two  && !$three ) {   # inside {, dumptype line has 1 field only
126                 s/$one/$dumptype/ if ( $dumptype );
127             } elsif ( $two && $three ) { # inside {, include/exclude line
128                 if ( $one eq "include" ) {
129                     if ( $includelist ) {
130                         s/$two.*$/list "$includelist"/;
131                     } elsif ( $includefile ) {
132                         s/$two.*$/file "$includefile"/;
133                     }
134                     $include_done=1;
135                 }
136                 if ( $one eq "exclude" ) {
137                     if ( $excludelist ) {
138                         s/$two.*$/list "$excludelist"/;
139                     } elsif ( $excludefile ) {
140                         s/$two.*$/file "$excludefile"/;
141                     }
142                     $exclude_done=1;
143                 }
144
145             }
146         }  # inside '{'
147         
148         # entry which previously doesn't have include/exclude
149         if (( $one eq $client ) && ($two eq $diskdev) ) {
150             $ok=1;
151             if ( $three && ($three ne "{") ) {
152                 if ( $sp_diskfile==1 ) {  #previously don't have include/exclude
153                     $three = $dumptype if ( $dumptype );
154                     $includeline="include list \"$includelist\""   if ( $includelist );
155                     $includeline="include file \"$includefile\""   if ( $includefile );
156                     $excludeline="exclude list \"$excludelist\"\n" if ( $excludelist );
157                     $excludeline="exclude file \"$excludefile\"\n" if ( $excludefile );
158                     s/$three/{\n$three\n$includeline\n$excludeline}/;
159                 } else {
160                     s/$three/$dumptype/ if ( $dumptype ); #easy one, just replace dumptype.
161                     $ok=0; #done with one entry
162                 }
163             } else {
164                 $open_seen=1;
165             }
166         }
167         print;
168     }  # while loop
169     unlink("$confdir/$config/disklist.tmp");
170     exit 0;
171 }
172     
173
174
175 #main
176 my $ret=0;
177           
178 $ret= GetOptions (      "config=s"=>\$config,
179                         "client=s"=>\$client,
180                         "diskdev=s"=>\$diskdev,
181                         "dumptype=s"=>\$dumptype,
182                         "includefile=s"=>\$includefile,
183                         "includelist=s"=>\$includelist,
184                         "excludefile=s"=>\$excludefile,
185                         "excludelist=s"=>\$excludelist,
186                         "user=s"=>\$root_user,
187                         "auth=s"=>\$auth,
188                         "gnutar_list_dir=s"=>\$tarlist,
189                         "amandates=s"=>\$amandates,
190                         "batch!"=>\$batch,
191                         "m!"=>\$mod,
192                         "no-client-update!"=>\$no_client_update,
193                         "help!"=>\$help
194                         );
195
196
197 unless ( $ret ) {
198     &usage;
199     exit 1;
200 }
201
202
203 if($help) {
204     &usage;
205     exit 0;
206 }
207
208 unless (defined $config && defined $client && defined $diskdev ) {
209     print STDERR "--config, --client and --diskdev are required.\n";
210     &usage;
211     exit 1;
212 }
213 else {
214     die ("ERROR: Invalid data in config.\n")  if is_tainted($config);
215     die ("ERROR: Invalid data in client.\n")  if is_tainted($client);
216 }
217
218
219 if ( defined $includefile && defined $includelist ) {
220     print STDERR "Specify either --includefile or --includelist, not both.\n";
221     &usage;
222     exit 1;
223 }
224    
225 if ( defined $excludefile && defined $excludelist ) {
226     print STDERR "Specify either --excludefile or --excludelist, not both.\n";
227     &usage;
228     exit 1;
229 }   
230
231 $oldPATH = $ENV{'PATH'};
232 $ENV{'PATH'} = "/usr/bin:/usr/sbin:/sbin:/bin:/usr/ucb"; # force known path
233 $date=`date +%Y%m%d%H%M%S`;
234 chomp($date);
235 my $logfile="$tmpdir/amaddclient.$date.debug";
236
237 &is_user_right;
238 open (LOG, ">$logfile") || die "ERROR: Cannot create logfile : $!\n";
239 print STDOUT "Logging to $logfile\n";
240
241 my $lhost=`hostname`;
242 chomp($lhost);
243 # get our own canonical name, if possible (we don't sweat the IPv6 stuff here)
244 my $host=(gethostbyname($lhost))[0];
245
246 unless ( $host ) {
247     $host = $lhost;  #gethostbyname() failed, go with hostname output
248 }
249
250
251 my $found=0;
252 my $fhs;
253
254 # make sure dumptype is defined in dumptypes or amanda.conf file
255
256 if ( defined $dumptype ) { 
257 for $fhs ( "$confdir/template.d/dumptypes", "$confdir/$config/amanda.conf" ) {
258     open (DTYPE, $fhs) ||
259         &log_and_die ("ERROR: Cannot open $fhs file : $!\n");
260     while (<DTYPE>) {
261         if (/^\s*define\s*dumptype\s*$dumptype\s*{/) {
262             $found=1;
263             last;
264         }
265         }
266         close (DTYPE);
267     }
268
269     unless ( $found ) {
270         &log_and_die ("ERROR: $dumptype not defined in $confdir/template.d/dumptypes or $confdir/$config/amanda.conf\n");
271     }
272 }
273
274 # create disklist file
275     unless ( -e "$confdir/$config"  ) {
276         &log_and_die ("ERROR: $confdir/$config not found\n");
277     }
278     $found=0;
279     if (defined $includefile || defined $includelist 
280                     || defined $excludefile || defined $excludelist) {
281         $sp_diskfile=1;
282         }
283
284     unless ( -e "$confdir/$config/disklist" ) {  # create it if necessary
285         open (DLE, ">$confdir/$config/disklist") || 
286             &log_and_die ("ERROR: Cannot create $confdir/$config/disklist file : $!\n");
287         print DLE "#This file is generated by amaddclient.\n";
288         print DLE "#Don't edit it manually, otherwise, 'amaddclient -m ...' might not work\n";
289     }
290
291     open (DLE, "+<$confdir/$config/disklist")    # open for read/write
292         || &log_and_die ("ERROR: Cannot open $confdir/$config/disklist file : $!\n");
293     while (<DLE>) {
294         my ($lclient, $ldiskdev, $dontcare ) = split(/\s+/, $_);
295         if (( $lclient eq $client ) && ($ldiskdev eq $diskdev) ) {
296             $found=1;
297             last;
298         }
299     }
300
301 # if found and -m, do modification and exit 
302     if ( defined $mod ) {
303         if ( $found ) {
304         &dle_mod;
305     } else {
306         &log_and_die ("ERROR: $client $diskdev not found, cannot modify\n");
307     }
308     }
309
310 unless ( defined $dumptype ) {
311     $dumptype=$def_dumptype;
312
313     if ( $found==1 ) {
314         &mprint("$confdir/$config/disklist has '$client $diskdev ...' entry, file not updated\n"); }
315     else {
316         print DLE "$client  $diskdev ";
317         print DLE "{\n$dumptype\n" if ($sp_diskfile);
318         if ( defined $includefile ) {
319             print DLE "include file \"$includefile\"\n";
320         }
321         elsif ( defined $includelist ) {
322             print DLE "include list \"$includelist\"\n";
323         }
324         if ( defined $excludefile ) {
325             print DLE "exclude file \"$excludefile\"\n";
326         }
327         elsif ( defined $excludelist ) {
328             print DLE "exclude list \"$excludelist\"\n";
329         }
330         print DLE "}\n" if ($sp_diskfile);
331
332         print DLE "  $dumptype\n" if ($sp_diskfile==0);
333         &mprint ("$confdir/$config/disklist updated\n");
334         close (DLE);
335     }
336
337
338 # update .amandahosts on server and client
339     my $scp="scp";
340     my $scp_opt1="-p";   # p: preserve mode
341     my $scp_opt2="-o ConnectTimeout=15";   #timeout after 15 seconds
342     my $ssh="ssh";
343     my $ssh_opt="-x"; # -x as a placeholder, otherwise ssh complains
344     my $mkdir="mkdir -p";
345     my $client_conf_dir="$confdir/$config";
346     my $amanda_client_conf="$client_conf_dir/amanda-client.conf";
347     my $file="$amandahomedir/.amandahosts";
348     my $client_file="$amandahomedir/amanda-client.conf-$client";
349    
350    if ( defined $batch ) {
351     $scp_opt1="-Bp";
352     $ssh_opt="-o BatchMode=yes";
353   }
354     
355     &mprint ("updating $file on $host\n");
356     unless ( defined $root_user ) {
357     $root_user=$def_root_user;
358   }
359     $found=0;
360     open (HFILE, "+<$file") 
361         || &log_and_die ("ERROR: Cannot open $file : $!\n");
362         
363         while (<HFILE>) {
364             if (/^\s*$client\s*$root_user\s*amindexd\s*amidxtaped\s/) {
365                 $found=1;
366                 last;
367             }
368         }
369     if ( $found==1 ) {
370         &mprint ("$file contains $client $root_user, file not updated\n") ; }
371     else {
372         print HFILE "$client  $root_user amindexd amidxtaped\n";
373         close (HFILE);
374     }
375
376 # update client .amandahosts
377 unless ( $no_client_update ) {
378      
379     &mprint ("Attempting to update $file on $client\n");
380
381     chdir ("$amandahomedir");
382     system "$scp", "$scp_opt1", "$scp_opt2", "$amanda_user\@$client:$file", "$file.tmp";
383     $exit_value  = $? >> 8;
384     if ( $exit_value !=0 ) {
385         &mprint ("WARNING: $scp from $client not successful.\n");
386         &mprint ("Check $client:$file file.\n");
387         &mprint ("If entry '$host $amanda_user' is not present,\n");
388         &mprint ("append the entry to the file manually.\n");
389     }
390     else { 
391     $found=0;
392     unless ( -e "$file.tmp" ) {
393         &mprint ("WARNING: $file.tmp not found\n"); }
394     else {
395         open (CFILE, "+<$file.tmp") 
396             || &log_and_die ("ERROR: Cannot open $file.tmp file : $!\n");
397         while (<CFILE>) {
398             if (/^\s*$host\s*$amanda_user\s*amdump\s/) {
399                 $found=1;
400                 last;
401             }
402         }
403         if ( $found==1 ) {
404             &mprint ("$file contains $host $amanda_user, file not updated\n") ; }
405         else {
406             print CFILE "$host  $amanda_user amdump\n";
407             close (CFILE);
408             
409             #make sure permission mode is correct
410             chmod (0600, "$file.tmp");
411             system "$scp", "$scp_opt1", "$scp_opt2", "$file.tmp", "$client:$file";
412             $exit_value  = $? >> 8;
413             if ( $exit_value !=0 ) {
414                 &mprint ("WARNING: $scp to $client not successful.\n");
415                 &mprint ("Check $client:$file file.\n");
416                 &mprint ("If entry '$host $amanda_user amdump' is not present,\n");
417                 &mprint ("append the entry to the file manually.\n");
418             }
419     
420         } 
421     }
422     unlink ("$file.tmp") || &mprint("unlink $file.tmp failed: $!\n");
423     &mprint ("$client:$file updated successfully\n");
424   }
425   }
426
427 # done updating client .amandahosts
428
429 #create amanda-client.conf and scp over to client
430
431 unless ( $no_client_update ) {
432 &mprint ("Creating amanda-client.conf for $client\n");
433
434 $auth="bsdtcp" unless ( defined $auth ); 
435
436 open (ACFILE, ">$client_file") || &log_and_die ("ERROR: Cannot open $client_file file : $!\n");
437  print ACFILE "#amanda-client.conf - Amanda client configuration file.\n";
438  print ACFILE "conf            \"$config\"\n";
439  print ACFILE "index_server    \"$host\"\n";
440  print ACFILE "tape_server     \"$host\"\n";
441  print ACFILE "#  auth  - authentication scheme to use between server and client.\n";
442  print ACFILE "#          Valid values are 'bsdtcp' or 'ssh'\n";
443  print ACFILE "auth            \"$auth\"\n";
444  print ACFILE "# ssh keys file if ssh auth is used\n";
445  print ACFILE "ssh_keys        \"$amandahomedir/.ssh/id_rsa_amrecover\"\n";
446  print ACFILE "gnutar_list_dir \"$tarlist\"\n" if ( defined $tarlist );
447  print ACFILE "amandates       \"$amandates\"\n" if ( defined $amandates ); 
448
449 close (ACFILE);
450 &mprint ("Creating  $client_conf_dir on $client\n");
451 system "$ssh", "$ssh_opt", "$amanda_user\@$client", "$mkdir", "$client_conf_dir";
452 $exit_value  = $? >> 8;
453 if ( $exit_value !=0 ) {
454   &mprint ("WARNING: Cannot create $client_conf_dir on $client\n");
455   &mprint ("Please copy $client_file to $client manually\n");
456 } else { 
457   chmod (0600, "$client_file");
458   system "$scp", "$scp_opt1", "$scp_opt2", "$client_file", "$amanda_user\@$client:$amanda_client_conf";
459   $exit_value  = $? >> 8;
460   if ( $exit_value !=0 ) {
461     &mprint ("WARNING: Cannot copy $client_file to $client\n");
462     &mprint ("Please copy $client_file to $client:$client_conf_dir manually\n");
463   } else {
464     &mprint ("Copy $client_file to $client successfully\n");
465     unlink($client_file);
466   }      
467 }
468 }
469
470 #create gnutar_list_dir
471 if ( defined $tarlist && !defined $no_client_update ) {
472  system "$ssh", "$ssh_opt", "$amanda_user\@$client", "$mkdir", "$gnutar_list_dir";
473  $exit_value  = $? >> 8;
474 if ( $exit_value !=0 ) {
475   &mprint ("WARNING: Cannot create $gnutar_list_dir on $client\n"); 
476   &mprint ("Please create $gnutar_list_dir on $client manually\n");
477 } else { 
478   &mprint ("$client_file created on $client successfully\n");
479 }
480 }
481
482 &mprint ("File /var/lib/amanda/example/xinetd.amandaclient contains the latest Amanda client daemon configuration.\n");
483 &mprint ("Please merge it to /etc/xinetd.d/amandaclient.\n");
484  
485 $ENV{'PATH'} = $oldPATH;
486 close (LOG);
487
488 #THE END                                       
489