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