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